* [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) @ 2018-03-14 0:08 Jaegeuk Kim 2018-03-14 0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim 2018-03-16 8:26 ` [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Chao Yu 0 siblings, 2 replies; 8+ messages in thread From: Jaegeuk Kim @ 2018-03-14 0:08 UTC (permalink / raw) To: linux-f2fs-devel; +Cc: Jaegeuk Kim, Hyojun Kim From: Hyojun Kim <hyojun@google.com> It was reported that #pragma pack(1) could create unwanted influences. pack(push, 1) and pack(pop) are used instead. Signed-off-by: Hyojun Kim <hyojun@google.com> Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org> --- include/f2fs_fs.h | 80 ++----------------------------------------------------- include/quota.h | 11 ++------ 2 files changed, 4 insertions(+), 87 deletions(-) diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h index 3121e0e..c460050 100644 --- a/include/f2fs_fs.h +++ b/include/f2fs_fs.h @@ -569,17 +569,12 @@ enum { /* * For superblock */ -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif +#pragma pack(push, 1) struct f2fs_device { __u8 path[MAX_PATH_LEN]; __le32 total_segments; } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_super_block { __le32 magic; /* Magic Number */ __le16 major_ver; /* Major Version */ @@ -639,9 +634,6 @@ struct f2fs_super_block { #define CP_ORPHAN_PRESENT_FLAG 0x00000002 #define CP_UMOUNT_FLAG 0x00000001 -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_checkpoint { __le64 checkpoint_ver; /* checkpoint block version number */ __le64 user_block_count; /* # of user blocks */ @@ -683,9 +675,6 @@ struct f2fs_checkpoint { */ #define F2FS_ORPHANS_PER_BLOCK 1020 -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_orphan_block { __le32 ino[F2FS_ORPHANS_PER_BLOCK]; /* inode numbers */ __le32 reserved; /* reserved */ @@ -698,9 +687,6 @@ struct f2fs_orphan_block { /* * For NODE structure */ -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_extent { __le32 fofs; /* start file offset of the extent */ __le32 blk_addr; /* start block address of the extent */ @@ -767,9 +753,6 @@ struct f2fs_extent { #define file_is_encrypt(fi) ((fi)->i_advise & FADVISE_ENCRYPT_BIT) #define file_enc_name(fi) ((fi)->i_advise & FADVISE_ENC_NAME_BIT) -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_inode { __le16 i_mode; /* file mode */ __u8 i_advise; /* file hints */ @@ -813,16 +796,10 @@ struct f2fs_inode { } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct direct_node { __le32 addr[ADDRS_PER_BLOCK]; /* array of data block address */ } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct indirect_node { __le32 nid[NIDS_PER_BLOCK]; /* array of data block address */ } __attribute__((packed)); @@ -836,9 +813,6 @@ enum { #define XATTR_NODE_OFFSET ((((unsigned int)-1) << OFFSET_BIT_SHIFT) \ >> OFFSET_BIT_SHIFT) -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct node_footer { __le32 nid; /* node id */ __le32 ino; /* inode nunmber */ @@ -847,9 +821,6 @@ struct node_footer { __le32 next_blkaddr; /* next node page block address */ } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_node { /* can be one of three types: inode, direct, and indirect types */ union { @@ -868,18 +839,12 @@ struct f2fs_node { #define DEFAULT_NAT_ENTRY_RATIO 20 -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_nat_entry { __u8 version; /* latest version of cached nat entry */ __le32 ino; /* inode number */ __le32 block_addr; /* block address */ } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_nat_block { struct f2fs_nat_entry entries[NAT_ENTRY_PER_BLOCK]; } __attribute__((packed)); @@ -916,18 +881,12 @@ struct f2fs_nat_block { ((le16_to_cpu((raw_sit)->vblocks) & ~SIT_VBLOCKS_MASK) \ >> SIT_VBLOCKS_SHIFT) -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_sit_entry { __le16 vblocks; /* reference above */ __u8 valid_map[SIT_VBLOCK_MAP_SIZE]; /* bitmap for valid blocks */ __le64 mtime; /* segment age for cleaning */ } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_sit_block { struct f2fs_sit_entry entries[SIT_ENTRY_PER_BLOCK]; } __attribute__((packed)); @@ -953,16 +912,10 @@ struct f2fs_sit_block { #define SUM_ENTRIES_SIZE (SUMMARY_SIZE * ENTRIES_IN_SUM) /* a summary entry for a 4KB-sized block in a segment */ -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_summary { __le32 nid; /* parent node id */ union { __u8 reserved[3]; -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct { __u8 version; /* node version number */ __le16 ofs_in_node; /* block index in parent node */ @@ -974,9 +927,6 @@ struct f2fs_summary { #define SUM_TYPE_NODE (1) #define SUM_TYPE_DATA (0) -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct summary_footer { unsigned char entry_type; /* SUM_TYPE_XXX */ __le32 check_sum; /* summary checksum */ @@ -1008,49 +958,31 @@ enum { SIT_JOURNAL }; -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct nat_journal_entry { __le32 nid; struct f2fs_nat_entry ne; } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct nat_journal { struct nat_journal_entry entries[NAT_JOURNAL_ENTRIES]; __u8 reserved[NAT_JOURNAL_RESERVED]; } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct sit_journal_entry { __le32 segno; struct f2fs_sit_entry se; } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct sit_journal { struct sit_journal_entry entries[SIT_JOURNAL_ENTRIES]; __u8 reserved[SIT_JOURNAL_RESERVED]; } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_extra_info { __le64 kbytes_written; __u8 reserved[EXTRA_INFO_RESERVED]; } __attribute__((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_journal { union { __le16 n_nats; @@ -1065,9 +997,6 @@ struct f2fs_journal { } __attribute__((packed)); /* 4KB-sized summary block structure */ -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_summary_block { struct f2fs_summary entries[ENTRIES_IN_SUM]; struct f2fs_journal journal; @@ -1107,9 +1036,6 @@ typedef __le32 f2fs_hash_t; NR_DENTRY_IN_BLOCK + SIZE_OF_DENTRY_BITMAP)) /* One directory entry slot representing F2FS_SLOT_LEN-sized file name */ -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_dir_entry { __le32 hash_code; /* hash code of file name */ __le32 ino; /* inode number */ @@ -1118,9 +1044,6 @@ struct f2fs_dir_entry { } __attribute__((packed)); /* 4KB-sized directory entry block */ -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct f2fs_dentry_block { /* validity bitmap for directory entries in each block */ __u8 dentry_bitmap[SIZE_OF_DENTRY_BITMAP]; @@ -1128,6 +1051,7 @@ struct f2fs_dentry_block { struct f2fs_dir_entry dentry[NR_DENTRY_IN_BLOCK]; __u8 filename[NR_DENTRY_IN_BLOCK][F2FS_SLOT_LEN]; } __attribute__((packed)); +#pragma pack(pop) /* for inline stuff */ #define DEF_INLINE_RESERVED_SIZE 1 diff --git a/include/quota.h b/include/quota.h index cfb9861..f578621 100644 --- a/include/quota.h +++ b/include/quota.h @@ -44,18 +44,13 @@ enum quota_type { #define QT_TREEOFF 1 /* Offset of tree in file in blocks */ -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif +#pragma pack(push, 1) struct v2_disk_dqheader { u_int32_t dqh_magic; /* Magic number identifying file */ u_int32_t dqh_version; /* File version */ } __attribute__ ((packed)); /* Header with type and version specific information */ -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct v2_disk_dqinfo { u_int32_t dqi_bgrace; /* Time before block soft limit becomes hard limit */ u_int32_t dqi_igrace; /* Time before inode soft limit becomes hard limit */ @@ -65,9 +60,6 @@ struct v2_disk_dqinfo { u_int32_t dqi_free_entry; /* Number of block with at least one free entry */ } __attribute__ ((packed)); -#ifdef ANDROID_WINDOWS_HOST -#pragma pack(1) -#endif struct v2r1_disk_dqblk { __le32 dqb_id; /* id this quota applies to */ __le32 dqb_pad; @@ -82,5 +74,6 @@ struct v2r1_disk_dqblk { __le64 dqb_btime; /* time limit for excessive disk use */ __le64 dqb_itime; /* time limit for excessive inode use */ } __attribute__ ((packed)); +#pragma pack(pop) #endif -- 2.15.0.531.g2ccb3012c9-goog ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot ^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU 2018-03-14 0:08 [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Jaegeuk Kim @ 2018-03-14 0:08 ` Jaegeuk Kim 2018-03-14 2:10 ` Junling Zheng 2018-03-16 8:29 ` [PATCH 2/2] " Chao Yu 2018-03-16 8:26 ` [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Chao Yu 1 sibling, 2 replies; 8+ messages in thread From: Jaegeuk Kim @ 2018-03-14 0:08 UTC (permalink / raw) To: linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim From: Hyojun Kim <hyojun@google.com> sg_write_buffer sources are added for FFU. Signed-off-by: Hyojun Kim <hyojun@google.com> Signed-off-by: Jaegeuk Kim <jaegeuk@google.com> --- tools/sg_write_buffer/Android.bp | 27 + tools/sg_write_buffer/include/freebsd_nvme_ioctl.h | 156 + tools/sg_write_buffer/include/sg_cmds.h | 21 + tools/sg_write_buffer/include/sg_cmds_basic.h | 310 ++ tools/sg_write_buffer/include/sg_cmds_extra.h | 369 +++ tools/sg_write_buffer/include/sg_cmds_mmc.h | 52 + tools/sg_write_buffer/include/sg_io_linux.h | 185 ++ tools/sg_write_buffer/include/sg_lib.h | 602 ++++ tools/sg_write_buffer/include/sg_lib_data.h | 121 + tools/sg_write_buffer/include/sg_linux_inc.h | 56 + tools/sg_write_buffer/include/sg_pr2serr.h | 30 + tools/sg_write_buffer/include/sg_pt.h | 215 ++ tools/sg_write_buffer/include/sg_pt_linux.h | 171 + tools/sg_write_buffer/include/sg_pt_nvme.h | 172 + tools/sg_write_buffer/include/sg_pt_win32.h | 473 +++ tools/sg_write_buffer/include/sg_unaligned.h | 325 ++ tools/sg_write_buffer/sg_cmds_basic.c | 663 ++++ tools/sg_write_buffer/sg_cmds_basic2.c | 1069 ++++++ tools/sg_write_buffer/sg_cmds_extra.c | 2524 ++++++++++++++ tools/sg_write_buffer/sg_cmds_mmc.c | 382 +++ tools/sg_write_buffer/sg_io_linux.c | 256 ++ tools/sg_write_buffer/sg_lib.c | 3494 ++++++++++++++++++++ tools/sg_write_buffer/sg_lib_data.c | 1684 ++++++++++ tools/sg_write_buffer/sg_pt_common.c | 141 + tools/sg_write_buffer/sg_pt_linux.c | 964 ++++++ tools/sg_write_buffer/sg_pt_linux_nvme.c | 1185 +++++++ tools/sg_write_buffer/sg_write_buffer.c | 516 +++ 27 files changed, 16163 insertions(+) create mode 100644 tools/sg_write_buffer/Android.bp create mode 100644 tools/sg_write_buffer/include/freebsd_nvme_ioctl.h create mode 100644 tools/sg_write_buffer/include/sg_cmds.h create mode 100644 tools/sg_write_buffer/include/sg_cmds_basic.h create mode 100644 tools/sg_write_buffer/include/sg_cmds_extra.h create mode 100644 tools/sg_write_buffer/include/sg_cmds_mmc.h create mode 100644 tools/sg_write_buffer/include/sg_io_linux.h create mode 100644 tools/sg_write_buffer/include/sg_lib.h create mode 100644 tools/sg_write_buffer/include/sg_lib_data.h create mode 100644 tools/sg_write_buffer/include/sg_linux_inc.h create mode 100644 tools/sg_write_buffer/include/sg_pr2serr.h create mode 100644 tools/sg_write_buffer/include/sg_pt.h create mode 100644 tools/sg_write_buffer/include/sg_pt_linux.h create mode 100644 tools/sg_write_buffer/include/sg_pt_nvme.h create mode 100644 tools/sg_write_buffer/include/sg_pt_win32.h create mode 100644 tools/sg_write_buffer/include/sg_unaligned.h create mode 100644 tools/sg_write_buffer/sg_cmds_basic.c create mode 100644 tools/sg_write_buffer/sg_cmds_basic2.c create mode 100644 tools/sg_write_buffer/sg_cmds_extra.c create mode 100644 tools/sg_write_buffer/sg_cmds_mmc.c create mode 100644 tools/sg_write_buffer/sg_io_linux.c create mode 100644 tools/sg_write_buffer/sg_lib.c create mode 100644 tools/sg_write_buffer/sg_lib_data.c create mode 100644 tools/sg_write_buffer/sg_pt_common.c create mode 100644 tools/sg_write_buffer/sg_pt_linux.c create mode 100644 tools/sg_write_buffer/sg_pt_linux_nvme.c create mode 100644 tools/sg_write_buffer/sg_write_buffer.c diff --git a/tools/sg_write_buffer/Android.bp b/tools/sg_write_buffer/Android.bp new file mode 100644 index 0000000..5222a59 --- /dev/null +++ b/tools/sg_write_buffer/Android.bp @@ -0,0 +1,27 @@ +cc_defaults { + name: "sg3-utils-defaults", + cflags: [ + "-Wno-unused-function" + ], + local_include_dirs: [ + "include", + ], +} + +cc_binary { + name: "sg_write_buffer", + defaults: [ "sg3-utils-defaults" ], + srcs: [ + "sg_write_buffer.c", + "sg_cmds_basic.c", + "sg_cmds_basic2.c", + "sg_cmds_extra.c", + "sg_cmds_mmc.c", + "sg_io_linux.c", + "sg_lib.c", + "sg_lib_data.c", + "sg_pt_common.c", + "sg_pt_linux.c", + "sg_pt_linux_nvme.c", + ], +} diff --git a/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h b/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h new file mode 100644 index 0000000..f5d2443 --- /dev/null +++ b/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h @@ -0,0 +1,156 @@ +PROPS-END +/*- + * Copyright (C) 2012-2013 Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + + +#include <sys/param.h> + +#define NVME_PASSTHROUGH_CMD _IOWR('n', 0, struct nvme_pt_command) + +#if __FreeBSD_version < 1100110 +struct nvme_command +{ + /* dword 0 */ + uint16_t opc : 8; /* opcode */ + uint16_t fuse : 2; /* fused operation */ + uint16_t rsvd1 : 6; + uint16_t cid; /* command identifier */ + + /* dword 1 */ + uint32_t nsid; /* namespace identifier */ + + /* dword 2-3 */ + uint32_t rsvd2; + uint32_t rsvd3; + + /* dword 4-5 */ + uint64_t mptr; /* metadata pointer */ + + /* dword 6-7 */ + uint64_t prp1; /* prp entry 1 */ + + /* dword 8-9 */ + uint64_t prp2; /* prp entry 2 */ + + /* dword 10-15 */ + uint32_t cdw10; /* command-specific */ + uint32_t cdw11; /* command-specific */ + uint32_t cdw12; /* command-specific */ + uint32_t cdw13; /* command-specific */ + uint32_t cdw14; /* command-specific */ + uint32_t cdw15; /* command-specific */ +} __packed; + +struct nvme_status { + + uint16_t p : 1; /* phase tag */ + uint16_t sc : 8; /* status code */ + uint16_t sct : 3; /* status code type */ + uint16_t rsvd2 : 2; + uint16_t m : 1; /* more */ + uint16_t dnr : 1; /* do not retry */ +} __packed; + +struct nvme_completion { + + /* dword 0 */ + uint32_t cdw0; /* command-specific */ + + /* dword 1 */ + uint32_t rsvd1; + + /* dword 2 */ + uint16_t sqhd; /* submission queue head pointer */ + uint16_t sqid; /* submission queue identifier */ + + /* dword 3 */ + uint16_t cid; /* command identifier */ + struct nvme_status status; +} __packed; + +struct nvme_pt_command { + + /* + * cmd is used to specify a passthrough command to a controller or + * namespace. + * + * The following fields from cmd may be specified by the caller: + * * opc (opcode) + * * nsid (namespace id) - for admin commands only + * * cdw10-cdw15 + * + * Remaining fields must be set to 0 by the caller. + */ + struct nvme_command cmd; + + /* + * cpl returns completion status for the passthrough command + * specified by cmd. + * + * The following fields will be filled out by the driver, for + * consumption by the caller: + * * cdw0 + * * status (except for phase) + * + * Remaining fields will be set to 0 by the driver. + */ + struct nvme_completion cpl; + + /* buf is the data buffer associated with this passthrough command. */ + void * buf; + + /* + * len is the length of the data buffer associated with this + * passthrough command. + */ + uint32_t len; + + /* + * is_read = 1 if the passthrough command will read data into the + * supplied buffer from the controller. + * + * is_read = 0 if the passthrough command will write data from the + * supplied buffer to the controller. + */ + uint32_t is_read; + + /* + * driver_lock is used by the driver only. It must be set to 0 + * by the caller. + */ + struct mtx * driver_lock; +}; +#else +#include <dev/nvme/nvme.h> +#endif + +#define nvme_completion_is_error(cpl) \ + ((cpl)->status.sc != 0 || (cpl)->status.sct != 0) + +#define NVME_CTRLR_PREFIX "/dev/nvme" +#define NVME_NS_PREFIX "ns" diff --git a/tools/sg_write_buffer/include/sg_cmds.h b/tools/sg_write_buffer/include/sg_cmds.h new file mode 100644 index 0000000..690f53a --- /dev/null +++ b/tools/sg_write_buffer/include/sg_cmds.h @@ -0,0 +1,21 @@ +#ifndef SG_CMDS_H +#define SG_CMDS_H + +/******************************************************************** + * This header did contain wrapper declarations for many SCSI commands + * up until sg3_utils version 1.22 . In that version, the command + * wrappers were broken into two groups, the 'basic' ones found in the + * "sg_cmds_basic.h" header and the 'extra' ones found in the + * "sg_cmds_extra.h" header. This header now simply includes those two + * headers. + * In sg3_utils version 1.26 the sg_cmds_mmc.h header was added and + * contains some MMC specific commands. + * The corresponding function definitions are found in the sg_cmds_basic.c, + * sg_cmds_extra.c and sg_cmds_mmc.c files. + ********************************************************************/ + +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#include "sg_cmds_mmc.h" + +#endif diff --git a/tools/sg_write_buffer/include/sg_cmds_basic.h b/tools/sg_write_buffer/include/sg_cmds_basic.h new file mode 100644 index 0000000..507effa --- /dev/null +++ b/tools/sg_write_buffer/include/sg_cmds_basic.h @@ -0,0 +1,310 @@ +#ifndef SG_CMDS_BASIC_H +#define SG_CMDS_BASIC_H + +/* + * Copyright (c) 2004-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. + */ + +/* + * Error, warning and verbose output is sent to the file pointed to by + * sg_warnings_strm which is declared in sg_lib.h and can be set with + * the sg_set_warnings_strm() function. If not given sg_warnings_strm + * defaults to stderr. + * If 'noisy' is false and 'verbose' is zero then following functions should + * not output anything to sg_warnings_strm. If 'noisy' is true and + * 'verbose' is zero then Unit Attention, Recovered, Medium and Hardware + * errors (sense keys) send output to sg_warnings_strm. Increasing values + * of 'verbose' send increasing amounts of (debug) output to + * sg_warnings_strm. + */ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Invokes a SCSI INQUIRY command and yields the response + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other errors */ +int sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. + * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so + * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION + * CODES command instead). Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI LOG SELECT command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Log Select not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ABORTED_COMMAND, * SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code, + int subpg_code, unsigned char * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Log Sense not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, unsigned char * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Same as sg_ll_log_sense() apart from timeout_secs and residp. See + * sg_ll_inquiry_v2() for their description */ +int sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, unsigned char * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Same as sg_ll_mode_sense10() apart from timeout_secs and residp. See + * sg_ll_inquiry_v2() for their description */ +int sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, + int pg_code, int sub_pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command (SPC-3) + * prevent==0 allows removal, prevent==1 prevents removal ... + * Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> command not supported + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose); + +/* Invokes a SCSI READ CAPACITY (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION + * -> perhaps media changed, SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success, + * SG_LIB_CAT_UNIT_ATTENTION -> media changed??, SG_LIB_CAT_INVALID_OP + * -> cdb not supported, SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Luns not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */ +int sg_ll_report_luns(int sg_fd, int select_report, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI REQUEST SENSE command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Request Sense not supported??, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI START STOP UNIT command (SBC + MMC). + * Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Start stop unit not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure + * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and + * format_layer_number(mmc) fields. They also overlap on the noflush(sbc) + * and fl(mmc) one bit field. This is the cause of the awkardly named + * pc_mod__fl_num and noflush__fl arguments to this function. */ +int sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num, + int power_cond, bool noflush__fl, bool loej, + bool start, bool noisy, int verbose); + +/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_INVALID_OP -> cdb not supported, + * SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */ +int sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group, + unsigned int lba, unsigned int count, bool noisy, + int verbose); + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other failure */ +int sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose); + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Looks for progress indicator if 'progress' non-NULL; + * if found writes value [0..65535] else write -1. + * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ABORTED_COMMAND, SG_LIB_CAT_NOT_READY -> + * device not ready, -1 -> other failure */ +int sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress, + bool noisy, int verbose); + + +struct sg_simple_inquiry_resp { + unsigned char peripheral_qualifier; + unsigned char peripheral_type; + unsigned char byte_1; /* was 'rmb' prior to version 1.39 */ + /* now rmb == !!(0x80 & byte_1) */ + unsigned char version; /* as per recent drafts: whole of byte 2 */ + unsigned char byte_3; + unsigned char byte_5; + unsigned char byte_6; + unsigned char byte_7; + char vendor[9]; /* T10 field is 8 bytes, NUL char appended */ + char product[17]; + char revision[5]; +}; + +/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response. + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other errors */ +int sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data, + bool noisy, int verbose); + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. In most cases users are + * interested in the first mode page. This function returns the (byte) + * offset of the start of the first mode page. Set mode_sense_6 to true for + * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful + * or -1 if failure. If there is a failure a message is written to err_buff + * if it is non-NULL and err_buff_len > 0. */ +int sg_mode_page_offset(const unsigned char * resp, int resp_len, + bool mode_sense_6, char * err_buff, int err_buff_len); + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. This functions returns the + * length (in bytes) of those three components. Note that the return value + * can exceed resp_len in which case the MODE SENSE command should be + * re-issued with a larger response buffer. If bd_lenp is non-NULL and if + * successful the block descriptor length (in bytes) is written to *bd_lenp. + * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10) + * responses. Returns -1 if there is an error (e.g. response too short). */ +int sg_msense_calc_length(const unsigned char * resp, int resp_len, + bool mode_sense_6, int * bd_lenp); + +/* Fetches current, changeable, default and/or saveable modes pages as + * indicated by pcontrol_arr for given pg_code and sub_pg_code. If + * mode6==0 then use MODE SENSE (10) else use MODE SENSE (6). If + * flexible set and mode data length seems wrong then try and + * fix (compensating hack for bad device or driver). pcontrol_arr + * should have 4 elements for output of current, changeable, default + * and saved values respectively. Each element should be NULL or + * at least mx_mpage_len bytes long. + * Return of 0 -> overall success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_MALFORMED -> bad response, -1 -> other failure. + * If success_mask pointer is not NULL then first zeros it. Then set bits + * 0, 1, 2 and/or 3 if the current, changeable, default and saved values + * respectively have been fetched. If error on current page + * then stops and returns that error; otherwise continues if an error is + * detected but returns the first error encountered. */ +int sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, + int sub_pg_code, bool dbd, bool flexible, + int mx_mpage_len, int * success_mask, + void * pcontrol_arr[], int * reported_lenp, + int verbose); + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. Implementation calls scsi_pt_open_device(). */ +int sg_cmds_open_device(const char * device_name, bool read_only, int verbose); + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. Implementation calls scsi_pt_open_flags(). */ +int sg_cmds_open_flags(const char * device_name, int flags, int verbose); + +/* Returns 0 if successful. If error in Unix returns negated errno. + Implementation calls scsi_pt_close_device(). */ +int sg_cmds_close_device(int device_fd); + +const char * sg_cmds_version(); + +#define SG_NO_DATA_IN 0 + +struct sg_pt_base; + +/* This is a helper function used by sg_cmds_* implementations after the + * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid + * sense data is found it is decoded and output to sg_warnings_strm (def: + * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for + * sense data (may not be fatal), -1 for failed, 0, or a positive number. If + * 'mx_di_len > 0' then asks pass-through for resid and returns + * (mx_di_len - resid); otherwise returns 0. So for data-in it should return + * the actual number of bytes received. For data-out (to device) or no data + * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category + * output via 'o_sense_cat' pointer (if not NULL). Note that several sense + * categories also have data in bytes received; -2 is still returned. */ +int sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin, + int pt_res, int mx_di_len, + const unsigned char * sense_b, bool noisy, + int verbose, int * o_sense_cat); + +/* NVMe devices use a different command set. This function will return true + * if the device associated with 'pvtp' is a NVME device, else it will + * return false (e.g. for SCSI devices). */ +bool sg_cmds_is_nvme(const struct sg_pt_base * ptvp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_cmds_extra.h b/tools/sg_write_buffer/include/sg_cmds_extra.h new file mode 100644 index 0000000..fbc2377 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_cmds_extra.h @@ -0,0 +1,369 @@ +#ifndef SG_CMDS_EXTRA_H +#define SG_CMDS_EXTRA_H + +/* + * Copyright (c) 2004-2018 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. + */ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Note: all functions that have an 'int timeout_secs' argument will use + * that value if it is > 0. Otherwise they will set an internal default + * which is currently 60 seconds. This timeout is typically applied in the + * SCSI stack above the initiator. If it goes off then the SCSI command is + * aborted and there can be other unwelcome side effects. Note that some + * commands (e.g. FORMAT UNIT and the Third Party copy commands) can take + * a lot longer than the default timeout. */ + + +/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is + * selected by the cdb_len argument that can take values of 12, 16 or 32 + * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9 + * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from + * the control byte, the rest is copied into an internal cdb which is then + * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15 + * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the + * timeout is set to 60 seconds. For data in or out transfers set dinp or + * doutp, and dlen to the number of bytes to transfer. If dlen is zero then + * no data transfer is assumed. If sense buffer obtained then it is written + * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is + * obtained then written to ata_return_dp, else ata_return_dp[0] is set to + * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers. + * Returns SCSI status value (>= 0) or -1 if other error. Users are + * expected to check the sense buffer themselves. If available the data in + * resid is written to residp. Note in SAT-2 and later, fixed format sense + * data may be placed in *sensep in which case sensep[0]==0x70, prior to + * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72). + */ +int sg_ll_ata_pt(int sg_fd, const unsigned char * cdbp, int cdb_len, + int timeout_secs, void * dinp, void * doutp, int dlen, + unsigned char * sensep, int max_sense_len, + unsigned char * ata_return_dp, int max_ata_return_len, + int * residp, int verbose); + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Format unit not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Note that sg_ll_format_unit2() and + * sg_ll_format_unit_v2() are the same, both add the ffmt argument. */ +int sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose); +int sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose); +int sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI GET LBA STATUS(16) or GET LBA STATUS(32) command (SBC). + * Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> GET LBA STATUS(16 or 32) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure. + * sg_ll_get_lba_status() calls the 16 byte variant with rt=0 . */ +int sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp, + int alloc_len, bool noisy, int verbose); +int sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose); +int sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len, + uint32_t element_id, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose); + +/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0 + * when successful, SG_LIB_CAT_INVALID_OP if command not supported, + * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0 + * when successful, SG_LIB_CAT_INVALID_OP if command not supported, + * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope, + unsigned int rq_type, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ BLOCK LIMITS not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */ +int sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI READ BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * resp, int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, + int dl_format, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ LONG(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose); + +/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ LONG(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose); + +/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Read media serial number not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI REASSIGN BLOCKS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */ +int sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, + void * paramp, int param_len, bool noisy, + int verbose); + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Receive diagnostic results not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Same as sg_ll_receive_diag() but with added timeout_secs and residp + * arguments. Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was + * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report identifying information not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); +int sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len, + bool extended, bool noisy, int verbose); + +/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose); + +/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Referrals not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg, + void * resp, int mx_resp_len, bool noisy, + int verbose); + +/* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can + * take a long time, if so set long_duration flag in which case the timeout + * is set to 7200 seconds; if the value of long_duration is > 7200 then that + * value is taken as the timeout value in seconds. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Send diagnostic not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit, + bool devofl_bit, bool unitofl_bit, int long_duration, + void * paramp, int param_len, bool noisy, int verbose); + +/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was + * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set identifying information not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI UNMAP (SBC-3) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose); +/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field + * (sbc3r22). Otherwise same as sg_ll_unmap() . */ +int sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose); + +/* Invokes a SCSI VERIFY (10) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Verify(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info, + * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */ +int sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytechk, + unsigned int lba, int veri_len, void * data_out, + int data_out_len, unsigned int * infop, bool noisy, + int verbose); + +/* Invokes a SCSI VERIFY (16) command (SBC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Verify(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info, + * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */ +int sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytechk, + uint64_t llba, int veri_len, int group_num, + void * data_out, int data_out_len, uint64_t * infop, + bool noisy, int verbose); + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * paramp, int param_len, bool noisy, int verbose); + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Adds mode specific field (spc4r32) and timeout + * to command abort to override default of 60 seconds. If timeout_secs is + * 0 or less then the default timeout is used instead. */ +int +sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id, + uint32_t buffer_offset, void * paramp, + uint32_t param_len, int timeout_secs, bool noisy, + int verbose); + +/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> WRITE LONG(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + unsigned int lba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose); + +/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> WRITE LONG(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + uint64_t llba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose); + +/* Invokes a SPC-3 SCSI RECEIVE COPY RESULTS command. In SPC-4 this function + * supports all service action variants of the THIRD-PARTY COPY IN opcode. + * SG_LIB_CAT_INVALID_OP -> Receive copy results not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI EXTENDED COPY(LID1) command. For EXTENDED COPY(LID4) + * including POPULATE TOKEN and WRITE USING TOKEN use + * sg_ll_3party_copy_out(). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Extended copy not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose); + +/* Handles various service actions associated with opcode 0x83 which is + * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID4), + * POPULATE TOKEN and WRITE USING TOKEN commands. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> opcode 0x83 not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, + int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC). + * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_* + * positive values or -1 -> other errors. Note that CONDITION MET status + * is returned when immed=true and num_blocks can fit in device's cache, + * somewaht strangely, GOOD status (return 0) is returned if num_blocks + * cannot fit in device's cache. If do_seek10==true then does a SEEK(10) + * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10) + * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then + * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */ +int sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed, + uint64_t lba, uint32_t num_blocks, int group_num, + int timeout_secs, bool noisy, int verbose); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_cmds_mmc.h b/tools/sg_write_buffer/include/sg_cmds_mmc.h new file mode 100644 index 0000000..3988b1d --- /dev/null +++ b/tools/sg_write_buffer/include/sg_cmds_mmc.h @@ -0,0 +1,52 @@ +#ifndef SG_CMDS_MMC_H +#define SG_CMDS_MMC_H + +/* + * Copyright (c) 2008-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. + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Invokes a SCSI GET CONFIGURATION command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_get_config(int sg_fd, int rt, int starting, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba, + int max_num_desc, int type, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI SET CD SPEED command (MMC). + * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed, + int drv_write_speed, bool noisy, int verbose); + +/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len, + bool noisy, int verbose); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_io_linux.h b/tools/sg_write_buffer/include/sg_io_linux.h new file mode 100644 index 0000000..bd44bd6 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_io_linux.h @@ -0,0 +1,185 @@ +#ifndef SG_IO_LINUX_H +#define SG_IO_LINUX_H + +/* + * Copyright (c) 2004-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. + */ + +/* + * Version 1.05 [20171009] + */ + +/* + * This header file contains linux specific information related to the SCSI + * command pass through in the SCSI generic (sg) driver and the linux + * block layer. + */ + +#include "sg_lib.h" +#include "sg_linux_inc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* The following are 'host_status' codes */ +#ifndef DID_OK +#define DID_OK 0x00 +#endif +#ifndef DID_NO_CONNECT +#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */ +#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */ +#define DID_TIME_OUT 0x03 /* Timed out for some other reason */ +#define DID_BAD_TARGET 0x04 /* Bad target (id?) */ +#define DID_ABORT 0x05 /* Told to abort for some other reason */ +#define DID_PARITY 0x06 /* Parity error (on SCSI bus) */ +#define DID_ERROR 0x07 /* Internal error */ +#define DID_RESET 0x08 /* Reset by somebody */ +#define DID_BAD_INTR 0x09 /* Received an unexpected interrupt */ +#define DID_PASSTHROUGH 0x0a /* Force command past mid-level */ +#define DID_SOFT_ERROR 0x0b /* The low-level driver wants a retry */ +#endif +#ifndef DID_IMM_RETRY +#define DID_IMM_RETRY 0x0c /* Retry without decrementing retry count */ +#endif +#ifndef DID_REQUEUE +#define DID_REQUEUE 0x0d /* Requeue command (no immediate retry) also + * without decrementing the retry count */ +#endif +#ifndef DID_TRANSPORT_DISRUPTED +#define DID_TRANSPORT_DISRUPTED 0xe +#endif +#ifndef DID_TRANSPORT_FAILFAST +#define DID_TRANSPORT_FAILFAST 0xf +#endif +#ifndef DID_TARGET_FAILURE +#define DID_TARGET_FAILURE 0x10 +#endif +#ifndef DID_NEXUS_FAILURE +#define DID_NEXUS_FAILURE 0x11 +#endif + +/* These defines are to isolate applications from kernel define changes */ +#define SG_LIB_DID_OK DID_OK +#define SG_LIB_DID_NO_CONNECT DID_NO_CONNECT +#define SG_LIB_DID_BUS_BUSY DID_BUS_BUSY +#define SG_LIB_DID_TIME_OUT DID_TIME_OUT +#define SG_LIB_DID_BAD_TARGET DID_BAD_TARGET +#define SG_LIB_DID_ABORT DID_ABORT +#define SG_LIB_DID_PARITY DID_PARITY +#define SG_LIB_DID_ERROR DID_ERROR +#define SG_LIB_DID_RESET DID_RESET +#define SG_LIB_DID_BAD_INTR DID_BAD_INTR +#define SG_LIB_DID_PASSTHROUGH DID_PASSTHROUGH +#define SG_LIB_DID_SOFT_ERROR DID_SOFT_ERROR +#define SG_LIB_DID_IMM_RETRY DID_IMM_RETRY +#define SG_LIB_DID_REQUEUE DID_REQUEUE +#define SG_LIB_TRANSPORT_DISRUPTED DID_TRANSPORT_DISRUPTED +#define SG_LIB_DID_TRANSPORT_FAILFAST DID_TRANSPORT_FAILFAST +#define SG_LIB_DID_TARGET_FAILURE DID_TARGET_FAILURE +#define SG_LIB_DID_NEXUS_FAILURE DID_NEXUS_FAILURE + +/* The following are 'driver_status' codes */ +#ifndef DRIVER_OK +#define DRIVER_OK 0x00 +#endif +#ifndef DRIVER_BUSY +#define DRIVER_BUSY 0x01 +#define DRIVER_SOFT 0x02 +#define DRIVER_MEDIA 0x03 +#define DRIVER_ERROR 0x04 +#define DRIVER_INVALID 0x05 +#define DRIVER_TIMEOUT 0x06 +#define DRIVER_HARD 0x07 +#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */ + +/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept + * to stop compilation breakages. + * Following "suggests" are "or-ed" with one of previous 8 entries */ +#define SUGGEST_RETRY 0x10 +#define SUGGEST_ABORT 0x20 +#define SUGGEST_REMAP 0x30 +#define SUGGEST_DIE 0x40 +#define SUGGEST_SENSE 0x80 +#define SUGGEST_IS_OK 0xff +#endif + +#ifndef DRIVER_MASK +#define DRIVER_MASK 0x0f +#endif +#ifndef SUGGEST_MASK +#define SUGGEST_MASK 0xf0 +#endif + +/* These defines are to isolate applications from kernel define changes */ +#define SG_LIB_DRIVER_OK DRIVER_OK +#define SG_LIB_DRIVER_BUSY DRIVER_BUSY +#define SG_LIB_DRIVER_SOFT DRIVER_SOFT +#define SG_LIB_DRIVER_MEDIA DRIVER_MEDIA +#define SG_LIB_DRIVER_ERROR DRIVER_ERROR +#define SG_LIB_DRIVER_INVALID DRIVER_INVALID +#define SG_LIB_DRIVER_TIMEOUT DRIVER_TIMEOUT +#define SG_LIB_DRIVER_HARD DRIVER_HARD +#define SG_LIB_DRIVER_SENSE DRIVER_SENSE + + +/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept + * to stop compilation breakages. */ +#define SG_LIB_SUGGEST_RETRY SUGGEST_RETRY +#define SG_LIB_SUGGEST_ABORT SUGGEST_ABORT +#define SG_LIB_SUGGEST_REMAP SUGGEST_REMAP +#define SG_LIB_SUGGEST_DIE SUGGEST_DIE +#define SG_LIB_SUGGEST_SENSE SUGGEST_SENSE +#define SG_LIB_SUGGEST_IS_OK SUGGEST_IS_OK +#define SG_LIB_DRIVER_MASK DRIVER_MASK +#define SG_LIB_SUGGEST_MASK SUGGEST_MASK + +void sg_print_masked_status(int masked_status); +void sg_print_host_status(int host_status); +void sg_print_driver_status(int driver_status); + +/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings + else it prints errors/warnings (prefixed by 'leadin') to + 'sg_warnings_fd' and returns 0. raw_sinfo indicates whether the + raw sense buffer (in ASCII hex) should be printed. */ +int sg_chk_n_print(const char * leadin, int masked_status, int host_status, + int driver_status, const unsigned char * sense_buffer, + int sb_len, bool raw_sinfo); + +/* The following function declaration is for the sg version 3 driver. */ +struct sg_io_hdr; +/* sg_chk_n_print3() returns 1 quietly if there are no errors/warnings; + else it prints errors/warnings (prefixed by 'leadin') to + 'sg_warnings_fd' and returns 0. */ +int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp, + bool raw_sinfo); + +/* Calls sg_scsi_normalize_sense() after obtaining the sense buffer and + its length from the struct sg_io_hdr pointer. If these cannot be + obtained, false is returned. */ +bool sg_normalize_sense(const struct sg_io_hdr * hp, + struct sg_scsi_sense_hdr * sshp); + +int sg_err_category(int masked_status, int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len); + +int sg_err_category_new(int scsi_status, int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len); + +/* The following function declaration is for the sg version 3 driver. */ +int sg_err_category3(struct sg_io_hdr * hp); + + +/* Note about SCSI status codes found in older versions of Linux. + Linux has traditionally used a 1 bit right shifted and masked + version of SCSI standard status codes. Now CHECK_CONDITION + and friends (in <scsi/scsi.h>) are deprecated. */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_lib.h b/tools/sg_write_buffer/include/sg_lib.h new file mode 100644 index 0000000..bc93d49 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_lib.h @@ -0,0 +1,602 @@ +#ifndef SG_LIB_H +#define SG_LIB_H + +/* + * Copyright (c) 2004-2018 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. + */ + +/* + * + * On 5th October 2004 a FreeBSD license was added to this file. + * The intention is to keep this file and the related sg_lib.c file + * as open source and encourage their unencumbered use. + * + * Current version number is in the sg_lib.c file and can be accessed + * with the sg_lib_version() function. + */ + + +/* + * This header file contains defines and function declarations that may + * be useful to applications that communicate with devices that use a + * SCSI command set. These command sets have names like SPC-4, SBC-3, + * SSC-3, SES-2 and draft standards defining them can be found at + * http://www.t10.org . Virtually all devices in the Linux SCSI subsystem + * utilize SCSI command sets. Many devices in other Linux device subsystems + * utilize SCSI command sets either natively or via emulation (e.g. a + * parallel ATA disk in a USB enclosure). + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* SCSI Peripheral Device Types (PDT) [5 bit field] */ +#define PDT_DISK 0x0 /* direct access block device (disk) */ +#define PDT_TAPE 0x1 /* sequential access device (magnetic tape) */ +#define PDT_PRINTER 0x2 /* printer device (see SSC-1) */ +#define PDT_PROCESSOR 0x3 /* processor device (e.g. SAFTE device) */ +#define PDT_WO 0x4 /* write once device (some optical disks) */ +#define PDT_MMC 0x5 /* CD/DVD/BD (multi-media) */ +#define PDT_SCANNER 0x6 /* obsolete */ +#define PDT_OPTICAL 0x7 /* optical memory device (some optical disks) */ +#define PDT_MCHANGER 0x8 /* media changer device (e.g. tape robot) */ +#define PDT_COMMS 0x9 /* communications device (obsolete) */ +#define PDT_SAC 0xc /* storage array controller device */ +#define PDT_SES 0xd /* SCSI Enclosure Services (SES) device */ +#define PDT_RBC 0xe /* Reduced Block Commands (simplified PDT_DISK) */ +#define PDT_OCRW 0xf /* optical card read/write device */ +#define PDT_BCC 0x10 /* bridge controller commands */ +#define PDT_OSD 0x11 /* Object Storage Device (OSD) */ +#define PDT_ADC 0x12 /* Automation/drive commands (ADC) */ +#define PDT_SMD 0x13 /* Security Manager Device (SMD) */ +#define PDT_ZBC 0x14 /* Zoned Block Commands (ZBC) */ +#define PDT_WLUN 0x1e /* Well known logical unit (WLUN) */ +#define PDT_UNKNOWN 0x1f /* Unknown or no device type */ + +#ifndef SAM_STAT_GOOD +/* The SCSI status codes as found in SAM-4 at www.t10.org */ +#define SAM_STAT_GOOD 0x0 +#define SAM_STAT_CHECK_CONDITION 0x2 +#define SAM_STAT_CONDITION_MET 0x4 +#define SAM_STAT_BUSY 0x8 +#define SAM_STAT_INTERMEDIATE 0x10 /* obsolete in SAM-4 */ +#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14 /* obsolete in SAM-4 */ +#define SAM_STAT_RESERVATION_CONFLICT 0x18 +#define SAM_STAT_COMMAND_TERMINATED 0x22 /* obsolete in SAM-3 */ +#define SAM_STAT_TASK_SET_FULL 0x28 +#define SAM_STAT_ACA_ACTIVE 0x30 +#define SAM_STAT_TASK_ABORTED 0x40 +#endif + +/* The SCSI sense key codes as found in SPC-4 at www.t10.org */ +#define SPC_SK_NO_SENSE 0x0 +#define SPC_SK_RECOVERED_ERROR 0x1 +#define SPC_SK_NOT_READY 0x2 +#define SPC_SK_MEDIUM_ERROR 0x3 +#define SPC_SK_HARDWARE_ERROR 0x4 +#define SPC_SK_ILLEGAL_REQUEST 0x5 +#define SPC_SK_UNIT_ATTENTION 0x6 +#define SPC_SK_DATA_PROTECT 0x7 +#define SPC_SK_BLANK_CHECK 0x8 +#define SPC_SK_VENDOR_SPECIFIC 0x9 +#define SPC_SK_COPY_ABORTED 0xa +#define SPC_SK_ABORTED_COMMAND 0xb +#define SPC_SK_RESERVED 0xc +#define SPC_SK_VOLUME_OVERFLOW 0xd +#define SPC_SK_MISCOMPARE 0xe +#define SPC_SK_COMPLETED 0xf + +/* Transport protocol identifiers or just Protocol identifiers */ +#define TPROTO_FCP 0 +#define TPROTO_SPI 1 +#define TPROTO_SSA 2 +#define TPROTO_1394 3 +#define TPROTO_SRP 4 /* SCSI over RDMA */ +#define TPROTO_ISCSI 5 +#define TPROTO_SAS 6 +#define TPROTO_ADT 7 +#define TPROTO_ATA 8 +#define TPROTO_UAS 9 /* USB attached SCSI */ +#define TPROTO_SOP 0xa /* SCSI over PCIe */ +#define TPROTO_PCIE 0xb /* includes NVMe */ +#define TPROTO_NONE 0xf + +/* SCSI Feature Sets (sfs) */ +#define SCSI_FS_SPC_DISCOVERY_2016 0x1 +#define SCSI_FS_SBC_BASE_2010 0x102 +#define SCSI_FS_SBC_BASE_2016 0x101 +#define SCSI_FS_SBC_BASIC_PROV_2016 0x103 +#define SCSI_FS_SBC_DRIVE_MAINT_2016 0x104 + +/* Often SCSI responses use the highest integer that can fit in a field + * to indicate "unbounded" or limit does not apply. Sometimes represented + * in output as "-1" for brevity */ +#define SG_LIB_UNBOUNDED_16BIT 0xffff +#define SG_LIB_UNBOUNDED_32BIT 0xffffffffU +#define SG_LIB_UNBOUNDED_64BIT 0xffffffffffffffffULL + +#if (__STDC_VERSION__ >= 199901L) /* C99 or later */ + typedef uintptr_t sg_uintptr_t; +#else + typedef unsigned long sg_uintptr_t; +#endif + + +/* The format of the version string is like this: "2.26 20170906" */ +const char * sg_lib_version(); + +/* Returns length of SCSI command given the opcode (first byte). + * Yields the wrong answer for variable length commands (opcode=0x7f) + * and potentially some vendor specific commands. */ +int sg_get_command_size(unsigned char cdb_byte0); + +/* Command name given pointer to the cdb. Certain command names + * depend on peripheral type (give 0 or -1 if unknown). Places command + * name into buff and will write no more than buff_len bytes. */ +void sg_get_command_name(const unsigned char * cdbp, int peri_type, + int buff_len, char * buff); + +/* Command name given only the first byte (byte 0) of a cdb and + * peripheral type (give 0 or -1 if unknown). */ +void sg_get_opcode_name(unsigned char cdb_byte0, int peri_type, int buff_len, + char * buff); + +/* Command name given opcode (byte 0), service action and peripheral type. + * If no service action give 0, if unknown peripheral type give 0 or -1 . */ +void sg_get_opcode_sa_name(unsigned char cdb_byte0, int service_action, + int peri_type, int buff_len, char * buff); + +/* Fetch scsi status string. */ +void sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff); + +/* This is a slightly stretched SCSI sense "descriptor" format header. + * The addition is to allow the 0x70 and 0x71 response codes. The idea + * is to place the salient data of both "fixed" and "descriptor" sense + * format into one structure to ease application processing. + * The original sense buffer should be kept around for those cases + * in which more information is required (e.g. the LBA of a MEDIUM ERROR). */ +struct sg_scsi_sense_hdr { + unsigned char response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */ + unsigned char sense_key; + unsigned char asc; + unsigned char ascq; + unsigned char byte4; + unsigned char byte5; + unsigned char byte6; + unsigned char additional_length; +}; + +/* Maps the salient data from a sense buffer which is in either fixed or + * descriptor format into a structure mimicking a descriptor format + * header (i.e. the first 8 bytes of sense descriptor format). + * If zero response code returns false. Otherwise returns true and if 'sshp' + * is non-NULL then zero all fields and then set the appropriate fields in + * that structure. sshp::additional_length is always 0 for response + * codes 0x70 and 0x71 (fixed format). */ +bool sg_scsi_normalize_sense(const unsigned char * sensep, int sense_len, + struct sg_scsi_sense_hdr * sshp); + +/* Attempt to find the first SCSI sense data descriptor that matches the + * given 'desc_type'. If found return pointer to start of sense data + * descriptor; otherwise (including fixed format sense data) returns NULL. */ +const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep, + int sense_len, int desc_type); + +/* Get sense key from sense buffer. If successful returns a sense key value + * between 0 and 15. If sense buffer cannot be decode, returns -1 . */ +int sg_get_sense_key(const unsigned char * sensep, int sense_len); + +/* Yield string associated with sense_key value. Returns 'buff'. */ +char * sg_get_sense_key_str(int sense_key, int buff_len, char * buff); + +/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */ +char * sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff); + +/* Returns true if valid bit set, false if valid bit clear. Irrespective the + * information field is written out via 'info_outp' (except when it is + * NULL). Handles both fixed and descriptor sense formats. */ +bool sg_get_sense_info_fld(const unsigned char * sensep, int sb_len, + uint64_t * info_outp); + +/* Returns true if fixed format or command specific information descriptor + * is found in the descriptor sense; else false. If available the command + * specific information field (4 byte integer in fixed format, 8 byte + * integer in descriptor format) is written out via 'cmd_spec_outp'. + * Handles both fixed and descriptor sense formats. */ +bool sg_get_sense_cmd_spec_fld(const unsigned char * sensep, int sb_len, + uint64_t * cmd_spec_outp); + +/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set. + * In descriptor format if the stream commands descriptor not found + * then returns false. Writes true or false corresponding to these bits to + * the last three arguments if they are non-NULL. */ +bool sg_get_sense_filemark_eom_ili(const unsigned char * sensep, int sb_len, + bool * filemark_p, bool * eom_p, + bool * ili_p); + +/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also + * returns true if progress indication sense data descriptor found. Places + * progress field from sense data where progress_outp points. If progress + * field is not available returns false. Handles both fixed and descriptor + * sense formats. N.B. App should multiply by 100 and divide by 65536 + * to get percentage completion from given value. */ +bool sg_get_sense_progress_fld(const unsigned char * sensep, int sb_len, + int * progress_outp); + +/* Closely related to sg_print_sense(). Puts decoded sense data in 'buff'. + * Usually multiline with multiple '\n' including one trailing. If + * 'raw_sinfo' set appends sense buffer in hex. 'leadin' is string prepended + * to each line written to 'buff', NULL treated as "". Returns the number of + * bytes written to 'buff' excluding the trailing '\0'. + * N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the first + * line output. Also this function returned type void. */ +int sg_get_sense_str(const char * leadin, const unsigned char * sense_buffer, + int sb_len, bool raw_sinfo, int buff_len, char * buff); + +/* Decode descriptor format sense descriptors (assumes sense buffer is + * in descriptor format). 'leadin' is string prepended to each line written + * to 'b', NULL treated as "". Returns the number of bytes written to 'b' + * excluding the trailing '\0'. */ +int sg_get_sense_descriptors_str(const char * leadin, + const unsigned char * sense_buffer, + int sb_len, int blen, char * b); + +/* Decodes a designation descriptor (e.g. as found in the Device + * Identification VPD page (0x83)) into string 'b' whose maximum length is + * blen. 'leadin' is string prepended to each line written to 'b', NULL + * treated as "". Returns the number of bytes written to 'b' excluding the + * trailing '\0'. */ +int sg_get_designation_descriptor_str(const char * leadin, + const unsigned char * ddp, int dd_len, + bool print_assoc, bool do_long, + int blen, char * b); + +/* Yield string associated with peripheral device type (pdt). Returns + * 'buff'. If 'pdt' out of range yields "bad pdt" string. */ +char * sg_get_pdt_str(int pdt, int buff_len, char * buff); + +/* Some lesser used PDTs share a lot in common with a more used PDT. + * Examples are PDT_ADC decaying to PDT_TAPE and PDT_ZBC to PDT_DISK. + * If such a lesser used 'pdt' is given to this function, then it will + * return the more used PDT (i.e. "decays to"); otherwise 'pdt' is returned. + * Valid for 'pdt' 0 to 31, for other values returns 0. */ +int sg_lib_pdt_decay(int pdt); + +/* Yield string associated with transport protocol identifier (tpi). Returns + * 'buff'. If 'tpi' out of range yields "bad tpi" string. */ +char * sg_get_trans_proto_str(int tpi, int buff_len, char * buff); + +/* Decode TransportID pointed to by 'bp' of length 'bplen'. Place decoded + * string output in 'buff' which is also the return value. Each new line + * is prefixed by 'leadin'. If leadin NULL treat as "". */ +char * sg_decode_transportid_str(const char * leadin, unsigned char * bp, + int bplen, bool only_one, int buff_len, + char * buff); + +/* Returns a designator's type string given 'val' (0 to 15 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_type_str(int val); + +/* Returns a designator's code_set string given 'val' (0 to 15 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_code_set_str(int val); + +/* Returns a designator's association string given 'val' (0 to 3 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_assoc_str(int val); + +/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31) + * returns pointer to string (same as 'buff') associated with 'sfs_code'. + * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match + * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL + * then where it points is set to true if a match is found else it is set to + * false. If 'buff' is not NULL then in the case of a match a descriptive + * string is written to 'buff' while if there is not a not then a string + * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC + * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL. + * Example: + * char b[64]; + * ... + * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0)); + */ +const char * sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, + char * buff, bool * foundp, int verbose); + +/* This is a heuristic that takes into account the command bytes and length + * to decide whether the presented unstructured sequence of bytes could be + * a SCSI command. If so it returns true otherwise false. Vendor specific + * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed + * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The + * only SCSI commands considered above 16 bytes of length are the Variable + * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e). + * Both have an inbuilt length field which can be cross checked with clen. + * No NVMe commands (64 bytes long plus some extra added by some OSes) have + * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS + * structures that are sent across the wire. The 'FIS register' structure is + * used to move a command from a SATA host to device, but the ATA 'command' + * is not the first byte. So it is harder to say what will happen if a + * FIS structure is presented as a SCSI command, hopfully there is a low + * probability this function will yield true in that case. */ +bool sg_is_scsi_cdb(const uint8_t * cdbp, int clen); + +/* Yield string associated with NVMe command status value in sct_sc. It + * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25 + * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC). + * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found + * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated. + * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/ +char * sg_get_nvme_cmd_status_str(uint16_t sct_sc, int buff_len, char * buff); + +/* Attempts to map NVMe status value ((SCT << 8) | SC) n sct_sc to a SCSI + * status, sense_key, asc and ascq tuple. If successful returns true and + * writes to non-NULL pointer arguments; otherwise returns false. */ +bool sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p, + uint8_t * asc_p, uint8_t * ascq_p); + +extern FILE * sg_warnings_strm; + +void sg_set_warnings_strm(FILE * warnings_strm); + +/* The following "print" functions send ACSII to 'sg_warnings_strm' file + * descriptor (default value is stderr). 'leadin' is string prepended to + * each line printed out, NULL treated as "". */ +void sg_print_command(const unsigned char * command); +void sg_print_scsi_status(int scsi_status); + +/* 'leadin' is string prepended to each line printed out, NULL treated as + * "". N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the + * first line printed. */ +void sg_print_sense(const char * leadin, const unsigned char * sense_buffer, + int sb_len, bool raw_info); + +/* Following examines exit_status and outputs a clear error message to + * warnings_strm (usually stderr) if one is known and returns true. + * Otherwise it doesn't print anything and returns false. Note that if + * exit_status==0 then returns true but prints nothing and if + * exit_status<0 ("some error occurred") false is returned. If leadin is + * non-NULL is will be printed before error message. */ +bool sg_if_can2stderr(const char * leadin, int exit_status); + +/* Utilities can use these exit status values for syntax errors and + * file (device node) problems (e.g. not found or permissions). */ +#define SG_LIB_SYNTAX_ERROR 1 /* command line syntax problem */ +#define SG_LIB_FILE_ERROR 15 /* device or other file problem */ + +/* The sg_err_category_sense() function returns one of the following. + * These may be used as exit status values (from a process). Notice that + * some of the lower values correspond to SCSI sense key values. */ +#define SG_LIB_CAT_CLEAN 0 /* No errors or other information */ +/* Value 1 left unused for utilities to use SG_LIB_SYNTAX_ERROR */ +#define SG_LIB_CAT_NOT_READY 2 /* sense key, unit stopped? */ + /* [sk,asc,ascq: 0x2,*,*] */ +#define SG_LIB_CAT_MEDIUM_HARD 3 /* medium or hardware error, blank check */ + /* [sk,asc,ascq: 0x3/0x4/0x8,*,*] */ +#define SG_LIB_CAT_ILLEGAL_REQ 5 /* Illegal request (other than invalid */ + /* opcode): [sk,asc,ascq: 0x5,*,*] */ +#define SG_LIB_CAT_UNIT_ATTENTION 6 /* sense key, device state changed */ + /* [sk,asc,ascq: 0x6,*,*] */ + /* was SG_LIB_CAT_MEDIA_CHANGED earlier [sk,asc,ascq: 0x6,0x28,*] */ +#define SG_LIB_CAT_DATA_PROTECT 7 /* sense key, media write protected? */ + /* [sk,asc,ascq: 0x7,*,*] */ +#define SG_LIB_CAT_INVALID_OP 9 /* (Illegal request,) Invalid opcode: */ + /* [sk,asc,ascq: 0x5,0x20,0x0] */ +#define SG_LIB_CAT_COPY_ABORTED 10 /* sense key, some data transferred */ + /* [sk,asc,ascq: 0xa,*,*] */ +#define SG_LIB_CAT_ABORTED_COMMAND 11 /* interpreted from sense buffer */ + /* [sk,asc,ascq: 0xb,! 0x10,*] */ +#define SG_LIB_CAT_MISCOMPARE 14 /* sense key, probably verify */ + /* [sk,asc,ascq: 0xe,*,*] */ +#define SG_LIB_CAT_NO_SENSE 20 /* sense data with key of "no sense" */ + /* [sk,asc,ascq: 0x0,*,*] */ +#define SG_LIB_CAT_RECOVERED 21 /* Successful command after recovered err */ + /* [sk,asc,ascq: 0x1,*,*] */ +#define SG_LIB_CAT_RES_CONFLICT SAM_STAT_RESERVATION_CONFLICT + /* 24: this is a SCSI status, not sense. */ + /* It indicates reservation by another */ + /* machine blocks this command */ +#define SG_LIB_CAT_CONDITION_MET 25 /* SCSI status, not sense key. */ + /* Only from PRE-FETCH (SBC-4) */ +#define SG_LIB_CAT_BUSY 26 /* SCSI status, not sense. Invites retry */ +#define SG_LIB_CAT_TS_FULL 27 /* SCSI status, not sense. Wait then retry */ +#define SG_LIB_CAT_ACA_ACTIVE 28 /* SCSI status; ACA seldom used */ +#define SG_LIB_CAT_TASK_ABORTED 29 /* SCSI status, this command aborted by? */ +#define SG_LIB_CAT_PROTECTION 40 /* subset of aborted command (for PI, DIF) */ + /* [sk,asc,ascq: 0xb,0x10,*] */ +#define SG_LIB_NVME_STATUS 48 /* NVMe Status Field (SF) other than 0 */ +#define SG_LIB_WILD_RESID 49 /* Residual value for data-in transfer of a */ + /* SCSI command is nonsensical */ +#define SG_LIB_OS_BASE_ERR 50 /* in Linux: values found in: */ + /* include/uapi/asm-generic/errno-base.h */ + /* Example: ENOMEM reported as 62 (=50+12) */ +#define SG_LIB_CAT_MALFORMED 97 /* Response to SCSI command malformed */ +#define SG_LIB_CAT_SENSE 98 /* Something else is in the sense buffer */ +#define SG_LIB_CAT_OTHER 99 /* Some other error/warning has occurred */ + /* (e.g. a transport or driver error) */ + +/* Returns a SG_LIB_CAT_* value. If cannot decode sense_buffer or a less + * common sense key then return SG_LIB_CAT_SENSE .*/ +int sg_err_category_sense(const unsigned char * sense_buffer, int sb_len); + +/* Here are some additional sense data categories that are not returned + * by sg_err_category_sense() but are returned by some related functions. */ +#define SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO 17 /* Illegal request (other than */ + /* invalid opcode) plus 'info' field: */ + /* [sk,asc,ascq: 0x5,*,*] */ +#define SG_LIB_CAT_MEDIUM_HARD_WITH_INFO 18 /* medium or hardware error */ + /* sense key plus 'info' field: */ + /* [sk,asc,ascq: 0x3/0x4,*,*] */ +#define SG_LIB_CAT_PROTECTION_WITH_INFO 41 /* aborted command sense key, */ + /* protection plus 'info' field: */ + /* [sk,asc,ascq: 0xb,0x10,*] */ +#define SG_LIB_CAT_TIMEOUT 33 + +/* Yield string associated with sense category. Returns 'buff' (or pointer + * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then + * yield "Sense category: <sense_cat>" string. */ +const char * sg_get_category_sense_str(int sense_cat, int buff_len, + char * buff, int verbose); + + +/* Iterates to next designation descriptor in the device identification + * VPD page. The 'initial_desig_desc' should point to start of first + * descriptor with 'page_len' being the number of valid bytes in that + * and following descriptors. To start, 'off' should point to a negative + * value, thereafter it should point to the value yielded by the previous + * call. If 0 returned then 'initial_desig_desc + *off' should be a valid + * descriptor; returns -1 if normal end condition and -2 for an abnormal + * termination. Matches association, designator_type and/or code_set when + * any of those values are greater than or equal to zero. */ +int sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len, + int * off, int m_assoc, int m_desig_type, + int m_code_set); + + +/* <<< General purpose (i.e. not SCSI specific) utility functions >>> */ + +/* Always returns valid string even if errnum is wild (or library problem). + * If errnum is negative, flip its sign. */ +char * safe_strerror(int errnum); + + +/* Print (to stdout) 'str' of bytes in hex, 16 bytes per line optionally + * followed at the right hand side of the line with an ASCII interpretation. + * Each line is prefixed with an address, starting at 0 for str[0]..str[15]. + * All output numbers are in hex. 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 16 ASCII-hex bytes + * = 0 in addition, the bytes are listed in ASCII to the right + * < 0 only the ASCII-hex bytes are listed (i.e. without address) +*/ +void dStrHex(const char * str, int len, int no_ascii); + +/* Print (to sg_warnings_strm (stderr)) 'str' of bytes in hex, 16 bytes per + * line optionally followed at right by its ASCII interpretation. Same + * logic as dStrHex() with different output stream (i.e. stderr). */ +void dStrHexErr(const char * str, int len, int no_ascii); + +/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space + * separated) to 'b' not to exceed 'b_len' characters. Each line + * starts with 'leadin' (NULL for no leadin) and there are 16 bytes + * per line with an extra space between the 8th and 9th bytes. 'format' + * is 0 for repeat in printable ASCII ('.' for non printable chars) to + * right of each line; 1 don't (so just output ASCII hex). Returns + * number of bytes written to 'b' excluding the trailing '\0'. */ +int dStrHexStr(const char * str, int len, const char * leadin, int format, + int cb_len, char * cbp); + +/* The following 3 functions are equivalent to dStrHex(), dStrHexErr() and + * dStrHexStr() respectively. The difference is the type of the first of + * argument: uint8_t instead of char. The name of the argument is changed + * to b_str to stress it is a pointer to the start of a binary string. */ +void hex2stdout(const uint8_t * b_str, int len, int no_ascii); +void hex2stderr(const uint8_t * b_str, int len, int no_ascii); +int hex2str(const uint8_t * b_str, int len, const char * leadin, int format, + int cb_len, char * cbp); + +/* Returns true when executed on big endian machine; else returns false. + * Useful for displaying ATA identify words (which need swapping on a + * big endian machine). */ +bool sg_is_big_endian(); + +/* Returns true if byte sequence starting at bp with a length of b_len is + * all zeros (for sg_all_zeros()) or all 0xff_s (for sg_all_ffs()); + * otherwise returns false. If bp is NULL ir b_len <= 0 returns false. */ +bool sg_all_zeros(const uint8_t * bp, int b_len); +bool sg_all_ffs(const uint8_t * bp, int b_len); + +/* Extract character sequence from ATA words as in the model string + * in a IDENTIFY DEVICE response. Returns number of characters + * written to 'ochars' before 0 character is found or 'num' words + * are processed. */ +int sg_ata_get_chars(const uint16_t * word_arr, int start_word, + int num_words, bool is_big_endian, char * ochars); + +/* Print (to stdout) 16 bit 'words' in hex, 8 words per line optionally + * followed at the right hand side of the line with an ASCII interpretation + * (pairs of ASCII characters in big endian order (upper first)). + * Each line is prefixed with an address, starting at 0. + * All output numbers are in hex. 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 8 ASCII-hex words + * = 0 in addition, the words are listed in ASCII pairs to the right + * = -1 only the ASCII-hex words are listed (i.e. without address) + * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin" + * < -2 same as -1 + * If 'swapb' is true then bytes in each word swapped. Needs to be set + * for ATA IDENTIFY DEVICE response on big-endian machines. +*/ +void dWordHex(const uint16_t * words, int num, int no_ascii, bool swapb); + +/* If the number in 'buf' can not be decoded or the multiplier is unknown + * then -1 is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H') + * suffix. Otherwise a decimal multiplier suffix may be given. Recognised + * multipliers: c C *1; w W *2; b B *512; k K KiB *1,024; + * KB *1,000; m M MiB *1,048,576; MB *1,000,000; g G GiB *1,073,741,824; + * GB *1,000,000,000 and <n>x<m> which multiplies <n> by <m> . Ignore leading + * spaces and tabs; accept comma, hyphen, space, tab and hash as terminator. + */ +int sg_get_num(const char * buf); + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int sg_get_num_nomult(const char * buf); + +/* If the number in 'buf' can not be decoded or the multiplier is unknown + * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H') + * suffix. Otherwise a decimal multiplier suffix may be given. In addition + * to supporting the multipliers of sg_get_num(), this function supports: + * t T TiB *(2**40); TB *(10**12); p P PiB *(2**50); PB *(10**15) . + * Ignore leading spaces and tabs; accept comma, hyphen, space, tab and hash + * as terminator. */ +int64_t sg_get_llnum(const char * buf); + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int64_t sg_get_llnum_nomult(const char * buf); + +/* Returns pointer to heap (or NULL) that is aligned to a align_to byte + * boundary. Sends back *buff_to_free pointer in third argument that may be + * different from the return value. If it is different then the *buff_to_free + * pointer should be freed (rather than the returned value) when the heap is + * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all + * returned heap to zeros. If num_bytes is 0 then set to page size. */ +uint8_t * sg_memalign(uint32_t num_bytes, uint32_t align_to, + uint8_t ** buff_to_free, bool vb); + +/* Returns OS page size in bytes. If uncertain returns 4096. */ +uint32_t sg_get_page_size(void); + +/* If os_err_num is within bounds then the returned value is 'os_err_num + + * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0 + * is returned. */ +int sg_convert_errno(int os_err_num); + + +/* <<< Architectural support functions [is there a better place?] >>> */ + +/* Non Unix OSes distinguish between text and binary files. + * Set text mode on fd. Does nothing in Unix. Returns negative number on + * failure. */ +int sg_set_text_mode(int fd); + +/* Set binary mode on fd. Does nothing in Unix. Returns negative number on + * failure. */ +int sg_set_binary_mode(int fd); + +#ifdef __cplusplus +} +#endif + +#endif /* SG_LIB_H */ diff --git a/tools/sg_write_buffer/include/sg_lib_data.h b/tools/sg_write_buffer/include/sg_lib_data.h new file mode 100644 index 0000000..09cd53c --- /dev/null +++ b/tools/sg_write_buffer/include/sg_lib_data.h @@ -0,0 +1,121 @@ +#ifndef SG_LIB_DATA_H +#define SG_LIB_DATA_H + +/* + * Copyright (c) 2007-2018 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. + */ + +/* + * This header file contains some structure declarations and array name + * declarations which are defined in the sg_lib_data.c . + * Typically this header does not need to be exposed to users of the + * sg_lib interface declared in sg_libs.h . + */ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Operation codes with associated service actions that change or qualify + * the command name */ +#define SG_EXTENDED_COPY 0x83 /* since spc4r34 became next entry */ +#define SG_3PARTY_COPY_OUT 0x83 /* new in spc4r34: Third party copy out */ +#define SG_RECEIVE_COPY 0x84 /* since spc4r34 became next entry */ +#define SG_3PARTY_COPY_IN 0x84 /* new in spc4r34: Third party copy in */ +#define SG_MAINTENANCE_IN 0xa3 +#define SG_MAINTENANCE_OUT 0xa4 +#define SG_PERSISTENT_RESERVE_IN 0x5e +#define SG_PERSISTENT_RESERVE_OUT 0x5f +#define SG_READ_ATTRIBUTE 0x8c +#define SG_READ_BUFFER 0x3c /* now READ BUFFER(10) */ +#define SG_READ_BUFFER_16 0x9b +#define SG_READ_POSITION 0x34 /* SSC command with service actions */ +#define SG_SANITIZE 0x48 +#define SG_SERVICE_ACTION_BIDI 0x9d +#define SG_SERVICE_ACTION_IN_12 0xab +#define SG_SERVICE_ACTION_IN_16 0x9e +#define SG_SERVICE_ACTION_OUT_12 0xa9 +#define SG_SERVICE_ACTION_OUT_16 0x9f +#define SG_VARIABLE_LENGTH_CMD 0x7f +#define SG_WRITE_BUFFER 0x3b +#define SG_ZONING_OUT 0x94 +#define SG_ZONING_IN 0x95 + + + +struct sg_lib_simple_value_name_t { + int value; + const char * name; +}; + +struct sg_lib_value_name_t { + int value; + int peri_dev_type; /* 0 -> SPC and/or PDT_DISK, >0 -> PDT */ + const char * name; +}; + +struct sg_lib_asc_ascq_t { + unsigned char asc; /* additional sense code */ + unsigned char ascq; /* additional sense code qualifier */ + const char * text; +}; + +struct sg_lib_asc_ascq_range_t { + unsigned char asc; /* additional sense code (ASC) */ + unsigned char ascq_min; /* ASCQ minimum in range */ + unsigned char ascq_max; /* ASCQ maximum in range */ + const char * text; +}; + +/* First use: SCSI status, sense_key, asc, ascq tuple */ +struct sg_lib_4tuple_u8 { + uint8_t t1; + uint8_t t2; + uint8_t t3; + uint8_t t4; +}; + + +extern const char * sg_lib_version_str; + +extern struct sg_lib_value_name_t sg_lib_normal_opcodes[]; +extern struct sg_lib_value_name_t sg_lib_read_buff_arr[]; +extern struct sg_lib_value_name_t sg_lib_write_buff_arr[]; +extern struct sg_lib_value_name_t sg_lib_maint_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_maint_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_pr_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_pr_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_in12_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_out12_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_in16_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_out16_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_bidi_arr[]; +extern struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_variable_length_arr[]; +extern struct sg_lib_value_name_t sg_lib_zoning_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_zoning_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_read_attr_arr[]; +extern struct sg_lib_value_name_t sg_lib_read_pos_arr[]; +extern struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[]; +extern struct sg_lib_asc_ascq_t sg_lib_asc_ascq[]; +extern struct sg_lib_value_name_t sg_lib_scsi_feature_sets[]; +extern const char * sg_lib_sense_key_desc[]; +extern const char * sg_lib_pdt_strs[]; +extern const char * sg_lib_transport_proto_strs[]; +extern int sg_lib_pdt_decay_arr[]; + +extern struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[]; +extern struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_linux_inc.h b/tools/sg_write_buffer/include/sg_linux_inc.h new file mode 100644 index 0000000..b587c6c --- /dev/null +++ b/tools/sg_write_buffer/include/sg_linux_inc.h @@ -0,0 +1,56 @@ +#ifndef SG_LINUX_INC_H +#define SG_LINUX_INC_H + +#ifdef SG_KERNEL_INCLUDES + #define __user + typedef unsigned char u8; + #include "/usr/src/linux/include/scsi/sg.h" + #include "/usr/src/linux/include/scsi/scsi.h" +#else + #ifdef SG_TRICK_GNU_INCLUDES + #include <linux/../scsi/sg.h> + #include <linux/../scsi/scsi.h> + #else + #include <scsi/sg.h> + #include <scsi/scsi.h> + #endif +#endif + +#ifdef BLKGETSIZE64 + #ifndef u64 + #include <stdint.h> /* C99 header for exact integer types */ + typedef uint64_t u64; /* problems with BLKGETSIZE64 ioctl in lk 2.4 */ + #endif +#endif + +/* + Getting the correct include files for the sg interface can be an ordeal. + In a perfect world, one would just write: + #include <scsi/sg.h> + #include <scsi/scsi.h> + This would include the files found in the /usr/include/scsi directory. + Those files are maintained with the GNU library which may or may not + agree with the kernel and version of sg driver that is running. Any + many cases this will not matter. However in some it might, for example + glibc 2.1's include files match the sg driver found in the lk 2.2 + series. Hence if glibc 2.1 is used with lk 2.4 then the additional + sg v3 interface will not be visible. + If this is a problem then defining SG_KERNEL_INCLUDES will access the + kernel supplied header files (assuming they are in the normal place). + The GNU library maintainers and various kernel people don't like + this approach (but it does work). + The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and + was used) prior to glibc 2.2 . Prior to that version /usr/include/linux + was a symbolic link to /usr/src/linux/include/linux . + + There are other approaches if this include "mixup" causes pain. These + would involve include files being copied or symbolic links being + introduced. + + Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES + nor SG_TRICK_GNU_INCLUDES is defined. + + dpg 20010415, 20030522 +*/ + +#endif diff --git a/tools/sg_write_buffer/include/sg_pr2serr.h b/tools/sg_write_buffer/include/sg_pr2serr.h new file mode 100644 index 0000000..4419087 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pr2serr.h @@ -0,0 +1,30 @@ +#ifndef SG_PR2SERR_H +#define SG_PR2SERR_H + +/* + * Copyright (c) 2004-2018 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. + */ + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(__GNUC__) || defined(__clang__) +int pr2serr(const char * fmt, ...) + __attribute__ ((format (printf, 1, 2))); +#else +int pr2serr(const char * fmt, ...); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_pt.h b/tools/sg_write_buffer/include/sg_pt.h new file mode 100644 index 0000000..b01215c --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pt.h @@ -0,0 +1,215 @@ +#ifndef SG_PT_H +#define SG_PT_H + +/* + * Copyright (c) 2005-2018 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. + */ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* This declaration hides the fact that each implementation has its own + * structure "derived" (using a C++ term) from this one. It compiles + * because 'struct sg_pt_base' is only referenced (by pointer: 'objp') + * in this interface. An instance of this structure represents the + * context of one SCSI command. */ +struct sg_pt_base; + + +/* The format of the version string is like this: "2.01 20090201". + * The leading digit will be incremented if this interface changes + * in a way that may impact backward compatibility. */ +const char * scsi_pt_version(); + + +/* 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); + +/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed + * together. Returns valid file descriptor( >= 0 ) if successful, otherwise + * returns -1 or a negated errno. + * In Win32 O_EXCL translated to equivalent. */ +int scsi_pt_open_flags(const char * device_name, int flags, int verbose); + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int scsi_pt_close_device(int device_fd); + +/* 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); + + +/* Creates an object that can be used to issue one or more SCSI commands + * (or task management functions). Returns NULL if problem. + * Once this object has been created it should be destroyed with + * 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 + * associate a device file descriptor (handle) with the object and a + * verbose argument that causes error messages 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. */ +struct sg_pt_base * + construct_scsi_pt_obj_with_fd(int dev_fd, int verbose); + +/* 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 * objp, int dev_fd, int verbose); + +/* 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 * objp); + +/* Clear state information held in *objp . This allows this object to be + * used to issue more than one SCSI command. The dev_fd is remembered. + * 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) */ +void set_scsi_pt_cdb(struct sg_pt_base * objp, const unsigned char * cdb, + int cdb_len); +/* Set the sense buffer and the maximum length that it can handle */ +void set_scsi_pt_sense(struct sg_pt_base * objp, unsigned char * 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 */ + unsigned char * 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 unsigned char * 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 */ +void set_pt_metadata_xfer(struct sg_pt_base * objp, unsigned char * 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); +void set_scsi_pt_task_management(struct sg_pt_base * objp, int tmf_code); +void set_scsi_pt_task_attr(struct sg_pt_base * objp, int attribute, + int priority); + +/* Following is a guard which is defined when set_scsi_pt_flags() is + * present. Older versions of this library may not have this function. */ +#define SCSI_PT_FLAGS_FUNCTION 1 +/* If neither QUEUE_AT_HEAD nor QUEUE_AT_TAIL are given, or both + * are given, use the pass-through default. */ +#define SCSI_PT_FLAGS_QUEUE_AT_TAIL 0x10 +#define SCSI_PT_FLAGS_QUEUE_AT_HEAD 0x20 +/* Set (potentially OS dependent) flags for pass-through mechanism. + * Apart from contradictions, flags can be OR-ed together. */ +void set_scsi_pt_flags(struct sg_pt_base * objp, int flags); + +#define SCSI_PT_DO_START_OK 0 +#define SCSI_PT_DO_BAD_PARAMS 1 +#define SCSI_PT_DO_TIMEOUT 2 +#define SCSI_PT_DO_NVME_STATUS 48 /* == SG_LIB_NVME_STATUS */ +/* If OS error prior to or during command submission then returns negated + * error value (e.g. Unix '-errno'). This includes interrupted system calls + * (e.g. by a signal) in which case -EINTR would be returned. Note that + * system call errors also can be fetched with get_scsi_pt_os_err(). + * Return 0 if okay (i.e. at the very least: command sent). Positive + * return values are errors (see SCSI_PT_DO_* defines). If a file descriptor + * has already been provided by construct_scsi_pt_obj_with_fd() then the + * given 'fd' can be -1 or the same value as given to the constructor. */ +int do_scsi_pt(struct sg_pt_base * objp, int fd, int timeout_secs, + int verbose); + +#define SCSI_PT_RESULT_GOOD 0 +#define SCSI_PT_RESULT_STATUS 1 /* other than GOOD and CHECK CONDITION */ +#define SCSI_PT_RESULT_SENSE 2 +#define SCSI_PT_RESULT_TRANSPORT_ERR 3 +#define SCSI_PT_RESULT_OS_ERR 4 +/* highest numbered applicable category returned */ +int get_scsi_pt_result_category(const struct sg_pt_base * objp); + +/* If not available return 0 which implies there is no residual + * value. If supported the number of bytes actually sent back by + * the device is 'dxfer_ilen - get_scsi_pt_len()' bytes. */ +int get_scsi_pt_resid(const struct sg_pt_base * objp); + +/* Returns SCSI status value (from device that received the command). If an + * NVMe command was issued directly (i.e. through do_scsi_pt() then return + * NVMe status (i.e. ((SCT << 8) | SC)) */ +int get_scsi_pt_status_response(const struct sg_pt_base * objp); + +/* Returns SCSI status value or, if NVMe command given to do_scsi_pt(), + * then returns NVMe result (i.e. DWord(0) from completion queue). If + * 'objp' is NULL then returns 0xffffffff. */ +uint32_t get_pt_result(const struct sg_pt_base * objp); + +/* Actual sense length returned. If sense data is present but + actual sense length is not known, return 'max_sense_len' */ +int get_scsi_pt_sense_len(const struct sg_pt_base * objp); + +/* If not available return 0 (for success). */ +int get_scsi_pt_os_err(const struct sg_pt_base * objp); +char * get_scsi_pt_os_err_str(const struct sg_pt_base * objp, int max_b_len, + char * b); + +/* If not available return 0 (for success) */ +int get_scsi_pt_transport_err(const struct sg_pt_base * objp); +void set_scsi_pt_transport_err(struct sg_pt_base * objp, int err); +char * get_scsi_pt_transport_err_str(const struct sg_pt_base * objp, + int max_b_len, char * b); + +/* If not available return -1 */ +int get_scsi_pt_duration_ms(const struct sg_pt_base * objp); + +/* Return true if device associated with 'objp' uses NVMe command set. To + * be useful (in modifying the type of command sent (SCSI or NVMe) then + * construct_scsi_pt_obj_with_fd() should be used followed by an invocation + * of this function. */ +bool pt_device_is_nvme(const struct sg_pt_base * objp); + +/* If a NVMe block device (which includes the NSID) handle is associated + * with 'objp', 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 * objp); + + +/* Should be invoked once per objp after other processing is complete in + * order to clean up resources. For ever successful construct_scsi_pt_obj() + * call there should be one destruct_scsi_pt_obj(). If the + * construct_scsi_pt_obj_with_fd() function was used to create this object + * then the dev_fd provided to that constructor is not altered by this + * destructor. So the user should still close dev_fd (perhaps with + * scsi_pt_close_device() ). */ +void destruct_scsi_pt_obj(struct sg_pt_base * objp); + +#ifdef SG_LIB_WIN32 +#define SG_LIB_WIN32_DIRECT 1 + +/* Request SPT direct interface when state_direct is 1, state_direct set + * to 0 for the SPT indirect interface. Default setting selected by build + * (i.e. library compile time) and is usually indirect. */ +void scsi_pt_win32_direct(int state_direct); + +/* Returns current SPT interface state, 1 for direct, 0 for indirect */ +int scsi_pt_win32_spt_state(void); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_pt_linux.h b/tools/sg_write_buffer/include/sg_pt_linux.h new file mode 100644 index 0000000..5e22fd7 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pt_linux.h @@ -0,0 +1,171 @@ +#ifndef SG_PT_LINUX_H +#define SG_PT_LINUX_H + +/* + * Copyright (c) 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. + */ + +#include <stdint.h> +#include <stdbool.h> + +#include <linux/types.h> + +#include "sg_pt_nvme.h" + +/* This header is for internal use by the sg3_utils library (libsgutils) + * and is Linux specific. Best not to include it directly in code that + * is meant to be OS independent. */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_LINUX_BSG_H + +#define BSG_PROTOCOL_SCSI 0 + +#define BSG_SUB_PROTOCOL_SCSI_CMD 0 +#define BSG_SUB_PROTOCOL_SCSI_TMF 1 +#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT 2 + +/* + * For flag constants below: + * sg.h sg_io_hdr also has bits defined for it's flags member. These + * two flag values (0x10 and 0x20) have the same meaning in sg.h . For + * bsg the BSG_FLAG_Q_AT_HEAD flag is ignored since it is the default. + */ +#define BSG_FLAG_Q_AT_TAIL 0x10 /* default is Q_AT_HEAD */ +#define BSG_FLAG_Q_AT_HEAD 0x20 + +struct sg_io_v4 { + __s32 guard; /* [i] 'Q' to differentiate from v3 */ + __u32 protocol; /* [i] 0 -> SCSI , .... */ + __u32 subprotocol; /* [i] 0 -> SCSI command, 1 -> SCSI task + management function, .... */ + + __u32 request_len; /* [i] in bytes */ + __u64 request; /* [i], [*i] {SCSI: cdb} */ + __u64 request_tag; /* [i] {SCSI: task tag (only if flagged)} */ + __u32 request_attr; /* [i] {SCSI: task attribute} */ + __u32 request_priority; /* [i] {SCSI: task priority} */ + __u32 request_extra; /* [i] {spare, for padding} */ + __u32 max_response_len; /* [i] in bytes */ + __u64 response; /* [i], [*o] {SCSI: (auto)sense data} */ + + /* "dout_": data out (to device); "din_": data in (from device) */ + __u32 dout_iovec_count; /* [i] 0 -> "flat" dout transfer else + dout_xfer points to array of iovec */ + __u32 dout_xfer_len; /* [i] bytes to be transferred to device */ + __u32 din_iovec_count; /* [i] 0 -> "flat" din transfer */ + __u32 din_xfer_len; /* [i] bytes to be transferred from device */ + __u64 dout_xferp; /* [i], [*i] */ + __u64 din_xferp; /* [i], [*o] */ + + __u32 timeout; /* [i] units: millisecond */ + __u32 flags; /* [i] bit mask */ + __u64 usr_ptr; /* [i->o] unused internally */ + __u32 spare_in; /* [i] */ + + __u32 driver_status; /* [o] 0 -> ok */ + __u32 transport_status; /* [o] 0 -> ok */ + __u32 device_status; /* [o] {SCSI: command completion status} */ + __u32 retry_delay; /* [o] {SCSI: status auxiliary information} */ + __u32 info; /* [o] additional information */ + __u32 duration; /* [o] time to complete, in milliseconds */ + __u32 response_len; /* [o] bytes of response actually written */ + __s32 din_resid; /* [o] din_xfer_len - actual_din_xfer_len */ + __s32 dout_resid; /* [o] dout_xfer_len - actual_dout_xfer_len */ + __u64 generated_tag; /* [o] {SCSI: transport generated task tag} */ + __u32 spare_out; /* [o] */ + + __u32 padding; +}; + +#else + +#include <linux/bsg.h> + +#endif + + +struct sg_pt_linux_scsi { + struct sg_io_v4 io_hdr; /* use v4 header as it is more general */ + /* Leave io_hdr in first place of this structure */ + bool is_sg; + bool is_bsg; + bool is_nvme; /* OS device type, if false ignore nvme_direct */ + bool nvme_direct; /* false: our SNTL; true: received NVMe command */ + bool mdxfer_out; /* direction of metadata xfer, true->data-out */ + bool scsi_dsense; /* SCSI descriptor sense active when true */ + int dev_fd; /* -1 if not given (yet) */ + int in_err; + int os_err; + 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; /* DW0 from completion queue */ + uint32_t nvme_status; /* SCT|SC: DW3 27:17 from completion queue, + * note: the DNR+More bit are not there. + * The whole 16 byte completion q entry is + * sent back as sense data */ + uint32_t mdxfer_len; + void * mdxferp; + uint8_t * nvme_id_ctlp; /* cached response to controller IDENTIFY */ + uint8_t * free_nvme_id_ctlp; + unsigned char tmf_request[4]; +}; + +struct sg_pt_base { + struct sg_pt_linux_scsi impl; +}; + + +#ifndef sg_nvme_admin_cmd +#define sg_nvme_admin_cmd sg_nvme_passthru_cmd +#endif + +/* Linux NVMe related ioctls */ +#ifndef NVME_IOCTL_ID +#define NVME_IOCTL_ID _IO('N', 0x40) +#endif +#ifndef NVME_IOCTL_ADMIN_CMD +#define NVME_IOCTL_ADMIN_CMD _IOWR('N', 0x41, struct sg_nvme_admin_cmd) +#endif +#ifndef NVME_IOCTL_SUBMIT_IO +#define NVME_IOCTL_SUBMIT_IO _IOW('N', 0x42, struct sg_nvme_user_io) +#endif +#ifndef NVME_IOCTL_IO_CMD +#define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct sg_nvme_passthru_cmd) +#endif +#ifndef NVME_IOCTL_RESET +#define NVME_IOCTL_RESET _IO('N', 0x44) +#endif +#ifndef NVME_IOCTL_SUBSYS_RESET +#define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45) +#endif + +extern bool sg_bsg_nvme_char_major_checked; +extern int sg_bsg_major; +extern volatile int sg_nvme_char_major; +extern long sg_lin_page_size; + +void sg_find_bsg_nvme_char_major(int verbose); +int sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb); + +/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5) + * to the name of its associated char device (e.g. /dev/nvme0). If this + * occurs true is returned and the char device name is placed in 'b' (as + * long as b_len is sufficient). Otherwise false is returned. */ +bool sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len, + char * b); + + +#ifdef __cplusplus +} +#endif + +#endif /* end of SG_PT_LINUX_H */ diff --git a/tools/sg_write_buffer/include/sg_pt_nvme.h b/tools/sg_write_buffer/include/sg_pt_nvme.h new file mode 100644 index 0000000..3df98b4 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pt_nvme.h @@ -0,0 +1,172 @@ +#ifndef SG_PT_NVME_H +#define SG_PT_NVME_H + +/* + * Copyright (c) 2017-2018 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. + */ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* structures copied and slightly modified from <linux/nvme_ioctl.h> which + * is Copyright (c) 2011-2014, Intel Corporation. */ + + +/* Note that the command input structure is in (packed) "cpu" format. That + * means, for example, if the CPU is little endian (most are) then so is the + * structure. However what comes out in the data-in buffer (e.g. for the + * Admin Identify command response) is almost all little endian following ATA + * (but no SCSI and IP which are big endian) and Intel's preference. There + * are exceptions, for example the EUI-64 identifiers in the Admin Identify + * response are big endian. + * + * Code online (e.g. nvme-cli at github.com) seems to like packed strcutures, + * the author prefers byte offset plus a range of unaligned integer builders + * such as those in sg_unaligned.h . + */ + +#ifdef __GNUC__ +#ifndef __clang__ + struct __attribute__((__packed__)) sg_nvme_user_io +#else + struct sg_nvme_user_io +#endif +#else +struct sg_nvme_user_io +#endif +{ + uint8_t opcode; + uint8_t flags; + uint16_t control; + uint16_t nblocks; + uint16_t rsvd; + uint64_t metadata; + uint64_t addr; + uint64_t slba; + uint32_t dsmgmt; + uint32_t reftag; + uint16_t apptag; + uint16_t appmask; +} +#ifdef SG_LIB_FREEBSD +__packed; +#else +; +#endif + +/* Using byte offsets and unaligned be/le copies safer than packed + * structures. These are for sg_nvme_user_io . */ +#define SG_NVME_IO_OPCODE 0 +#define SG_NVME_IO_FLAGS 1 +#define SG_NVME_IO_CONTROL 2 +#define SG_NVME_IO_NBLOCKS 4 +#define SG_NVME_IO_RSVD 6 +#define SG_NVME_IO_METADATA 8 +#define SG_NVME_IO_ADDR 16 +#define SG_NVME_IO_SLBA 24 +#define SG_NVME_IO_DSMGMT 32 +#define SG_NVME_IO_REFTAG 36 +#define SG_NVME_IO_APPTAG 40 +#define SG_NVME_IO_APPMASK 42 + +#ifdef __GNUC__ +#ifndef __clang__ + struct __attribute__((__packed__)) sg_nvme_passthru_cmd +#else + struct sg_nvme_passthru_cmd +#endif +#else +struct sg_nvme_passthru_cmd +#endif +{ + uint8_t opcode; + uint8_t flags; + uint16_t rsvd1; + uint32_t nsid; + uint32_t cdw2; + uint32_t cdw3; + uint64_t metadata; + uint64_t addr; + uint32_t metadata_len; + uint32_t data_len; + uint32_t cdw10; + uint32_t cdw11; + uint32_t cdw12; + uint32_t cdw13; + uint32_t cdw14; + uint32_t cdw15; +#ifdef SG_LIB_LINUX + uint32_t timeout_ms; + uint32_t result; /* out: DWord(0) from completion queue */ +#endif +} +#ifdef SG_LIB_FREEBSD +__packed; +#else +; +#endif + + +/* Using byte offsets and unaligned be/le copies safer than packed + * structures. These are for sg_nvme_passthru_cmd . */ +#define SG_NVME_PT_OPCODE 0 /* length: 1 byte */ +#define SG_NVME_PT_FLAGS 1 /* length: 1 byte */ +#define SG_NVME_PT_RSVD1 2 /* length: 2 bytes */ +#define SG_NVME_PT_NSID 4 /* length: 4 bytes */ +#define SG_NVME_PT_CDW2 8 /* length: 4 bytes */ +#define SG_NVME_PT_CDW3 12 /* length: 4 bytes */ +#define SG_NVME_PT_METADATA 16 /* length: 8 bytes */ +#define SG_NVME_PT_ADDR 24 /* length: 8 bytes */ +#define SG_NVME_PT_METADATA_LEN 32 /* length: 4 bytes */ +#define SG_NVME_PT_DATA_LEN 36 /* length: 4 bytes */ +#define SG_NVME_PT_CDW10 40 /* length: 4 bytes */ +#define SG_NVME_PT_CDW11 44 /* length: 4 bytes */ +#define SG_NVME_PT_CDW12 48 /* length: 4 bytes */ +#define SG_NVME_PT_CDW13 52 /* length: 4 bytes */ +#define SG_NVME_PT_CDW14 56 /* length: 4 bytes */ +#define SG_NVME_PT_CDW15 60 /* length: 4 bytes */ + +#ifdef SG_LIB_LINUX +/* General references state that "all NVMe commands are 64 bytes long". If + * so then the following are add-ons by Linux, go to the OS and not the + * the NVMe device. */ +#define SG_NVME_PT_TIMEOUT_MS 64 /* length: 4 bytes */ +#define SG_NVME_PT_RESULT 68 /* length: 4 bytes */ +#endif + +/* Byte offset of Result and Status (plus phase bit) in CQ */ +#define SG_NVME_PT_CQ_RESULT 0 /* CDW0, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW0 0 /* CDW0, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW1 4 /* CDW1, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW2 8 /* CDW2, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW3 12 /* CDW3, length: 4 bytes */ +#define SG_NVME_PT_CQ_STATUS_P 14 /* CDW3 31:16, length: 2 bytes */ + + +/* Valid namespace IDs (nsid_s) range from 1 to 0xfffffffe, leaving: */ +#define SG_NVME_BROADCAST_NSID 0xffffffff /* all namespaces */ +#define SG_NVME_CTL_NSID 0x0 /* the "controller's" namespace */ + +/* Given the NVMe Identify Controller response and optionally the NVMe + * Identify Namespace response (NULL otherwise), generate the SCSI VPD + * page 0x83 (device identification) descriptor(s) in dop. Return the + * number of bytes written which will not exceed max_do_len. Probably use + * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport + * protocol (tproto) should be -1 if not known, else SCSI value. + * N.B. Does not write total VPD page length into dop[2:3] . */ +int sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p, + const uint8_t * nvme_id_ns_p, int pdt, + int tproto, uint8_t * dop, int max_do_len); + +#ifdef __cplusplus +} +#endif + +#endif /* SG_PT_NVME_H */ diff --git a/tools/sg_write_buffer/include/sg_pt_win32.h b/tools/sg_write_buffer/include/sg_pt_win32.h new file mode 100644 index 0000000..b49437f --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pt_win32.h @@ -0,0 +1,473 @@ +#ifndef SG_PT_WIN32_H +#define SG_PT_WIN32_H +/* + * The information in this file was obtained from scsi-wnt.h by + * Richard Stemmer, rs@epost.de . He in turn gives credit to + * Jay A. Key (for scsipt.c). + * The plscsi program (by Pat LaVarre <p.lavarre@ieee.org>) has + * also been used as a reference. + * Much of the information in this header can also be obtained + * from msdn.microsoft.com . + * Updated for cygwin version 1.7.17 changes 20121026 + */ + +/* WIN32_LEAN_AND_MEAN may be required to prevent inclusion of <winioctl.h> */ +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCSI_MAX_SENSE_LEN 64 +#define SCSI_MAX_CDB_LEN 16 +#define SCSI_MAX_INDIRECT_DATA 16384 + +typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + ULONG_PTR DataBufferOffset; /* was ULONG; problem in 64 bit */ + ULONG SenseInfoOffset; + UCHAR Cdb[SCSI_MAX_CDB_LEN]; +} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH; + + +typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + PVOID DataBuffer; + ULONG SenseInfoOffset; + UCHAR Cdb[SCSI_MAX_CDB_LEN]; +} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT; + + +typedef struct { + SCSI_PASS_THROUGH spt; + /* plscsi shows a follow on 16 bytes allowing 32 byte cdb */ + ULONG Filler; + UCHAR ucSenseBuf[SCSI_MAX_SENSE_LEN]; + UCHAR ucDataBuf[SCSI_MAX_INDIRECT_DATA]; +} SCSI_PASS_THROUGH_WITH_BUFFERS, *PSCSI_PASS_THROUGH_WITH_BUFFERS; + + +typedef struct { + SCSI_PASS_THROUGH_DIRECT spt; + ULONG Filler; + UCHAR ucSenseBuf[SCSI_MAX_SENSE_LEN]; +} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; + + + +typedef struct { + UCHAR NumberOfLogicalUnits; + UCHAR InitiatorBusId; + ULONG InquiryDataOffset; +} SCSI_BUS_DATA, *PSCSI_BUS_DATA; + + +typedef struct { + UCHAR NumberOfBusses; + SCSI_BUS_DATA BusData[1]; +} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO; + + +typedef struct { + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + BOOLEAN DeviceClaimed; + ULONG InquiryDataLength; + ULONG NextInquiryDataOffset; + UCHAR InquiryData[1]; +} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA; + + +typedef struct { + ULONG Length; + UCHAR PortNumber; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; +} SCSI_ADDRESS, *PSCSI_ADDRESS; + +/* + * Standard IOCTL define + */ +#ifndef CTL_CODE +#define CTL_CODE(DevType, Function, Method, Access) \ + (((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) +#endif + +/* + * file access values + */ +#ifndef FILE_ANY_ACCESS +#define FILE_ANY_ACCESS 0 +#endif +#ifndef FILE_READ_ACCESS +#define FILE_READ_ACCESS 0x0001 +#endif +#ifndef FILE_WRITE_ACCESS +#define FILE_WRITE_ACCESS 0x0002 +#endif + +// IOCTL_STORAGE_QUERY_PROPERTY + +#define FILE_DEVICE_MASS_STORAGE 0x0000002d +#define IOCTL_STORAGE_BASE FILE_DEVICE_MASS_STORAGE +#define FILE_ANY_ACCESS 0 + +// #define METHOD_BUFFERED 0 + +#define IOCTL_STORAGE_QUERY_PROPERTY \ + CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) + + +#ifndef _DEVIOCTL_ +typedef enum _STORAGE_BUS_TYPE { + BusTypeUnknown = 0x00, + BusTypeScsi = 0x01, + BusTypeAtapi = 0x02, + BusTypeAta = 0x03, + BusType1394 = 0x04, + BusTypeSsa = 0x05, + BusTypeFibre = 0x06, + BusTypeUsb = 0x07, + BusTypeRAID = 0x08, + BusTypeiScsi = 0x09, + BusTypeSas = 0x0A, + BusTypeSata = 0x0B, + BusTypeSd = 0x0C, + BusTypeMmc = 0x0D, + BusTypeVirtual = 0xE, + BusTypeFileBackedVirtual = 0xF, + BusTypeSpaces = 0x10, + BusTypeNvme = 0x11, + BusTypeSCM = 0x12, + BusTypeUfs = 0x13, + BusTypeMax = 0x14, + BusTypeMaxReserved = 0x7F +} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE; + +typedef enum _STORAGE_PROTOCOL_TYPE { + ProtocolTypeUnknown = 0, + ProtocolTypeScsi, + ProtocolTypeAta, + ProtocolTypeNvme, + ProtocolTypeSd +} STORAGE_PROTOCOL_TYPE; + +typedef enum _STORAGE_PROTOCOL_NVME_DATA_TYPE { + NVMeDataTypeUnknown = 0, + NVMeDataTypeIdentify, + NVMeDataTypeLogPage, + NVMeDataTypeFeature +} STORAGE_PROTOCOL_NVME_DATA_TYPE; + +typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA { + STORAGE_PROTOCOL_TYPE ProtocolType; + ULONG DataType; + ULONG ProtocolDataRequestValue; + ULONG ProtocolDataRequestSubValue; + ULONG ProtocolDataOffset; + ULONG ProtocolDataLength; + ULONG FixedProtocolReturnData; + ULONG Reserved[3]; +} STORAGE_PROTOCOL_SPECIFIC_DATA; + + +typedef struct _STORAGE_DEVICE_DESCRIPTOR { + ULONG Version; + ULONG Size; + UCHAR DeviceType; + UCHAR DeviceTypeModifier; + BOOLEAN RemovableMedia; + BOOLEAN CommandQueueing; + ULONG VendorIdOffset; /* 0 if not available */ + ULONG ProductIdOffset; /* 0 if not available */ + ULONG ProductRevisionOffset;/* 0 if not available */ + ULONG SerialNumberOffset; /* -1 if not available ?? */ + STORAGE_BUS_TYPE BusType; + ULONG RawPropertiesLength; + UCHAR RawDeviceProperties[1]; +} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR; + +#define STORAGE_PROTOCOL_STRUCTURE_VERSION 0x1 + +#define IOCTL_STORAGE_PROTOCOL_COMMAND \ + CTL_CODE(IOCTL_STORAGE_BASE, 0x04F0, METHOD_BUFFERED, \ + FILE_READ_ACCESS | FILE_WRITE_ACCESS) + +typedef struct _STORAGE_PROTOCOL_COMMAND { + DWORD Version; /* STORAGE_PROTOCOL_STRUCTURE_VERSION */ + DWORD Length; + STORAGE_PROTOCOL_TYPE ProtocolType; + DWORD Flags; + DWORD ReturnStatus; + DWORD ErrorCode; + DWORD CommandLength; + DWORD ErrorInfoLength; + DWORD DataToDeviceTransferLength; + DWORD DataFromDeviceTransferLength; + DWORD TimeOutValue; + DWORD ErrorInfoOffset; + DWORD DataToDeviceBufferOffset; + DWORD DataFromDeviceBufferOffset; + DWORD CommandSpecific; + DWORD Reserved0; + DWORD FixedProtocolReturnData; + DWORD Reserved1[3]; + BYTE Command[1]; /* has CommandLength elements */ +} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND; + +#endif /* _DEVIOCTL_ */ + +typedef struct _STORAGE_DEVICE_UNIQUE_IDENTIFIER { + ULONG Version; + ULONG Size; + ULONG StorageDeviceIdOffset; + ULONG StorageDeviceOffset; + ULONG DriveLayoutSignatureOffset; +} STORAGE_DEVICE_UNIQUE_IDENTIFIER, *PSTORAGE_DEVICE_UNIQUE_IDENTIFIER; + +// Use CompareStorageDuids(PSTORAGE_DEVICE_UNIQUE_IDENTIFIER duid1, duid2) +// to test for equality + +#ifndef _DEVIOCTL_ +typedef enum _STORAGE_QUERY_TYPE { + PropertyStandardQuery = 0, + PropertyExistsQuery, + PropertyMaskQuery, + PropertyQueryMaxDefined +} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE; + +typedef enum _STORAGE_PROPERTY_ID { + StorageDeviceProperty = 0, + StorageAdapterProperty, + StorageDeviceIdProperty, + StorageDeviceUniqueIdProperty, + StorageDeviceWriteCacheProperty, + StorageMiniportProperty, + StorageAccessAlignmentProperty, + /* Identify controller goes to adapter; Identify namespace to device */ + StorageAdapterProtocolSpecificProperty = 49, + StorageDeviceProtocolSpecificProperty = 50 +} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID; + +typedef struct _STORAGE_PROPERTY_QUERY { + STORAGE_PROPERTY_ID PropertyId; + STORAGE_QUERY_TYPE QueryType; + UCHAR AdditionalParameters[1]; +} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY; + +typedef struct _STORAGE_PROTOCOL_DATA_DESCRIPTOR { + DWORD Version; + DWORD Size; + STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecificData; +} STORAGE_PROTOCOL_DATA_DESCRIPTOR, *PSTORAGE_PROTOCOL_DATA_DESCRIPTOR; + +// Command completion status +// The "Phase Tag" field and "Status Field" are separated in spec. We define +// them in the same data structure to ease the memory access from software. +// +typedef union { + struct { + USHORT P : 1; // Phase Tag (P) + + USHORT SC : 8; // Status Code (SC) + USHORT SCT : 3; // Status Code Type (SCT) + USHORT Reserved : 2; + USHORT M : 1; // More (M) + USHORT DNR : 1; // Do Not Retry (DNR) + } DUMMYSTRUCTNAME; + USHORT AsUshort; +} NVME_COMMAND_STATUS, *PNVME_COMMAND_STATUS; + +// Information of log: NVME_LOG_PAGE_ERROR_INFO. Size: 64 bytes +// +typedef struct { + ULONGLONG ErrorCount; + USHORT SQID; // Submission Queue ID + USHORT CMDID; // Command ID + NVME_COMMAND_STATUS Status; // Status Field: This field indicates the + // Status Field for the command that + // completed. The Status Field is located in + // bits 15:01, bit 00 corresponds to the Phase + // Tag posted for the command. + struct { + USHORT Byte : 8; // Byte in command that contained error + USHORT Bit : 3; // Bit in command that contained error + USHORT Reserved : 5; + } ParameterErrorLocation; + + ULONGLONG Lba; // LBA: This field indicates the first LBA + // that experienced the error condition, if + // applicable. + ULONG NameSpace; // Namespace: This field indicates the nsid + // that the error is associated with, if + // applicable. + UCHAR VendorInfoAvailable; // Vendor Specific Information Available + UCHAR Reserved0[3]; + ULONGLONG CommandSpecificInfo; // This field contains command specific + // information. If used, the command + // definition specifies the information + // returned. + UCHAR Reserved1[24]; +} NVME_ERROR_INFO_LOG, *PNVME_ERROR_INFO_LOG; + +typedef struct { + + ULONG DW0; + ULONG Reserved; + + union { + struct { + USHORT SQHD; // SQ Head Pointer (SQHD) + USHORT SQID; // SQ Identifier (SQID) + } DUMMYSTRUCTNAME; + + ULONG AsUlong; + } DW2; + + union { + struct { + USHORT CID; // Command Identifier (CID) + NVME_COMMAND_STATUS Status; + } DUMMYSTRUCTNAME; + + ULONG AsUlong; + } DW3; + +} NVME_COMPLETION_ENTRY, *PNVME_COMPLETION_ENTRY; + + +// Bit-mask values for STORAGE_PROTOCOL_COMMAND - "Flags" field. +// +// Flag indicates the request targeting to adapter instead of device. +#define STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST 0x80000000 + +// +// Status values for STORAGE_PROTOCOL_COMMAND - "ReturnStatus" field. +// +#define STORAGE_PROTOCOL_STATUS_PENDING 0x0 +#define STORAGE_PROTOCOL_STATUS_SUCCESS 0x1 +#define STORAGE_PROTOCOL_STATUS_ERROR 0x2 +#define STORAGE_PROTOCOL_STATUS_INVALID_REQUEST 0x3 +#define STORAGE_PROTOCOL_STATUS_NO_DEVICE 0x4 +#define STORAGE_PROTOCOL_STATUS_BUSY 0x5 +#define STORAGE_PROTOCOL_STATUS_DATA_OVERRUN 0x6 +#define STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES 0x7 + +#define STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED 0xFF + +// Command Length for Storage Protocols. +// +// NVMe commands are always 64 bytes. +#define STORAGE_PROTOCOL_COMMAND_LENGTH_NVME 0x40 + +// Command Specific Information for Storage Protocols - CommandSpecific field +// +#define STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND 0x01 +#define STORAGE_PROTOCOL_SPECIFIC_NVME_NVM_COMMAND 0x02 + +#endif /* _DEVIOCTL_ */ + + +// NVME_PASS_THROUGH + +#ifndef STB_IO_CONTROL +typedef struct _SRB_IO_CONTROL { + ULONG HeaderLength; + UCHAR Signature[8]; + ULONG Timeout; + ULONG ControlCode; + ULONG ReturnCode; + ULONG Length; +} SRB_IO_CONTROL, *PSRB_IO_CONTROL; +#endif + +#ifndef NVME_PASS_THROUGH_SRB_IO_CODE + +#define NVME_SIG_STR "NvmeMini" +#define NVME_STORPORT_DRIVER 0xe000 + +#define NVME_PASS_THROUGH_SRB_IO_CODE \ + CTL_CODE(NVME_STORPORT_DRIVER, 0x0800, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#pragma pack(1) + +/* Following is pre-Win10; used with DeviceIoControl(IOCTL_SCSI_MINIPORT), + * in Win10 need DeviceIoControl(IOCTL_STORAGE_PROTOCOL_COMMAND) for pure + * pass-through. Win10 also has "Protocol specific queries" for things like + * Identify and Get feature. */ +typedef struct _NVME_PASS_THROUGH_IOCTL +{ + SRB_IO_CONTROL SrbIoCtrl; + ULONG VendorSpecific[6]; + ULONG NVMeCmd[16]; /* Command DW[0...15] */ + ULONG CplEntry[4]; /* Completion DW[0...3] */ + ULONG Direction; /* 0=None, 1=Out, 2=In, 3=I/O */ + ULONG QueueId; /* 0=AdminQ */ + ULONG DataBufferLen; /* sizeof(DataBuffer) if Data In */ + ULONG MetaDataLen; + ULONG ReturnBufferLen; /* offsetof(DataBuffer), plus + * sizeof(DataBuffer) if Data Out */ + UCHAR DataBuffer[1]; +} NVME_PASS_THROUGH_IOCTL; +#pragma pack() + +#endif // NVME_PASS_THROUGH_SRB_IO_CODE + + +/* + * method codes + */ +#define METHOD_BUFFERED 0 +#define METHOD_IN_DIRECT 1 +#define METHOD_OUT_DIRECT 2 +#define METHOD_NEITHER 3 + + +#define IOCTL_SCSI_BASE 0x00000004 + +/* + * constants for DataIn member of SCSI_PASS_THROUGH* structures + */ +#define SCSI_IOCTL_DATA_OUT 0 +#define SCSI_IOCTL_DATA_IN 1 +#define SCSI_IOCTL_DATA_UNSPECIFIED 2 + +#define IOCTL_SCSI_PASS_THROUGH CTL_CODE(IOCTL_SCSI_BASE, 0x0401, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_MINIPORT CTL_CODE(IOCTL_SCSI_BASE, 0x0402, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_GET_INQUIRY_DATA CTL_CODE(IOCTL_SCSI_BASE, 0x0403, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_GET_CAPABILITIES CTL_CODE(IOCTL_SCSI_BASE, 0x0404, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE(IOCTL_SCSI_BASE, 0x0405, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_GET_ADDRESS CTL_CODE(IOCTL_SCSI_BASE, 0x0406, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_unaligned.h b/tools/sg_write_buffer/include/sg_unaligned.h new file mode 100644 index 0000000..3b2c70a --- /dev/null +++ b/tools/sg_write_buffer/include/sg_unaligned.h @@ -0,0 +1,325 @@ +#ifndef SG_UNALIGNED_H +#define SG_UNALIGNED_H + +/* + * Copyright (c) 2014-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. + */ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Borrowed from the Linux kernel, via mhvtl */ + +/* In the first section below, functions that copy unsigned integers in a + * computer's native format, to and from an unaligned big endian sequence of + * bytes. Big endian byte format "on the wire" is the default used by SCSI + * standards (www.t10.org). Big endian is also the network byte order. */ + +static inline uint16_t __get_unaligned_be16(const uint8_t *p) +{ + return p[0] << 8 | p[1]; +} + +static inline uint32_t __get_unaligned_be32(const uint8_t *p) +{ + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +} + +/* Assume 48 bit value placed in uint64_t */ +static inline uint64_t __get_unaligned_be48(const uint8_t *p) +{ + return (uint64_t)__get_unaligned_be16(p) << 32 | + __get_unaligned_be32(p + 2); +} + +static inline uint64_t __get_unaligned_be64(const uint8_t *p) +{ + return (uint64_t)__get_unaligned_be32(p) << 32 | + __get_unaligned_be32(p + 4); +} + +static inline void __put_unaligned_be16(uint16_t val, uint8_t *p) +{ + *p++ = val >> 8; + *p++ = val; +} + +static inline void __put_unaligned_be32(uint32_t val, uint8_t *p) +{ + __put_unaligned_be16(val >> 16, p); + __put_unaligned_be16(val, p + 2); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline void __put_unaligned_be48(uint64_t val, uint8_t *p) +{ + __put_unaligned_be16(val >> 32, p); + __put_unaligned_be32(val, p + 2); +} + +static inline void __put_unaligned_be64(uint64_t val, uint8_t *p) +{ + __put_unaligned_be32(val >> 32, p); + __put_unaligned_be32(val, p + 4); +} + +static inline uint16_t sg_get_unaligned_be16(const void *p) +{ + return __get_unaligned_be16((const uint8_t *)p); +} + +static inline uint32_t sg_get_unaligned_be24(const void *p) +{ + return ((const uint8_t *)p)[0] << 16 | ((const uint8_t *)p)[1] << 8 | + ((const uint8_t *)p)[2]; +} + +static inline uint32_t sg_get_unaligned_be32(const void *p) +{ + return __get_unaligned_be32((const uint8_t *)p); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline uint64_t sg_get_unaligned_be48(const void *p) +{ + return __get_unaligned_be48((const uint8_t *)p); +} + +static inline uint64_t sg_get_unaligned_be64(const void *p) +{ + return __get_unaligned_be64((const uint8_t *)p); +} + +/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than + * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is + * an 8 byte unsigned integer. */ +static inline uint64_t sg_get_unaligned_be(int num_bytes, const void *p) +{ + if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t))) + return 0; + else { + const uint8_t * xp = (const uint8_t *)p; + uint64_t res = *xp; + + for (++xp; num_bytes > 1; ++xp, --num_bytes) + res = (res << 8) | *xp; + return res; + } +} + +static inline void sg_put_unaligned_be16(uint16_t val, void *p) +{ + __put_unaligned_be16(val, (uint8_t *)p); +} + +static inline void sg_put_unaligned_be24(uint32_t val, void *p) +{ + ((uint8_t *)p)[0] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[2] = val & 0xff; +} + +static inline void sg_put_unaligned_be32(uint32_t val, void *p) +{ + __put_unaligned_be32(val, (uint8_t *)p); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline void sg_put_unaligned_be48(uint64_t val, void *p) +{ + __put_unaligned_be48(val, (uint8_t *)p); +} + +static inline void sg_put_unaligned_be64(uint64_t val, void *p) +{ + __put_unaligned_be64(val, (uint8_t *)p); +} + +/* Since cdb and parameter blocks are often memset to zero before these + * unaligned function partially fill them, then check for a val of zero + * and ignore if it is with these variants. */ +static inline void sg_nz_put_unaligned_be16(uint16_t val, void *p) +{ + if (val) + __put_unaligned_be16(val, (uint8_t *)p); +} + +static inline void sg_nz_put_unaligned_be24(uint32_t val, void *p) +{ + if (val) { + ((uint8_t *)p)[0] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[2] = val & 0xff; + } +} + +static inline void sg_nz_put_unaligned_be32(uint32_t val, void *p) +{ + if (val) + __put_unaligned_be32(val, (uint8_t *)p); +} + +static inline void sg_nz_put_unaligned_be64(uint64_t val, void *p) +{ + if (val) + __put_unaligned_be64(val, (uint8_t *)p); +} + + +/* Below are the little endian equivalents of the big endian functions + * above. Little endian is used by ATA, PCI and NVMe. + */ + +static inline uint16_t __get_unaligned_le16(const uint8_t *p) +{ + return p[1] << 8 | p[0]; +} + +static inline uint32_t __get_unaligned_le32(const uint8_t *p) +{ + return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0]; +} + +static inline uint64_t __get_unaligned_le64(const uint8_t *p) +{ + return (uint64_t)__get_unaligned_le32(p + 4) << 32 | + __get_unaligned_le32(p); +} + +static inline void __put_unaligned_le16(uint16_t val, uint8_t *p) +{ + *p++ = val; + *p++ = val >> 8; +} + +static inline void __put_unaligned_le32(uint32_t val, uint8_t *p) +{ + __put_unaligned_le16(val >> 16, p + 2); + __put_unaligned_le16(val, p); +} + +static inline void __put_unaligned_le64(uint64_t val, uint8_t *p) +{ + __put_unaligned_le32(val >> 32, p + 4); + __put_unaligned_le32(val, p); +} + +static inline uint16_t sg_get_unaligned_le16(const void *p) +{ + return __get_unaligned_le16((const uint8_t *)p); +} + +static inline uint32_t sg_get_unaligned_le24(const void *p) +{ + return (uint32_t)__get_unaligned_le16((const uint8_t *)p) | + ((const uint8_t *)p)[2] << 16; +} + +static inline uint32_t sg_get_unaligned_le32(const void *p) +{ + return __get_unaligned_le32((const uint8_t *)p); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline uint64_t sg_get_unaligned_le48(const void *p) +{ + return (uint64_t)__get_unaligned_le16((const uint8_t *)p + 4) << 32 | + __get_unaligned_le32((const uint8_t *)p); +} + +static inline uint64_t sg_get_unaligned_le64(const void *p) +{ + return __get_unaligned_le64((const uint8_t *)p); +} + +/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than + * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is + * an 8 byte unsigned integer. */ +static inline uint64_t sg_get_unaligned_le(int num_bytes, const void *p) +{ + if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t))) + return 0; + else { + const uint8_t * xp = (const uint8_t *)p + (num_bytes - 1); + uint64_t res = *xp; + + for (--xp; num_bytes > 1; --xp, --num_bytes) + res = (res << 8) | *xp; + return res; + } +} + +static inline void sg_put_unaligned_le16(uint16_t val, void *p) +{ + __put_unaligned_le16(val, (uint8_t *)p); +} + +static inline void sg_put_unaligned_le24(uint32_t val, void *p) +{ + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; +} + +static inline void sg_put_unaligned_le32(uint32_t val, void *p) +{ + __put_unaligned_le32(val, (uint8_t *)p); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline void sg_put_unaligned_le48(uint64_t val, void *p) +{ + ((uint8_t *)p)[5] = (val >> 40) & 0xff; + ((uint8_t *)p)[4] = (val >> 32) & 0xff; + ((uint8_t *)p)[3] = (val >> 24) & 0xff; + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; +} + +static inline void sg_put_unaligned_le64(uint64_t val, void *p) +{ + __put_unaligned_le64(val, (uint8_t *)p); +} + +/* Since cdb and parameter blocks are often memset to zero before these + * unaligned function partially fill them, then check for a val of zero + * and ignore if it is with these variants. */ +static inline void sg_nz_put_unaligned_le16(uint16_t val, void *p) +{ + if (val) + __put_unaligned_le16(val, (uint8_t *)p); +} + +static inline void sg_nz_put_unaligned_le24(uint32_t val, void *p) +{ + if (val) { + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; + } +} + +static inline void sg_nz_put_unaligned_le32(uint32_t val, void *p) +{ + if (val) + __put_unaligned_le32(val, (uint8_t *)p); +} + +static inline void sg_nz_put_unaligned_le64(uint64_t val, void *p) +{ + if (val) + __put_unaligned_le64(val, (uint8_t *)p); +} + +#ifdef __cplusplus +} +#endif + +#endif /* SG_UNALIGNED_H */ diff --git a/tools/sg_write_buffer/sg_cmds_basic.c b/tools/sg_write_buffer/sg_cmds_basic.c new file mode 100644 index 0000000..35a4991 --- /dev/null +++ b/tools/sg_write_buffer/sg_cmds_basic.c @@ -0,0 +1,663 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +/* + * CONTENTS + * Some SCSI commands are executed in many contexts and hence began + * to appear in several sg3_utils utilities. This files centralizes + * some of the low level command execution code. In most cases the + * interpretation of the command response is left to the each + * utility. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_pt.h" +#include "sg_unaligned.h" + +/* Needs to be after config.h */ +#ifdef SG_LIB_LINUX +#include <errno.h> +#endif + + +static const char * const version_str = "1.83 20180204"; + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define EBUFF_SZ 256 + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define INQUIRY_CMD 0x12 +#define INQUIRY_CMDLEN 6 +#define REQUEST_SENSE_CMD 0x3 +#define REQUEST_SENSE_CMDLEN 6 +#define REPORT_LUNS_CMD 0xa0 +#define REPORT_LUNS_CMDLEN 12 +#define TUR_CMD 0x0 +#define TUR_CMDLEN 6 + +#define SAFE_STD_INQ_RESP_LEN 36 /* other lengths lock up some devices */ + + +const char * +sg_cmds_version() +{ + return version_str; +} + +#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; +} + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. */ +int +sg_cmds_open_device(const char * device_name, bool read_only, int verbose) +{ + /* 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; + + return scsi_pt_open_device(device_name, read_only, verbose); +} + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. */ +int +sg_cmds_open_flags(const char * device_name, int flags, int verbose) +{ + return scsi_pt_open_flags(device_name, flags, verbose); +} + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int +sg_cmds_close_device(int device_fd) +{ + return scsi_pt_close_device(device_fd); +} + +static const char * const pass_through_s = "pass-through"; + +static int +sg_cmds_process_helper(const char * leadin, int mx_di_len, int resid, + const unsigned char * sbp, int slen, bool noisy, + int verbose, int * o_sense_cat) +{ + int scat, got; + bool n = false; + bool check_data_in = false; + char b[512]; + + scat = sg_err_category_sense(sbp, slen); + switch (scat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_ABORTED_COMMAND: + case SG_LIB_CAT_COPY_ABORTED: + case SG_LIB_CAT_DATA_PROTECT: + case SG_LIB_CAT_PROTECTION: + case SG_LIB_CAT_NO_SENSE: + case SG_LIB_CAT_MISCOMPARE: + n = false; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_MEDIUM_HARD: + check_data_in = true; +#if defined(__GNUC__) +#if (__GNUC__ >= 7) + __attribute__((fallthrough)); + /* FALL THROUGH */ +#endif +#endif + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_SENSE: + default: + n = noisy; + break; + } + if (verbose || n) { + if (leadin && (strlen(leadin) > 0)) + pr2ws("%s:\n", leadin); + sg_get_sense_str(NULL, sbp, slen, (verbose > 1), + sizeof(b), b); + pr2ws("%s", b); + if ((mx_di_len > 0) && (resid > 0)) { + got = mx_di_len - resid; + if ((verbose > 2) || check_data_in || (got > 0)) + pr2ws(" %s requested %d bytes (data-in) but got %d " + "bytes\n", pass_through_s, mx_di_len, got); + } + } + if (o_sense_cat) + *o_sense_cat = scat; + return -2; +} + +/* This is a helper function used by sg_cmds_* implementations after the + * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid + * sense data is found it is decoded and output to sg_warnings_strm (def: + * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for + * "sense" category (may not be fatal), -1 for failed, 0, or a positive + * number. If 'mx_di_len > 0' then asks pass-through for resid and returns + * (mx_di_len - resid); otherwise returns 0. So for data-in it should return + * the actual number of bytes received. For data-out (to device) or no data + * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category + * output via 'o_sense_cat' pointer (if not NULL). Note that several sense + * categories also have data in bytes received; -2 is still returned. */ +int +sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin, + int pt_res, int mx_di_len, const unsigned char * sbp, + bool noisy, int verbose, int * o_sense_cat) +{ + int got, cat, duration, slen, resid, resp_code, sstat; + bool transport_sense; + char b[1024]; + + if (NULL == leadin) + leadin = ""; + if (pt_res < 0) { +#ifdef SG_LIB_LINUX + if (verbose) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); + if ((-ENXIO == pt_res) && o_sense_cat) { + if (verbose > 2) + pr2ws("map ENXIO to SG_LIB_CAT_NOT_READY\n"); + *o_sense_cat = SG_LIB_CAT_NOT_READY; + return -2; + } else if (noisy && (0 == verbose)) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); +#else + if (noisy || verbose) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); +#endif + return -1; + } else if (SCSI_PT_DO_BAD_PARAMS == pt_res) { + pr2ws("%s: bad %s setup\n", leadin, pass_through_s); + return -1; + } else if (SCSI_PT_DO_TIMEOUT == pt_res) { + pr2ws("%s: %s timeout\n", leadin, pass_through_s); + return -1; + } + if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + pr2ws(" duration=%d ms\n", duration); + resid = (mx_di_len > 0) ? get_scsi_pt_resid(ptvp) : 0; + slen = get_scsi_pt_sense_len(ptvp); + switch ((cat = get_scsi_pt_result_category(ptvp))) { + case SCSI_PT_RESULT_GOOD: + if (sbp && (slen > 7)) { + resp_code = sbp[0] & 0x7f; + /* SBC referrals can have status=GOOD and sense_key=COMPLETED */ + if (resp_code >= 0x70) { + if (resp_code < 0x72) { + if (SPC_SK_NO_SENSE != (0xf & sbp[2])) + sg_err_category_sense(sbp, slen); + } else if (resp_code < 0x74) { + if (SPC_SK_NO_SENSE != (0xf & sbp[1])) + sg_err_category_sense(sbp, slen); + } + } + } + if (mx_di_len > 0) { + got = mx_di_len - resid; + if ((verbose > 1) && (resid != 0)) + pr2ws(" %s: %s requested %d bytes (data-in) but got %d " + "bytes\n", leadin, pass_through_s, mx_di_len, got); + if (got >= 0) + return got; + else { + if (verbose) + pr2ws(" %s: %s can't get negative bytes, say it got " + "none\n", leadin, pass_through_s); + return 0; + } + } else + return 0; + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + sstat = get_scsi_pt_status_response(ptvp); + if (o_sense_cat) { + switch (sstat) { + case SAM_STAT_RESERVATION_CONFLICT: + *o_sense_cat = SG_LIB_CAT_RES_CONFLICT; + return -2; + case SAM_STAT_CONDITION_MET: + *o_sense_cat = SG_LIB_CAT_CONDITION_MET; + return -2; + case SAM_STAT_BUSY: + *o_sense_cat = SG_LIB_CAT_BUSY; + return -2; + case SAM_STAT_TASK_SET_FULL: + *o_sense_cat = SG_LIB_CAT_TS_FULL; + return -2; + case SAM_STAT_ACA_ACTIVE: + *o_sense_cat = SG_LIB_CAT_ACA_ACTIVE; + return -2; + case SAM_STAT_TASK_ABORTED: + *o_sense_cat = SG_LIB_CAT_TASK_ABORTED; + return -2; + default: + break; + } + } + if (verbose || noisy) { + sg_get_scsi_status_str(sstat, sizeof(b), b); + pr2ws("%s: scsi status: %s\n", leadin, b); + } + return -1; + case SCSI_PT_RESULT_SENSE: + return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp, slen, + noisy, verbose, o_sense_cat); + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose || noisy) { + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); + pr2ws("%s: transport: %s\n", leadin, b); + } +#ifdef SG_LIB_LINUX + transport_sense = (slen > 0); +#else + transport_sense = ((SAM_STAT_CHECK_CONDITION == + get_scsi_pt_status_response(ptvp)) && (slen > 0)); +#endif + if (transport_sense) + return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp, + slen, noisy, verbose, o_sense_cat); + else + return -1; + case SCSI_PT_RESULT_OS_ERR: + if (verbose || noisy) { + get_scsi_pt_os_err_str(ptvp, sizeof(b), b); + pr2ws("%s: os: %s\n", leadin, b); + } + return -1; + default: + pr2ws("%s: unknown %s result category (%d)\n", leadin, pass_through_s, + cat); + return -1; + } +} + +bool +sg_cmds_is_nvme(const struct sg_pt_base * ptvp) +{ + return pt_device_is_nvme(ptvp); +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +static const char * const inquiry_s = "inquiry"; + +static int +sg_ll_inquiry_com(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + int res, ret, k, sense_cat, resid; + unsigned char inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + unsigned char * up; + struct sg_pt_base * ptvp; + + if (cmddt) + inq_cdb[1] |= 0x2; + if (evpd) + inq_cdb[1] |= 0x1; + inq_cdb[2] = (unsigned char)pg_op; + /* 16 bit allocation length (was 8, increased in spc3r09, 200209) */ + sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3); + if (verbose) { + pr2ws(" %s cdb: ", inquiry_s); + for (k = 0; k < INQUIRY_CMDLEN; ++k) + pr2ws("%02x ", inq_cdb[k]); + pr2ws("\n"); + } + if (resp && (mx_resp_len > 0)) { + up = (unsigned char *)resp; + up[0] = 0x7f; /* defensive prefill */ + if (mx_resp_len > 4) + up[4] = 0; + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) { + pr2ws("%s: out of memory\n", __func__); + if (residp) + *residp = 0; + return -1; + } + set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, inquiry_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else if (ret < 4) { + if (verbose) + pr2ws("%s: got too few bytes (%d)\n", __func__, ret); + ret = SG_LIB_CAT_MALFORMED; + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s resid (%d) should never exceed requested " + "len=%d\n", inquiry_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer, based on resid */ + memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +} + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. + * The CMDDT field is obsolete in the INQUIRY cdb. */ +int +sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + return sg_ll_inquiry_com(sg_fd, cmddt, evpd, pg_op, resp, mx_resp_len, + 0 /* timeout_sec */, NULL, noisy, verbose); +} + +/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response. + * Returns 0 when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data, + bool noisy, int verbose) +{ + int ret; + unsigned char inq_resp[SAFE_STD_INQ_RESP_LEN]; + + if (inq_data) { + memset(inq_data, 0, sizeof(* inq_data)); + inq_data->peripheral_qualifier = 0x3; + inq_data->peripheral_type = 0x1f; + } + ret = sg_ll_inquiry_com(sg_fd, false, false, 0, inq_resp, + sizeof(inq_resp), 0, NULL, noisy, verbose); + + if (inq_data && (0 == ret)) { + inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7; + inq_data->peripheral_type = inq_resp[0] & 0x1f; + inq_data->byte_1 = inq_resp[1]; + inq_data->version = inq_resp[2]; + inq_data->byte_3 = inq_resp[3]; + inq_data->byte_5 = inq_resp[5]; + inq_data->byte_6 = inq_resp[6]; + inq_data->byte_7 = inq_resp[7]; + memcpy(inq_data->vendor, inq_resp + 8, 8); + memcpy(inq_data->product, inq_resp + 16, 16); + memcpy(inq_data->revision, inq_resp + 32, 4); + } + return ret; +} + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. + * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so + * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION + * CODES command instead). Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + return sg_ll_inquiry_com(sg_fd, false, evpd, pg_op, resp, mx_resp_len, + timeout_secs, residp, noisy, verbose); +} + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Looks for progress indicator if 'progress' non-NULL; + * if found writes value [0..65535] else write -1. + * Returns 0 when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress, + bool noisy, int verbose) +{ + static const char * const tur_s = "test unit ready"; + int res, ret, k, sense_cat; + unsigned char tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (verbose) { + pr2ws(" %s cdb: ", tur_s); + for (k = 0; k < TUR_CMDLEN; ++k) + pr2ws("%02x ", tur_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(tur_s)))) + return -1; + set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_packet_id(ptvp, pack_id); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, tur_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + if (progress) { + int slen = get_scsi_pt_sense_len(ptvp); + + if (! sg_get_sense_progress_fld(sense_b, slen, progress)) + *progress = -1; + } + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Returns 0 when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose) +{ + return sg_ll_test_unit_ready_progress(sg_fd, pack_id, NULL, noisy, + verbose); +} + +/* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various + * SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const rq_s = "request sense"; + int k, ret, res, sense_cat; + unsigned char rs_cdb[REQUEST_SENSE_CMDLEN] = + {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (desc) + rs_cdb[1] |= 0x1; + if (mx_resp_len > 0xff) { + pr2ws("mx_resp_len cannot exceed 255\n"); + return -1; + } + rs_cdb[4] = mx_resp_len & 0xff; + if (verbose) { + pr2ws(" %s cmd: ", rq_s); + for (k = 0; k < REQUEST_SENSE_CMDLEN; ++k) + pr2ws("%02x ", rs_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(rq_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, rq_s, res, mx_resp_len, sense_b, noisy, + verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((mx_resp_len >= 8) && (ret < 8)) { + if (verbose) + pr2ws(" %s: got %d bytes in response, too short\n", rq_s, + ret); + ret = -1; + } else + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const report_luns_s = "report luns"; + int k, ret, res, sense_cat; + unsigned char rl_cdb[REPORT_LUNS_CMDLEN] = + {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + rl_cdb[2] = select_report & 0xff; + sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", report_luns_s); + for (k = 0; k < REPORT_LUNS_CMDLEN; ++k) + pr2ws("%02x ", rl_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(report_luns_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, report_luns_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/tools/sg_write_buffer/sg_cmds_basic2.c b/tools/sg_write_buffer/sg_cmds_basic2.c new file mode 100644 index 0000000..18b6cd7 --- /dev/null +++ b/tools/sg_write_buffer/sg_cmds_basic2.c @@ -0,0 +1,1069 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +/* + * CONTENTS + * Some SCSI commands are executed in many contexts and hence began + * to appear in several sg3_utils utilities. This files centralizes + * some of the low level command execution code. In most cases the + * interpretation of the command response is left to the each + * utility. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_pt.h" +#include "sg_unaligned.h" + + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define EBUFF_SZ 256 + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define SYNCHRONIZE_CACHE_CMD 0x35 +#define SYNCHRONIZE_CACHE_CMDLEN 10 +#define SERVICE_ACTION_IN_16_CMD 0x9e +#define SERVICE_ACTION_IN_16_CMDLEN 16 +#define READ_CAPACITY_16_SA 0x10 +#define READ_CAPACITY_10_CMD 0x25 +#define READ_CAPACITY_10_CMDLEN 10 +#define MODE_SENSE6_CMD 0x1a +#define MODE_SENSE6_CMDLEN 6 +#define MODE_SENSE10_CMD 0x5a +#define MODE_SENSE10_CMDLEN 10 +#define MODE_SELECT6_CMD 0x15 +#define MODE_SELECT6_CMDLEN 6 +#define MODE_SELECT10_CMD 0x55 +#define MODE_SELECT10_CMDLEN 10 +#define LOG_SENSE_CMD 0x4d +#define LOG_SENSE_CMDLEN 10 +#define LOG_SELECT_CMD 0x4c +#define LOG_SELECT_CMDLEN 10 +#define START_STOP_CMD 0x1b +#define START_STOP_CMDLEN 6 +#define PREVENT_ALLOW_CMD 0x1e +#define PREVENT_ALLOW_CMDLEN 6 + +#define MODE6_RESP_HDR_LEN 4 +#define MODE10_RESP_HDR_LEN 8 +#define MODE_RESP_ARB_LEN 1024 + +#define INQUIRY_RESP_INITIAL_LEN 36 + + +#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; +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group, + unsigned int lba, unsigned int count, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "synchronize cache(10)"; + int res, ret, k, sense_cat; + unsigned char sc_cdb[SYNCHRONIZE_CACHE_CMDLEN] = + {SYNCHRONIZE_CACHE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (sync_nv) + sc_cdb[1] |= 4; + if (immed) + sc_cdb[1] |= 2; + sg_put_unaligned_be32((uint32_t)lba, sc_cdb + 2); + sc_cdb[6] = group & 0x1f; + if (count > 0xffff) { + pr2ws("count too big\n"); + return -1; + } + sg_put_unaligned_be16((int16_t)count, sc_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SYNCHRONIZE_CACHE_CMDLEN; ++k) + pr2ws("%02x ", sc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, sc_cdb, sizeof(sc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read capacity(16)"; + int k, ret, res, sense_cat; + unsigned char rc_cdb[SERVICE_ACTION_IN_16_CMDLEN] = + {SERVICE_ACTION_IN_16_CMD, READ_CAPACITY_16_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (pmi) { /* lbs only valid when pmi set */ + rc_cdb[14] |= 1; + sg_put_unaligned_be64(llba, rc_cdb + 2); + } + /* Allocation length, no guidance in SBC-2 rev 15b */ + sg_put_unaligned_be32((uint32_t)mx_resp_len, rc_cdb + 10); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", rc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ CAPACITY (10) command. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read capacity(10)"; + int k, ret, res, sense_cat; + unsigned char rc_cdb[READ_CAPACITY_10_CMDLEN] = + {READ_CAPACITY_10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (pmi) { /* lbs only valid when pmi set */ + rc_cdb[8] |= 1; + sg_put_unaligned_be32((uint32_t)lba, rc_cdb + 2); + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_CAPACITY_10_CMDLEN; ++k) + pr2ws("%02x ", rc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, int sub_pg_code, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode sense(6)"; + int res, ret, k, sense_cat, resid; + unsigned char modes_cdb[MODE_SENSE6_CMDLEN] = + {MODE_SENSE6_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (unsigned char)(dbd ? 0x8 : 0); + modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff); + modes_cdb[4] = (unsigned char)(mx_resp_len & 0xff); + if (mx_resp_len > 0xff) { + pr2ws("mx_resp_len too big\n"); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SENSE6_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +} + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + return sg_ll_mode_sense10_v2(sg_fd, llbaa, dbd, pc, pg_code, sub_pg_code, + resp, mx_resp_len, 0, NULL, noisy, verbose); +} + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + int timeout_secs, int * residp, bool noisy, int verbose) +{ + int res, ret, k, sense_cat, resid; + static const char * const cdb_name_s = "mode sense(10)"; + struct sg_pt_base * ptvp; + unsigned char modes_cdb[MODE_SENSE10_CMDLEN] = + {MODE_SENSE10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + + modes_cdb[1] = (unsigned char)((dbd ? 0x8 : 0) | (llbaa ? 0x10 : 0)); + modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff); + sg_put_unaligned_be16((int16_t)mx_resp_len, modes_cdb + 7); + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + goto gen_err; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SENSE10_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + goto gen_err; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +gen_err: + if (residp) + *residp = 0; + return -1; +} + +/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode select(6)"; + int res, ret, k, sense_cat; + unsigned char modes_cdb[MODE_SELECT6_CMDLEN] = + {MODE_SELECT6_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0)); + modes_cdb[4] = (unsigned char)(param_len & 0xff); + if (param_len > 0xff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SELECT6_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode select(10)"; + int res, ret, k, sense_cat; + unsigned char modes_cdb[MODE_SELECT10_CMDLEN] = + {MODE_SELECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0)); + sg_put_unaligned_be16((int16_t)param_len, modes_cdb + 7); + if (param_len > 0xffff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SELECT10_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. In most cases users are + * interested in the first mode page. This function returns the (byte) + * offset of the start of the first mode page. Set mode_sense_6 to true for + * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful + * or -1 if failure. If there is a failure a message is written to err_buff + * if it is non-NULL and err_buff_len > 0. */ +int +sg_mode_page_offset(const unsigned char * resp, int resp_len, + bool mode_sense_6, char * err_buff, int err_buff_len) +{ + int bd_len, calc_len, offset; + bool err_buff_ok = ((err_buff_len > 0) && err_buff); + + if ((NULL == resp) || (resp_len < 4)) + goto too_short; + if (mode_sense_6) { + calc_len = resp[0] + 1; + bd_len = resp[3]; + offset = bd_len + MODE6_RESP_HDR_LEN; + } else { /* Mode sense(10) */ + if (resp_len < 8) + goto too_short; + calc_len = sg_get_unaligned_be16(resp) + 2; + bd_len = sg_get_unaligned_be16(resp + 6); + /* LongLBA doesn't change this calculation */ + offset = bd_len + MODE10_RESP_HDR_LEN; + } + if ((offset + 2) > calc_len) { + if (err_buff_ok) + snprintf(err_buff, err_buff_len, "calculated response " + "length too small, offset=%d calc_len=%d bd_len=%d\n", + offset, calc_len, bd_len); + offset = -1; + } + return offset; +too_short: + if (err_buff_ok) + snprintf(err_buff, err_buff_len, "given MS(%d) response length (%d) " + "too short\n", (mode_sense_6 ? 6 : 10), resp_len); + return -1; +} + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. This functions returns the + * length (in bytes) of those three components. Note that the return value + * can exceed resp_len in which case the MODE SENSE command should be + * re-issued with a larger response buffer. If bd_lenp is non-NULL and if + * successful the block descriptor length (in bytes) is written to *bd_lenp. + * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10) + * responses. Returns -1 if there is an error (e.g. response too short). */ +int +sg_msense_calc_length(const unsigned char * resp, int resp_len, + bool mode_sense_6, int * bd_lenp) +{ + int calc_len; + + if (NULL == resp) + goto an_err; + if (mode_sense_6) { + if (resp_len < 4) + goto an_err; + calc_len = resp[0] + 1; + } else { + if (resp_len < 8) + goto an_err; + calc_len = sg_get_unaligned_be16(resp + 0) + 2; + } + if (bd_lenp) + *bd_lenp = mode_sense_6 ? resp[3] : sg_get_unaligned_be16(resp + 6); + return calc_len; +an_err: + if (bd_lenp) + *bd_lenp = 0; + return -1; +} + +/* Fetches current, changeable, default and/or saveable modes pages as + * indicated by pcontrol_arr for given pg_code and sub_pg_code. If + * mode6==false then use MODE SENSE (10) else use MODE SENSE (6). If + * flexible set and mode data length seems wrong then try and + * fix (compensating hack for bad device or driver). pcontrol_arr + * should have 4 elements for output of current, changeable, default + * and saved values respectively. Each element should be NULL or + * at least mx_mpage_len bytes long. + * Return of 0 -> overall success, various SG_LIB_CAT_* positive values or + * -1 -> other errors. + * If success_mask pointer is not NULL then first zeros it. Then set bits + * 0, 1, 2 and/or 3 if the current, changeable, default and saved values + * respectively have been fetched. If error on current page + * then stops and returns that error; otherwise continues if an error is + * detected but returns the first error encountered. */ +int +sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, int sub_pg_code, + bool dbd, bool flexible, int mx_mpage_len, + int * success_mask, void * pcontrol_arr[], + int * reported_lenp, int verbose) +{ + bool resp_mode6; + int k, n, res, offset, calc_len, xfer_len; + int resid = 0; + const int msense10_hlen = MODE10_RESP_HDR_LEN; + unsigned char buff[MODE_RESP_ARB_LEN]; + char ebuff[EBUFF_SZ]; + int first_err = 0; + + if (success_mask) + *success_mask = 0; + if (reported_lenp) + *reported_lenp = 0; + if (mx_mpage_len < 4) + return 0; + memset(ebuff, 0, sizeof(ebuff)); + /* first try to find length of current page response */ + memset(buff, 0, msense10_hlen); + if (mode6) /* want first 8 bytes just in case */ + res = sg_ll_mode_sense6(sg_fd, dbd, 0 /* pc */, pg_code, + sub_pg_code, buff, msense10_hlen, true, + verbose); + else /* MODE SENSE(10) obviously */ + res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd, + 0 /* pc */, pg_code, sub_pg_code, buff, + msense10_hlen, 0, &resid, true, verbose); + if (0 != res) + return res; + n = buff[0]; + if (reported_lenp) { + int m; + + m = sg_msense_calc_length(buff, msense10_hlen, mode6, NULL) - resid; + if (m < 0) /* Grrr, this should not happen */ + m = 0; + *reported_lenp = m; + } + resp_mode6 = mode6; + if (flexible) { + if (mode6 && (n < 3)) { + resp_mode6 = false; + if (verbose) + pr2ws(">>> msense(6) but resp[0]=%d so try msense(10) " + "response processing\n", n); + } + if ((! mode6) && (n > 5)) { + if ((n > 11) && (0 == (n % 2)) && (0 == buff[4]) && + (0 == buff[5]) && (0 == buff[6])) { + buff[1] = n; + buff[0] = 0; + if (verbose) + pr2ws(">>> msense(10) but resp[0]=%d and not msense(6) " + "response so fix length\n", n); + } else + resp_mode6 = true; + } + } + if (verbose && (resp_mode6 != mode6)) + pr2ws(">>> msense(%d) but resp[0]=%d so switch response " + "processing\n", (mode6 ? 6 : 10), buff[0]); + calc_len = sg_msense_calc_length(buff, msense10_hlen, resp_mode6, NULL); + if (calc_len > MODE_RESP_ARB_LEN) + calc_len = MODE_RESP_ARB_LEN; + offset = sg_mode_page_offset(buff, calc_len, resp_mode6, ebuff, EBUFF_SZ); + if (offset < 0) { + if (('\0' != ebuff[0]) && (verbose > 0)) + pr2ws("%s: %s\n", __func__, ebuff); + return SG_LIB_CAT_MALFORMED; + } + xfer_len = calc_len - offset; + if (xfer_len > mx_mpage_len) + xfer_len = mx_mpage_len; + + for (k = 0; k < 4; ++k) { + if (NULL == pcontrol_arr[k]) + continue; + memset(pcontrol_arr[k], 0, mx_mpage_len); + resid = 0; + if (mode6) + res = sg_ll_mode_sense6(sg_fd, dbd, k /* pc */, + pg_code, sub_pg_code, buff, + calc_len, true, verbose); + else + res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd, + k /* pc */, pg_code, sub_pg_code, + buff, calc_len, 0, &resid, true, + verbose); + if (res || resid) { + if (0 == first_err) { + if (res) + first_err = res; + else { + first_err = -49; /* unexpected resid != 0 */ + if (verbose) + pr2ws("%s: unexpected resid=%d, page=0x%x, " + "pcontrol=%d\n", __func__, resid, pg_code, k); + } + } + if (0 == k) + break; /* if problem on current page, it won't improve */ + else + continue; + } + if (xfer_len > 0) + memcpy(pcontrol_arr[k], buff + offset, xfer_len); + if (success_mask) + *success_mask |= (1 << k); + } + return first_err; +} + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. */ +int +sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, unsigned char * resp, + int mx_resp_len, bool noisy, int verbose) +{ + return sg_ll_log_sense_v2(sg_fd, ppc, sp, pc, pg_code, subpg_code, + paramp, resp, mx_resp_len, 0, NULL, noisy, + verbose); +} + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, unsigned char * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "log sense"; + int res, ret, k, sense_cat, resid; + unsigned char logs_cdb[LOG_SENSE_CMDLEN] = + {LOG_SENSE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + goto gen_err; + } + logs_cdb[1] = (unsigned char)((ppc ? 2 : 0) | (sp ? 1 : 0)); + logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + logs_cdb[3] = (unsigned char)(subpg_code & 0xff); + sg_put_unaligned_be16((int16_t)paramp, logs_cdb + 5); + sg_put_unaligned_be16((int16_t)mx_resp_len, logs_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < LOG_SENSE_CMDLEN; ++k) + pr2ws("%02x ", logs_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + goto gen_err; + set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((mx_resp_len > 3) && (ret < 4)) { + /* resid indicates LOG SENSE response length bad, so zero it */ + resp[2] = 0; + resp[3] = 0; + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +gen_err: + if (residp) + *residp = 0; + return -1; +} + +/* Invokes a SCSI LOG SELECT command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code, + int subpg_code, unsigned char * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "log select"; + int res, ret, k, sense_cat; + unsigned char logs_cdb[LOG_SELECT_CMDLEN] = + {LOG_SELECT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (param_len > 0xffff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + logs_cdb[1] = (unsigned char)((pcr ? 2 : 0) | (sp ? 1 : 0)); + logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + logs_cdb[3] = (unsigned char)(subpg_code & 0xff); + sg_put_unaligned_be16((int16_t)param_len, logs_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < LOG_SELECT_CMDLEN; ++k) + pr2ws("%02x ", logs_cdb[k]); + pr2ws("\n"); + } + if ((verbose > 1) && (param_len > 0)) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr(paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI START STOP UNIT command (SBC + MMC). + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and + * format_layer_number(mmc) fields. They also overlap on the noflush(sbc) + * and fl(mmc) one bit field. This is the cause of the awkardly named + * pc_mod__fl_num and noflush__fl arguments to this function. + * */ +int +sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num, + int power_cond, bool noflush__fl, bool loej, bool start, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "start stop unit"; + int k, res, ret, sense_cat; + struct sg_pt_base * ptvp; + unsigned char ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + + if (immed) + ssuBlk[1] = 0x1; + ssuBlk[3] = pc_mod__fl_num & 0xf; /* bits 2 and 3 are reserved in MMC */ + ssuBlk[4] = ((power_cond & 0xf) << 4); + if (noflush__fl) + ssuBlk[4] |= 0x4; + if (loej) + ssuBlk[4] |= 0x2; + if (start) + ssuBlk[4] |= 0x1; + if (verbose) { + pr2ws(" %s command:", cdb_name_s); + for (k = 0; k < (int)sizeof(ssuBlk); ++k) + pr2ws(" %02x", ssuBlk[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, START_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command + * [was in SPC-3 but displaced from SPC-4 into SBC-3, MMC-5, SSC-3] + * prevent==0 allows removal, prevent==1 prevents removal ... + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "prevent allow medium removal"; + int k, res, ret, sense_cat; + unsigned char p_cdb[PREVENT_ALLOW_CMDLEN] = + {PREVENT_ALLOW_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((prevent < 0) || (prevent > 3)) { + pr2ws("prevent argument should be 0, 1, 2 or 3\n"); + return -1; + } + p_cdb[4] |= (prevent & 0x3); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PREVENT_ALLOW_CMDLEN; ++k) + pr2ws("%02x ", p_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, p_cdb, sizeof(p_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/tools/sg_write_buffer/sg_cmds_extra.c b/tools/sg_write_buffer/sg_cmds_extra.c new file mode 100644 index 0000000..bebc859 --- /dev/null +++ b/tools/sg_write_buffer/sg_cmds_extra.c @@ -0,0 +1,2524 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#include "sg_pt.h" +#include "sg_unaligned.h" + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define SERVICE_ACTION_IN_16_CMD 0x9e +#define SERVICE_ACTION_IN_16_CMDLEN 16 +#define SERVICE_ACTION_OUT_16_CMD 0x9f +#define SERVICE_ACTION_OUT_16_CMDLEN 16 +#define MAINTENANCE_IN_CMD 0xa3 +#define MAINTENANCE_IN_CMDLEN 12 +#define MAINTENANCE_OUT_CMD 0xa4 +#define MAINTENANCE_OUT_CMDLEN 12 + +#define ATA_PT_12_CMD 0xa1 +#define ATA_PT_12_CMDLEN 12 +#define ATA_PT_16_CMD 0x85 +#define ATA_PT_16_CMDLEN 16 +#define ATA_PT_32_SA 0x1ff0 +#define ATA_PT_32_CMDLEN 32 +#define FORMAT_UNIT_CMD 0x4 +#define FORMAT_UNIT_CMDLEN 6 +#define PERSISTENT_RESERVE_IN_CMD 0x5e +#define PERSISTENT_RESERVE_IN_CMDLEN 10 +#define PERSISTENT_RESERVE_OUT_CMD 0x5f +#define PERSISTENT_RESERVE_OUT_CMDLEN 10 +#define READ_BLOCK_LIMITS_CMD 0x5 +#define READ_BLOCK_LIMITS_CMDLEN 6 +#define READ_BUFFER_CMD 0x3c +#define READ_BUFFER_CMDLEN 10 +#define READ_DEFECT10_CMD 0x37 +#define READ_DEFECT10_CMDLEN 10 +#define REASSIGN_BLKS_CMD 0x7 +#define REASSIGN_BLKS_CMDLEN 6 +#define RECEIVE_DIAGNOSTICS_CMD 0x1c +#define RECEIVE_DIAGNOSTICS_CMDLEN 6 +#define THIRD_PARTY_COPY_OUT_CMD 0x83 /* was EXTENDED_COPY_CMD */ +#define THIRD_PARTY_COPY_OUT_CMDLEN 16 +#define THIRD_PARTY_COPY_IN_CMD 0x84 /* was RECEIVE_COPY_RESULTS_CMD */ +#define THIRD_PARTY_COPY_IN_CMDLEN 16 +#define SEND_DIAGNOSTIC_CMD 0x1d +#define SEND_DIAGNOSTIC_CMDLEN 6 +#define SERVICE_ACTION_IN_12_CMD 0xab +#define SERVICE_ACTION_IN_12_CMDLEN 12 +#define READ_LONG10_CMD 0x3e +#define READ_LONG10_CMDLEN 10 +#define UNMAP_CMD 0x42 +#define UNMAP_CMDLEN 10 +#define VERIFY10_CMD 0x2f +#define VERIFY10_CMDLEN 10 +#define VERIFY16_CMD 0x8f +#define VERIFY16_CMDLEN 16 +#define WRITE_LONG10_CMD 0x3f +#define WRITE_LONG10_CMDLEN 10 +#define WRITE_BUFFER_CMD 0x3b +#define WRITE_BUFFER_CMDLEN 10 +#define PRE_FETCH10_CMD 0x34 +#define PRE_FETCH10_CMDLEN 10 +#define PRE_FETCH16_CMD 0x90 +#define PRE_FETCH16_CMDLEN 16 +#define SEEK10_CMD 0x2b +#define SEEK10_CMDLEN 10 + +#define GET_LBA_STATUS16_SA 0x12 +#define GET_LBA_STATUS32_SA 0x12 +#define READ_LONG_16_SA 0x11 +#define READ_MEDIA_SERIAL_NUM_SA 0x1 +#define REPORT_IDENTIFYING_INFORMATION_SA 0x5 +#define REPORT_TGT_PRT_GRP_SA 0xa +#define SET_IDENTIFYING_INFORMATION_SA 0x6 +#define SET_TGT_PRT_GRP_SA 0xa +#define WRITE_LONG_16_SA 0x11 +#define REPORT_REFERRALS_SA 0x13 +#define EXTENDED_COPY_LID1_SA 0x0 + +#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; +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + + +/* Invokes a SCSI GET LBA STATUS(16) command (SBC). Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt, + void * resp, int alloc_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Get LBA status(16)"; + int k, res, sense_cat, ret; + unsigned char getLbaStatCmd[SERVICE_ACTION_IN_16_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(getLbaStatCmd, 0, sizeof(getLbaStatCmd)); + getLbaStatCmd[0] = SERVICE_ACTION_IN_16_CMD; + getLbaStatCmd[1] = GET_LBA_STATUS16_SA; + + sg_put_unaligned_be64(start_llba, getLbaStatCmd + 2); + sg_put_unaligned_be32((uint32_t)alloc_len, getLbaStatCmd + 10); + getLbaStatCmd[14] = rt; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", getLbaStatCmd[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, getLbaStatCmd, sizeof(getLbaStatCmd)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, alloc_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp, + int alloc_len, bool noisy, int verbose) +{ + return sg_ll_get_lba_status16(sg_fd, start_llba, /* rt = */ 0x0, resp, + alloc_len, noisy, verbose); +} + +#define GLS32_CMD_LEN 32 + +int +sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len, + uint32_t element_id, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Get LBA status(32)"; + int k, res, sense_cat, ret; + unsigned char gls32_cmd[GLS32_CMD_LEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(gls32_cmd, 0, sizeof(gls32_cmd)); + gls32_cmd[0] = SG_VARIABLE_LENGTH_CMD; + gls32_cmd[7] = GLS32_CMD_LEN - 8; + sg_put_unaligned_be16((uint16_t)GET_LBA_STATUS32_SA, gls32_cmd + 8); + gls32_cmd[10] = rt; + sg_put_unaligned_be64(start_llba, gls32_cmd + 12); + sg_put_unaligned_be32(scan_len, gls32_cmd + 20); + sg_put_unaligned_be32(element_id, gls32_cmd + 24); + sg_put_unaligned_be32((uint32_t)alloc_len, gls32_cmd + 28); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GLS32_CMD_LEN; ++k) + pr2ws("%02x ", gls32_cmd[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, gls32_cmd, sizeof(gls32_cmd)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, alloc_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + return sg_ll_report_tgt_prt_grp2(sg_fd, resp, mx_resp_len, false, noisy, + verbose); +} + +/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len, + bool extended, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Report target port groups"; + int k, res, ret, sense_cat; + unsigned char rtpg_cdb[MAINTENANCE_IN_CMDLEN] = + {MAINTENANCE_IN_CMD, REPORT_TGT_PRT_GRP_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (extended) + rtpg_cdb[1] |= 0x20; + sg_put_unaligned_be32((uint32_t)mx_resp_len, rtpg_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k) + pr2ws("%02x ", rtpg_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rtpg_cdb, sizeof(rtpg_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Set target port groups"; + int k, res, ret, sense_cat; + unsigned char stpg_cdb[MAINTENANCE_OUT_CMDLEN] = + {MAINTENANCE_OUT_CMD, SET_TGT_PRT_GRP_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)param_len, stpg_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k) + pr2ws("%02x ", stpg_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, stpg_cdb, sizeof(stpg_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg, + void * resp, int mx_resp_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Report referrals"; + int k, res, ret, sense_cat; + unsigned char repRef_cdb[SERVICE_ACTION_IN_16_CMDLEN] = + {SERVICE_ACTION_IN_16_CMD, REPORT_REFERRALS_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be64(start_llba, repRef_cdb + 2); + sg_put_unaligned_be32((uint32_t)mx_resp_len, repRef_cdb + 10); + if (one_seg) + repRef_cdb[14] = 0x1; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", repRef_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, repRef_cdb, sizeof(repRef_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can + * take a long time, if so set long_duration flag in which case the timeout + * is set to 7200 seconds; if the value of long_duration is > 7200 then that + * value is taken as the timeout value in seconds. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit, + bool devofl_bit, bool unitofl_bit, int long_duration, + void * paramp, int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Send diagnostic"; + int k, res, ret, sense_cat, tmout; + unsigned char senddiag_cdb[SEND_DIAGNOSTIC_CMDLEN] = + {SEND_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + senddiag_cdb[1] = (unsigned char)(st_code << 5); + if (pf_bit) + senddiag_cdb[1] |= 0x10; + if (st_bit) + senddiag_cdb[1] |= 0x4; + if (devofl_bit) + senddiag_cdb[1] |= 0x2; + if (unitofl_bit) + senddiag_cdb[1] |= 0x1; + sg_put_unaligned_be16((uint16_t)param_len, senddiag_cdb + 3); + if (long_duration > LONG_PT_TIMEOUT) + tmout = long_duration; + else + tmout = long_duration ? LONG_PT_TIMEOUT : DEF_PT_TIMEOUT; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SEND_DIAGNOSTIC_CMDLEN; ++k) + pr2ws("%02x ", senddiag_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + if (paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + pr2ws(" %s timeout: %d seconds\n", cdb_name_s, tmout); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + return sg_ll_receive_diag_v2(sg_fd, pcv, pg_code, resp, mx_resp_len, 0, + NULL, noisy, verbose); +} + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + int resid = 0; + int k, res, ret, sense_cat; + static const char * const cdb_name_s = "Receive diagnostic results"; + struct sg_pt_base * ptvp; + unsigned char rcvdiag_cdb[RECEIVE_DIAGNOSTICS_CMDLEN] = + {RECEIVE_DIAGNOSTICS_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + + if (pcv) + rcvdiag_cdb[1] = 0x1; + rcvdiag_cdb[2] = (unsigned char)(pg_code); + sg_put_unaligned_be16((uint16_t)mx_resp_len, rcvdiag_cdb + 3); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < RECEIVE_DIAGNOSTICS_CMDLEN; ++k) + pr2ws("%02x ", rcvdiag_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) { + if (residp) + *residp = 0; + return -1; + } + set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, int dl_format, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Read defect(10)"; + int res, k, ret, sense_cat; + unsigned char rdef_cdb[READ_DEFECT10_CMDLEN] = + {READ_DEFECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + rdef_cdb[2] = (dl_format & 0x7); + if (req_plist) + rdef_cdb[2] |= 0x10; + if (req_glist) + rdef_cdb[2] |= 0x8; + sg_put_unaligned_be16((uint16_t)mx_resp_len, rdef_cdb + 7); + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_DEFECT10_CMDLEN; ++k) + pr2ws("%02x ", rdef_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rdef_cdb, sizeof(rdef_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Read media serial number"; + int k, res, ret, sense_cat; + unsigned char rmsn_cdb[SERVICE_ACTION_IN_12_CMDLEN] = + {SERVICE_ACTION_IN_12_CMD, READ_MEDIA_SERIAL_NUM_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)mx_resp_len, rmsn_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_12_CMDLEN; ++k) + pr2ws("%02x ", rmsn_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rmsn_cdb, sizeof(rmsn_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was + * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Report identifying information"; + int k, res, ret, sense_cat; + unsigned char rii_cdb[MAINTENANCE_IN_CMDLEN] = {MAINTENANCE_IN_CMD, + REPORT_IDENTIFYING_INFORMATION_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)max_resp_len, rii_cdb + 6); + rii_cdb[10] |= (itype << 1) & 0xfe; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k) + pr2ws("%02x ", rii_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rii_cdb, sizeof(rii_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, max_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, max_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was + * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Set identifying information"; + int k, res, ret, sense_cat; + unsigned char sii_cdb[MAINTENANCE_OUT_CMDLEN] = {MAINTENANCE_OUT_CMD, + SET_IDENTIFYING_INFORMATION_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)param_len, sii_cdb + 6); + sii_cdb[10] |= (itype << 1) & 0xfe; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k) + pr2ws("%02x ", sii_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, sii_cdb, sizeof(sii_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst, + dlist_format, 0, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int ffmt, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst, + dlist_format, ffmt, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a FORMAT UNIT (SBC-4) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * FFMT field added in sbc4r10 [20160121] */ +int +sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Format unit"; + int k, res, ret, sense_cat, tmout; + unsigned char fu_cdb[FORMAT_UNIT_CMDLEN] = + {FORMAT_UNIT_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (fmtpinfo) + fu_cdb[1] |= (fmtpinfo << 6); + if (longlist) + fu_cdb[1] |= 0x20; + if (fmtdata) + fu_cdb[1] |= 0x10; + if (cmplst) + fu_cdb[1] |= 0x8; + if (dlist_format) + fu_cdb[1] |= (dlist_format & 0x7); + if (ffmt) + fu_cdb[4] |= (ffmt & 0x3); + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < 6; ++k) + pr2ws("%02x ", fu_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + if (param_len > 0) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + pr2ws(" %s timeout: %d seconds\n", cdb_name_s, tmout); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, fu_cdb, sizeof(fu_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REASSIGN BLOCKS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, void * paramp, + int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Reassign blocks"; + int res, k, ret, sense_cat; + unsigned char reass_cdb[REASSIGN_BLKS_CMDLEN] = + {REASSIGN_BLKS_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (longlba) + reass_cdb[1] = 0x2; + if (longlist) + reass_cdb[1] |= 0x1; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < REASSIGN_BLKS_CMDLEN; ++k) + pr2ws("%02x ", reass_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, reass_cdb, sizeof(reass_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0 + * when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Persistent reservation in"; + int res, k, ret, sense_cat; + unsigned char prin_cdb[PERSISTENT_RESERVE_IN_CMDLEN] = + {PERSISTENT_RESERVE_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (rq_servact > 0) + prin_cdb[1] = (unsigned char)(rq_servact & 0x1f); + sg_put_unaligned_be16((uint16_t)mx_resp_len, prin_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PERSISTENT_RESERVE_IN_CMDLEN; ++k) + pr2ws("%02x ", prin_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, prin_cdb, sizeof(prin_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0 + * when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope, + unsigned int rq_type, void * paramp, + int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Persistent reservation out"; + int res, k, ret, sense_cat; + unsigned char prout_cdb[PERSISTENT_RESERVE_OUT_CMDLEN] = + {PERSISTENT_RESERVE_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (rq_servact > 0) + prout_cdb[1] = (unsigned char)(rq_servact & 0x1f); + prout_cdb[2] = (((rq_scope & 0xf) << 4) | (rq_type & 0xf)); + sg_put_unaligned_be16((uint16_t)param_len, prout_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PERSISTENT_RESERVE_OUT_CMDLEN; ++k) + pr2ws("%02x ", prout_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + pr2ws(" %s parameters:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, 0); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, prout_cdb, sizeof(prout_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +static bool +has_blk_ili(unsigned char * sensep, int sb_len) +{ + int resp_code; + const unsigned char * cup; + + if (sb_len < 8) + return false; + resp_code = (0x7f & sensep[0]); + if (resp_code >= 0x72) { /* descriptor format */ + /* find block command descriptor */ + if ((cup = sg_scsi_sense_desc_find(sensep, sb_len, 0x5))) + return (cup[3] & 0x20); + } else /* fixed */ + return (sensep[2] & 0x20); + return false; +} + +/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "read long(10)"; + int k, res, sense_cat, ret; + unsigned char readLong_cdb[READ_LONG10_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(readLong_cdb, 0, READ_LONG10_CMDLEN); + readLong_cdb[0] = READ_LONG10_CMD; + if (pblock) + readLong_cdb[1] |= 0x4; + if (correct) + readLong_cdb[1] |= 0x2; + + sg_put_unaligned_be32((uint32_t)lba, readLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_LONG10_CMDLEN; ++k) + pr2ws("%02x ", readLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, valid, ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "read long(16)"; + int k, res, sense_cat, ret; + unsigned char readLong_cdb[SERVICE_ACTION_IN_16_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(readLong_cdb, 0, sizeof(readLong_cdb)); + readLong_cdb[0] = SERVICE_ACTION_IN_16_CMD; + readLong_cdb[1] = READ_LONG_16_SA; + if (pblock) + readLong_cdb[14] |= 0x2; + if (correct) + readLong_cdb[14] |= 0x1; + + sg_put_unaligned_be64(llba, readLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 12); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", readLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + unsigned int lba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write long(10)"; + int k, res, sense_cat, ret; + unsigned char writeLong_cdb[WRITE_LONG10_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(writeLong_cdb, 0, WRITE_LONG10_CMDLEN); + writeLong_cdb[0] = WRITE_LONG10_CMD; + if (cor_dis) + writeLong_cdb[1] |= 0x80; + if (wr_uncor) + writeLong_cdb[1] |= 0x40; + if (pblock) + writeLong_cdb[1] |= 0x20; + + sg_put_unaligned_be32((uint32_t)lba, writeLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < (int)sizeof(writeLong_cdb); ++k) + pr2ws("%02x ", writeLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ; + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_ILLEGAL_REQ: + { + int valid, slen, ili; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + uint64_t llba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write long(16)"; + int k, res, sense_cat, ret; + unsigned char writeLong_cdb[SERVICE_ACTION_OUT_16_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(writeLong_cdb, 0, sizeof(writeLong_cdb)); + writeLong_cdb[0] = SERVICE_ACTION_OUT_16_CMD; + writeLong_cdb[1] = WRITE_LONG_16_SA; + if (cor_dis) + writeLong_cdb[1] |= 0x80; + if (wr_uncor) + writeLong_cdb[1] |= 0x40; + if (pblock) + writeLong_cdb[1] |= 0x20; + + sg_put_unaligned_be64(llba, writeLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 12); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_OUT_16_CMDLEN; ++k) + pr2ws("%02x ", writeLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI VERIFY (10) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, * various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytchk, + unsigned int lba, int veri_len, void * data_out, + int data_out_len, unsigned int * infop, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "verify(10)"; + int k, res, ret, sense_cat, slen; + unsigned char v_cdb[VERIFY10_CMDLEN] = + {VERIFY10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */ + v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ; + if (dpo) + v_cdb[1] |= 0x10; + sg_put_unaligned_be32((uint32_t)lba, v_cdb + 2); + sg_put_unaligned_be16((uint16_t)veri_len, v_cdb + 7); + if (verbose > 1) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < VERIFY10_CMDLEN; ++k) + pr2ws("%02x ", v_cdb[k]); + pr2ws("\n"); + if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) { + k = data_out_len > 4104 ? 4104 : data_out_len; + pr2ws(" data_out buffer%s\n", + (data_out_len > 4104 ? ", first 4104 bytes" : "")); + hex2stderr((const uint8_t *)data_out, k, verbose < 5); + } + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + if (data_out_len > 0) + set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, data_out_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_MEDIUM_HARD: + { + bool valid; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + if (valid) { + if (infop) + *infop = (unsigned int)ull; + ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO; + } else + ret = SG_LIB_CAT_MEDIUM_HARD; + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI VERIFY (16) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytchk, uint64_t llba, + int veri_len, int group_num, void * data_out, + int data_out_len, uint64_t * infop, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "verify(16)"; + int k, res, ret, sense_cat, slen; + unsigned char v_cdb[VERIFY16_CMDLEN] = + {VERIFY16_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */ + v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ; + if (dpo) + v_cdb[1] |= 0x10; + sg_put_unaligned_be64(llba, v_cdb + 2); + sg_put_unaligned_be32((uint32_t)veri_len, v_cdb + 10); + v_cdb[14] = group_num & 0x1f; + if (verbose > 1) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < VERIFY16_CMDLEN; ++k) + pr2ws("%02x ", v_cdb[k]); + pr2ws("\n"); + if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) { + k = data_out_len > 4104 ? 4104 : data_out_len; + pr2ws(" data_out buffer%s\n", + (data_out_len > 4104 ? ", first 4104 bytes" : "")); + hex2stderr((const uint8_t *)data_out, k, verbose < 5); + } + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + if (data_out_len > 0) + set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, data_out_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_MEDIUM_HARD: + { + bool valid; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + if (valid) { + if (infop) + *infop = ull; + ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO; + } else + ret = SG_LIB_CAT_MEDIUM_HARD; + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is + * selected by the cdb_len argument that can take values of 12, 16 or 32 + * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9 + * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from + * the control byte, the rest is copied into an internal cdb which is then + * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15 + * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the + * timeout is set to 60 seconds. For data in or out transfers set dinp or + * doutp, and dlen to the number of bytes to transfer. If dlen is zero then + * no data transfer is assumed. If sense buffer obtained then it is written + * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is + * obtained then written to ata_return_dp, else ata_return_dp[0] is set to + * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers. + * Returns SCSI status value (>= 0) or -1 if other error. Users are + * expected to check the sense buffer themselves. If available the data in + * resid is written to residp. Note in SAT-2 and later, fixed format sense + * data may be placed in *sensep in which case sensep[0]==0x70, prior to + * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72). + */ +int +sg_ll_ata_pt(int sg_fd, const unsigned char * cdbp, int cdb_len, + int timeout_secs, void * dinp, void * doutp, int dlen, + unsigned char * sensep, int max_sense_len, + unsigned char * ata_return_dp, int max_ata_return_len, + int * residp, int verbose) +{ + int k, res, slen, duration; + int ret = -1; + unsigned char apt_cdb[ATA_PT_32_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + unsigned char * sp; + const unsigned char * bp; + struct sg_pt_base * ptvp; + const char * cnamep; + char b[256]; + + memset(apt_cdb, 0, sizeof(apt_cdb)); + b[0] = '\0'; + switch (cdb_len) { + case 12: + cnamep = "ATA pass-through(12)"; + apt_cdb[0] = ATA_PT_12_CMD; + memcpy(apt_cdb + 1, cdbp + 1, 10); + /* control byte at cdb[11] left at zero */ + break; + case 16: + cnamep = "ATA pass-through(16)"; + apt_cdb[0] = ATA_PT_16_CMD; + memcpy(apt_cdb + 1, cdbp + 1, 14); + /* control byte at cdb[15] left at zero */ + break; + case 32: + cnamep = "ATA pass-through(32)"; + apt_cdb[0] = SG_VARIABLE_LENGTH_CMD; + /* control byte at cdb[1] left at zero */ + apt_cdb[7] = 0x18; /* length starting at next byte */ + sg_put_unaligned_be16(ATA_PT_32_SA, apt_cdb + 8); + memcpy(apt_cdb + 10, cdbp + 10, 32 - 10); + break; + default: + pr2ws("cdb_len must be 12, 16 or 32\n"); + return -1; + } + if (NULL == cdbp) { + if (verbose) + pr2ws("%s NULL cdb pointer\n", cnamep); + return -1; + } + if (sensep && (max_sense_len >= (int)sizeof(sense_b))) { + sp = sensep; + slen = max_sense_len; + } else { + sp = sense_b; + slen = sizeof(sense_b); + } + if (verbose) { + pr2ws(" %s cdb: ", cnamep); + if (cdb_len < 32) { + for (k = 0; k < cdb_len; ++k) + pr2ws("%02x ", apt_cdb[k]); + pr2ws("\n"); + } else { + pr2ws("\n"); + hex2stderr(apt_cdb, cdb_len, -1); + } + } + if (NULL == ((ptvp = create_pt_obj(cnamep)))) + return -1; + set_scsi_pt_cdb(ptvp, apt_cdb, cdb_len); + set_scsi_pt_sense(ptvp, sp, slen); + if (dlen > 0) { + if (dinp) + set_scsi_pt_data_in(ptvp, (unsigned char *)dinp, dlen); + else if (doutp) + set_scsi_pt_data_out(ptvp, (unsigned char *)doutp, dlen); + } + res = do_scsi_pt(ptvp, sg_fd, + ((timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT), + verbose); + if (SCSI_PT_DO_BAD_PARAMS == res) { + if (verbose) + pr2ws("%s: bad parameters\n", cnamep); + goto out; + } else if (SCSI_PT_DO_TIMEOUT == res) { + if (verbose) + pr2ws("%s: timeout\n", cnamep); + goto out; + } else if (res > 2) { + if (verbose) + pr2ws("%s: do_scsi_pt: errno=%d\n", cnamep, -res); + } + + if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + pr2ws(" duration=%d ms\n", duration); + + switch (get_scsi_pt_result_category(ptvp)) { + case SCSI_PT_RESULT_GOOD: + if ((sensep) && (max_sense_len > 0)) + *sensep = 0; + if ((ata_return_dp) && (max_ata_return_len > 0)) + *ata_return_dp = 0; + if (residp && (dlen > 0)) + *residp = get_scsi_pt_resid(ptvp); + ret = 0; + break; + case SCSI_PT_RESULT_STATUS: /* other than GOOD + CHECK CONDITION */ + if ((sensep) && (max_sense_len > 0)) + *sensep = 0; + if ((ata_return_dp) && (max_ata_return_len > 0)) + *ata_return_dp = 0; + ret = get_scsi_pt_status_response(ptvp); + break; + case SCSI_PT_RESULT_SENSE: + if (sensep && (sp != sensep)) { + k = get_scsi_pt_sense_len(ptvp); + k = (k > max_sense_len) ? max_sense_len : k; + memcpy(sensep, sp, k); + } + if (ata_return_dp && (max_ata_return_len > 0)) { + /* search for ATA return descriptor */ + bp = sg_scsi_sense_desc_find(sp, slen, 0x9); + if (bp) { + k = bp[1] + 2; + k = (k > max_ata_return_len) ? max_ata_return_len : k; + memcpy(ata_return_dp, bp, k); + } else + ata_return_dp[0] = 0x0; + } + if (residp && (dlen > 0)) + *residp = get_scsi_pt_resid(ptvp); + ret = get_scsi_pt_status_response(ptvp); + break; + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose) + pr2ws("%s: transport error: %s\n", cnamep, + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b)); + break; + case SCSI_PT_RESULT_OS_ERR: + if (verbose) + pr2ws("%s: os error: %s\n", cnamep, + get_scsi_pt_os_err_str(ptvp, sizeof(b) , b)); + break; + default: + if (verbose) + pr2ws("%s: unknown pt_result_category=%d\n", cnamep, + get_scsi_pt_result_category(ptvp)); + break; + } + +out: + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ BUFFER(10) command (SPC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read buffer(10)"; + int res, k, ret, sense_cat; + unsigned char rbuf_cdb[READ_BUFFER_CMDLEN] = + {READ_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + rbuf_cdb[1] = (unsigned char)(mode & 0x1f); + rbuf_cdb[2] = (unsigned char)(buffer_id & 0xff); + sg_put_unaligned_be24((uint32_t)buffer_offset, rbuf_cdb + 3); + sg_put_unaligned_be24((uint32_t)mx_resp_len, rbuf_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", rbuf_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rbuf_cdb, sizeof(rbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * paramp, int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write buffer"; + int k, res, ret, sense_cat; + unsigned char wbuf_cdb[WRITE_BUFFER_CMDLEN] = + {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + wbuf_cdb[1] = (unsigned char)(mode & 0x1f); + wbuf_cdb[2] = (unsigned char)(buffer_id & 0xff); + sg_put_unaligned_be24((uint32_t)buffer_offset, wbuf_cdb + 3); + sg_put_unaligned_be24((uint32_t)param_len, wbuf_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", wbuf_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list", cdb_name_s); + if (2 == verbose) { + pr2ws("%s:\n", (param_len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)paramp, + (param_len > 256 ? 256 : param_len), -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)paramp, param_len, 0); + } + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Adds mode specific field (spc4r32) and timeout + * to command abort to override default of 60 seconds. If timeout_secs is + * 0 or less then the default timeout is used instead. */ +int +sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id, + uint32_t buffer_offset, void * paramp, + uint32_t param_len, int timeout_secs, bool noisy, + int verbose) +{ + int k, res, ret, sense_cat; + uint8_t wbuf_cdb[WRITE_BUFFER_CMDLEN] = + {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (buffer_offset > 0xffffff) { + pr2ws("%s: buffer_offset value too large for 24 bits\n", __func__); + return -1; + } + if (param_len > 0xffffff) { + pr2ws("%s: param_len value too large for 24 bits\n", __func__); + return -1; + } + wbuf_cdb[1] = (uint8_t)(mode & 0x1f); + wbuf_cdb[1] |= (uint8_t)((m_specific & 0x7) << 5); + wbuf_cdb[2] = (uint8_t)(buffer_id & 0xff); + sg_put_unaligned_be24(buffer_offset, wbuf_cdb + 3); + sg_put_unaligned_be24(param_len, wbuf_cdb + 6); + if (verbose) { + pr2ws(" Write buffer cdb: "); + for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", wbuf_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" Write buffer parameter list%s:\n", + ((param_len > 256) ? " (first 256 bytes)" : "")); + hex2stderr((const uint8_t *)paramp, + ((param_len > 256) ? 256 : param_len), -1); + } + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) { + pr2ws("%s: out of memory\n", __func__); + return -1; + } + set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, "Write buffer", res, SG_NO_DATA_IN, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI UNMAP command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose) +{ + return sg_ll_unmap_v2(sg_fd, false, group_num, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field + * (sbc3r22). Otherwise same as sg_ll_unmap() . */ +int +sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "unmap"; + int k, res, ret, sense_cat, tmout; + unsigned char u_cdb[UNMAP_CMDLEN] = + {UNMAP_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (anchor) + u_cdb[1] |= 0x1; + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + u_cdb[6] = group_num & 0x1f; + sg_put_unaligned_be16((uint16_t)param_len, u_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < UNMAP_CMDLEN; ++k) + pr2ws("%02x ", u_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, u_cdb, sizeof(u_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read block limits"; + int k, ret, res, sense_cat; + unsigned char rl_cdb[READ_BLOCK_LIMITS_CMDLEN] = + {READ_BLOCK_LIMITS_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_BLOCK_LIMITS_CMDLEN; ++k) + pr2ws("%02x ", rl_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI RECEIVE COPY RESULTS command. Actually cover all current + * uses of opcode 0x84 (Third-party copy IN). Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + int k, res, ret, sense_cat; + unsigned char rcvcopyres_cdb[THIRD_PARTY_COPY_IN_CMDLEN] = + {THIRD_PARTY_COPY_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + char b[64]; + + sg_get_opcode_sa_name(THIRD_PARTY_COPY_IN_CMD, sa, 0, (int)sizeof(b), b); + rcvcopyres_cdb[1] = (unsigned char)(sa & 0x1f); + if (sa <= 4) /* LID1 variants */ + rcvcopyres_cdb[2] = (unsigned char)(list_id); + else if ((sa >= 5) && (sa <= 7)) /* LID4 variants */ + sg_put_unaligned_be32((uint32_t)list_id, rcvcopyres_cdb + 2); + sg_put_unaligned_be32((uint32_t)mx_resp_len, rcvcopyres_cdb + 10); + + if (verbose) { + pr2ws(" %s cdb: ", b); + for (k = 0; k < THIRD_PARTY_COPY_IN_CMDLEN; ++k) + pr2ws("%02x ", rcvcopyres_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(b)))) + return -1; + set_scsi_pt_cdb(ptvp, rcvcopyres_cdb, sizeof(rcvcopyres_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, b, res, mx_resp_len, sense_b, noisy, + verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + + +/* SPC-4 rev 35 and later calls this opcode (0x83) "Third-party copy OUT" + * The original EXTENDED COPY command (now called EXTENDED COPY (LID1)) + * is the only one supported by sg_ll_extended_copy(). See function + * sg_ll_3party_copy_out() for the other service actions ( > 0 ). */ + +/* Invokes a SCSI EXTENDED COPY (LID1) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose) +{ + int k, res, ret, sense_cat; + unsigned char xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] = + {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + const char * opcode_name = "Extended copy (LID1)"; + + xcopy_cdb[1] = (unsigned char)(EXTENDED_COPY_LID1_SA & 0x1f); + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + + if (verbose) { + pr2ws(" %s cdb: ", opcode_name); + for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k) + pr2ws("%02x ", xcopy_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", opcode_name); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(opcode_name)))) + return -1; + set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, opcode_name, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Handles various service actions associated with opcode 0x83 which is + * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID1 and + * LID4), POPULATE TOKEN and WRITE USING TOKEN commands. + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, int group_num, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose) +{ + int k, res, ret, sense_cat, tmout; + unsigned char xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] = + {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + char cname[80]; + + sg_get_opcode_sa_name(THIRD_PARTY_COPY_OUT_CMD, sa, 0, sizeof(cname), + cname); + xcopy_cdb[1] = (unsigned char)(sa & 0x1f); + switch (sa) { + case 0x0: /* XCOPY(LID1) */ + case 0x1: /* XCOPY(LID4) */ + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + break; + case 0x10: /* POPULATE TOKEN (SBC-3) */ + case 0x11: /* WRITE USING TOKEN (SBC-3) */ + sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 6); + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + xcopy_cdb[14] = (unsigned char)(group_num & 0x1f); + break; + case 0x1c: /* COPY OPERATION ABORT */ + sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 2); + break; + default: + pr2ws("%s: unknown service action 0x%x\n", __func__, sa); + return -1; + } + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + + if (verbose) { + pr2ws(" %s cdb: ", cname); + for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k) + pr2ws("%02x ", xcopy_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cname); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cname)))) + return -1; + set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cname, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC). + * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_* + * positive values or -1 -> other errors. Note that CONDITION MET status + * is returned when immed=true and num_blocks can fit in device's cache, + * somewaht strangely, GOOD status (return 0) is returned if num_blocks + * cannot fit in device's cache. If do_seek10==true then does a SEEK(10) + * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10) + * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then + * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */ +int +sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed, + uint64_t lba, uint32_t num_blocks, int group_num, + int timeout_secs, bool noisy, int verbose) +{ + static const char * const cdb10_name_s = "Pre-fetch(10)"; + static const char * const cdb16_name_s = "Pre-fetch(16)"; + static const char * const cdb_seek_name_s = "Seek(10)"; + int k, res, sense_cat, ret, cdb_len, tmout; + const char *cdb_name_s; + unsigned char preFetchCdb[PRE_FETCH16_CMDLEN]; /* all use longest cdb */ + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(preFetchCdb, 0, sizeof(preFetchCdb)); + if (do_seek10) { + if (lba > UINT32_MAX) { + if (verbose) + pr2ws("%s: LBA exceeds 2**32 in %s\n", __func__, + cdb_seek_name_s); + return -1; + } + preFetchCdb[0] = SEEK10_CMD; + cdb_len = SEEK10_CMDLEN; + cdb_name_s = cdb_seek_name_s; + sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2); + } else { + if ((! cdb16) && + ((lba > UINT32_MAX) || (num_blocks > UINT16_MAX))) { + cdb16 = true; + if (noisy || verbose) + pr2ws("%s: do %s due to %s size\n", __func__, cdb16_name_s, + (lba > UINT32_MAX) ? "LBA" : "NUM_BLOCKS"); + } + if (cdb16) { + preFetchCdb[0] = PRE_FETCH16_CMD; + cdb_len = PRE_FETCH16_CMDLEN; + cdb_name_s = cdb16_name_s; + if (immed) + preFetchCdb[1] = 0x2; + sg_put_unaligned_be64(lba, preFetchCdb + 2); + sg_put_unaligned_be32(num_blocks, preFetchCdb + 10); + preFetchCdb[14] = 0x3f & group_num; + } else { + preFetchCdb[0] = PRE_FETCH10_CMD; + cdb_len = PRE_FETCH10_CMDLEN; + cdb_name_s = cdb10_name_s; + if (immed) + preFetchCdb[1] = 0x2; + sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2); + preFetchCdb[6] = 0x3f & group_num; + sg_put_unaligned_be16((uint16_t)num_blocks, preFetchCdb + 7); + } + } + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < cdb_len; ++k) + pr2ws("%02x ", preFetchCdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, preFetchCdb, cdb_len); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + if (0 == res) { + int sstat = get_scsi_pt_status_response(ptvp); + + if (SG_LIB_CAT_CONDITION_MET == sstat) { + ret = SG_LIB_CAT_CONDITION_MET; + if (verbose > 2) + pr2ws("%s: returns SG_LIB_CAT_CONDITION_MET\n", __func__); + goto fini; + } + } + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; +fini: + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/tools/sg_write_buffer/sg_cmds_mmc.c b/tools/sg_write_buffer/sg_cmds_mmc.c new file mode 100644 index 0000000..18f6ae1 --- /dev/null +++ b/tools/sg_write_buffer/sg_cmds_mmc.c @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2008-2018 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_mmc.h" +#include "sg_pt.h" +#include "sg_unaligned.h" + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ + +#define GET_CONFIG_CMD 0x46 +#define GET_CONFIG_CMD_LEN 10 +#define GET_PERFORMANCE_CMD 0xac +#define GET_PERFORMANCE_CMD_LEN 12 +#define SET_CD_SPEED_CMD 0xbb +#define SET_CD_SPEED_CMDLEN 12 +#define SET_STREAMING_CMD 0xb6 +#define SET_STREAMING_CMDLEN 12 + +#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; +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +/* Invokes a SCSI SET CD SPEED command (MMC). + * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int +sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed, + int drv_write_speed, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "set cd speed"; + int res, ret, k, sense_cat; + unsigned char scsCmdBlk[SET_CD_SPEED_CMDLEN] = {SET_CD_SPEED_CMD, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + scsCmdBlk[1] |= (rot_control & 0x3); + sg_put_unaligned_be16((uint16_t)drv_read_speed, scsCmdBlk + 2); + sg_put_unaligned_be16((uint16_t)drv_write_speed, scsCmdBlk + 4); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SET_CD_SPEED_CMDLEN; ++k) + pr2ws("%02x ", scsCmdBlk[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, scsCmdBlk, sizeof(scsCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI GET CONFIGURATION command (MMC-3,4,5). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int +sg_ll_get_config(int sg_fd, int rt, int starting, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "get configuration"; + int res, k, ret, sense_cat; + unsigned char gcCmdBlk[GET_CONFIG_CMD_LEN] = {GET_CONFIG_CMD, 0, 0, 0, + 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((rt < 0) || (rt > 3)) { + pr2ws("Bad rt value: %d\n", rt); + return -1; + } + gcCmdBlk[1] = (rt & 0x3); + if ((starting < 0) || (starting > 0xffff)) { + pr2ws("Bad starting field number: 0x%x\n", starting); + return -1; + } + sg_put_unaligned_be16((uint16_t)starting, gcCmdBlk + 2); + if ((mx_resp_len < 0) || (mx_resp_len > 0xffff)) { + pr2ws("Bad mx_resp_len: 0x%x\n", starting); + return -1; + } + sg_put_unaligned_be16((uint16_t)mx_resp_len, gcCmdBlk + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GET_CONFIG_CMD_LEN; ++k) + pr2ws("%02x ", gcCmdBlk[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, gcCmdBlk, sizeof(gcCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else { + if ((verbose > 2) && (ret > 3)) { + unsigned char * bp; + int len; + + bp = (unsigned char *)resp; + len = sg_get_unaligned_be32(bp + 0); + if (len < 0) + len = 0; + len = (ret < len) ? ret : len; + pr2ws(" %s: response:\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, len, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int +sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba, + int max_num_desc, int ttype, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "get performance"; + int res, k, ret, sense_cat; + unsigned char gpCmdBlk[GET_PERFORMANCE_CMD_LEN] = {GET_PERFORMANCE_CMD, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((data_type < 0) || (data_type > 0x1f)) { + pr2ws("Bad data_type value: %d\n", data_type); + return -1; + } + gpCmdBlk[1] = (data_type & 0x1f); + sg_put_unaligned_be32((uint32_t)starting_lba, gpCmdBlk + 2); + if ((max_num_desc < 0) || (max_num_desc > 0xffff)) { + pr2ws("Bad max_num_desc: 0x%x\n", max_num_desc); + return -1; + } + sg_put_unaligned_be16((uint16_t)max_num_desc, gpCmdBlk + 8); + if ((ttype < 0) || (ttype > 0xff)) { + pr2ws("Bad type: 0x%x\n", ttype); + return -1; + } + gpCmdBlk[10] = (unsigned char)ttype; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GET_PERFORMANCE_CMD_LEN; ++k) + pr2ws("%02x ", gpCmdBlk[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, gpCmdBlk, sizeof(gpCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else { + if ((verbose > 2) && (ret > 3)) { + unsigned char * bp; + int len; + + bp = (unsigned char *)resp; + len = sg_get_unaligned_be32(bp + 0); + if (len < 0) + len = 0; + len = (ret < len) ? ret : len; + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, len, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int +sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "set streaming"; + int k, res, ret, sense_cat; + unsigned char ssCmdBlk[SET_STREAMING_CMDLEN] = + {SET_STREAMING_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + ssCmdBlk[8] = type; + sg_put_unaligned_be16((uint16_t)param_len, ssCmdBlk + 9); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SET_STREAMING_CMDLEN; ++k) + pr2ws("%02x ", ssCmdBlk[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, ssCmdBlk, sizeof(ssCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/tools/sg_write_buffer/sg_io_linux.c b/tools/sg_write_buffer/sg_io_linux.c new file mode 100644 index 0000000..f9f08e7 --- /dev/null +++ b/tools/sg_write_buffer/sg_io_linux.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef SG_LIB_LINUX + +#include "sg_io_linux.h" + + +/* Version 1.07 20160405 */ + +#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; +} + + +void +sg_print_masked_status(int masked_status) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + sg_print_scsi_status(scsi_status); +} + +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", "DID_TRANSPORT_DISRUPTED", + "DID_TRANSPORT_FAILFAST", "DID_TARGET_FAILURE", "DID_NEXUS_FAILURE", + "DID_ALLOC_FAILURE", "DID_MEDIUM_ERROR", +}; + +#define LINUX_HOST_BYTES_SZ \ + (int)(sizeof(linux_host_bytes) / sizeof(linux_host_bytes[0])) + +void +sg_print_host_status(int host_status) +{ + pr2ws("Host_status=0x%02x ", host_status); + if ((host_status < 0) || (host_status >= LINUX_HOST_BYTES_SZ)) + pr2ws("is invalid "); + else + pr2ws("[%s] ", linux_host_bytes[host_status]); +} + +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 + + +void +sg_print_driver_status(int driver_status) +{ + int driv; + const char * driv_cp = "invalid"; + + driv = driver_status & SG_LIB_DRIVER_MASK; + if (driv < LINUX_DRIVER_BYTES_SZ) + driv_cp = linux_driver_bytes[driv]; +#if 0 + sugg = (driver_status & SG_LIB_SUGGEST_MASK) >> 4; + if (sugg < LINUX_DRIVER_SUGGESTS_SZ) + sugg_cp = linux_driver_suggests[sugg]; +#endif + pr2ws("Driver_status=0x%02x", driver_status); + pr2ws(" [%s] ", driv_cp); +} + +/* Returns 1 if no errors found and thus nothing printed; otherwise + prints error/warning (prefix by 'leadin') and returns 0. */ +static int +sg_linux_sense_print(const char * leadin, int scsi_status, int host_status, + int driver_status, const unsigned char * sense_buffer, + int sb_len, bool raw_sinfo) +{ + bool done_leadin = false; + bool done_sense = false; + + scsi_status &= 0x7e; /*sanity */ + if ((0 == scsi_status) && (0 == host_status) && (0 == driver_status)) + return 1; /* No problems */ + if (0 != scsi_status) { + if (leadin) + pr2ws("%s: ", leadin); + done_leadin = true; + pr2ws("SCSI status: "); + sg_print_scsi_status(scsi_status); + pr2ws("\n"); + if (sense_buffer && ((scsi_status == SAM_STAT_CHECK_CONDITION) || + (scsi_status == SAM_STAT_COMMAND_TERMINATED))) { + /* SAM_STAT_COMMAND_TERMINATED is obsolete */ + sg_print_sense(0, sense_buffer, sb_len, raw_sinfo); + done_sense = true; + } + } + if (0 != host_status) { + if (leadin && (! done_leadin)) + pr2ws("%s: ", leadin); + if (done_leadin) + pr2ws("plus...: "); + else + done_leadin = true; + sg_print_host_status(host_status); + pr2ws("\n"); + } + if (0 != driver_status) { + if (done_sense && + (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status))) + return 0; + if (leadin && (! done_leadin)) + pr2ws("%s: ", leadin); + if (done_leadin) + pr2ws("plus...: "); + sg_print_driver_status(driver_status); + pr2ws("\n"); + if (sense_buffer && (! done_sense) && + (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status))) + sg_print_sense(0, sense_buffer, sb_len, raw_sinfo); + } + return 0; +} + +#ifdef SG_IO + +bool +sg_normalize_sense(const struct sg_io_hdr * hp, + struct sg_scsi_sense_hdr * sshp) +{ + if ((NULL == hp) || (0 == hp->sb_len_wr)) { + if (sshp) + memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr)); + return 0; + } + return sg_scsi_normalize_sense(hp->sbp, hp->sb_len_wr, sshp); +} + +/* Returns 1 if no errors found and thus nothing printed; otherwise + returns 0. */ +int +sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp, + bool raw_sinfo) +{ + return sg_linux_sense_print(leadin, hp->status, hp->host_status, + hp->driver_status, hp->sbp, hp->sb_len_wr, + raw_sinfo); +} +#endif + +/* Returns 1 if no errors found and thus nothing printed; otherwise + returns 0. */ +int +sg_chk_n_print(const char * leadin, int masked_status, int host_status, + int driver_status, const unsigned char * sense_buffer, + int sb_len, bool raw_sinfo) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + return sg_linux_sense_print(leadin, scsi_status, host_status, + driver_status, sense_buffer, sb_len, + raw_sinfo); +} + +#ifdef SG_IO +int +sg_err_category3(struct sg_io_hdr * hp) +{ + return sg_err_category_new(hp->status, hp->host_status, + hp->driver_status, hp->sbp, hp->sb_len_wr); +} +#endif + +int +sg_err_category(int masked_status, int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + return sg_err_category_new(scsi_status, host_status, driver_status, + sense_buffer, sb_len); +} + +int +sg_err_category_new(int scsi_status, int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len) +{ + int masked_driver_status = (SG_LIB_DRIVER_MASK & driver_status); + + scsi_status &= 0x7e; + if ((0 == scsi_status) && (0 == host_status) && + (0 == masked_driver_status)) + return SG_LIB_CAT_CLEAN; + if ((SAM_STAT_CHECK_CONDITION == scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == scsi_status) || + (SG_LIB_DRIVER_SENSE == masked_driver_status)) + return sg_err_category_sense(sense_buffer, sb_len); + if (0 != host_status) { + if ((SG_LIB_DID_NO_CONNECT == host_status) || + (SG_LIB_DID_BUS_BUSY == host_status) || + (SG_LIB_DID_TIME_OUT == host_status)) + return SG_LIB_CAT_TIMEOUT; + if (SG_LIB_DID_NEXUS_FAILURE == host_status) + return SG_LIB_CAT_RES_CONFLICT; + } + if (SG_LIB_DRIVER_TIMEOUT == masked_driver_status) + return SG_LIB_CAT_TIMEOUT; + return SG_LIB_CAT_OTHER; +} + +#endif /* if SG_LIB_LINUX defined */ diff --git a/tools/sg_write_buffer/sg_lib.c b/tools/sg_write_buffer/sg_lib.c new file mode 100644 index 0000000..53a17d4 --- /dev/null +++ b/tools/sg_write_buffer/sg_lib.c @@ -0,0 +1,3494 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +/* NOTICE: + * On 5th October 2004 (v1.00) this file name was changed from sg_err.c + * to sg_lib.c and the previous GPL was changed to a FreeBSD license. + * The intention is to maintain this file and the related sg_lib.h file + * as open source and encourage their unencumbered use. + * + * CONTRIBUTIONS: + * This file started out as a copy of SCSI opcodes, sense keys and + * additional sense codes (ASC/ASCQ) kept in the Linux SCSI subsystem + * in the kernel source file: drivers/scsi/constant.c . That file + * bore this notice: "Copyright (C) 1993, 1994, 1995 Eric Youngdale" + * and a GPL notice. + * + * Much of the data in this file is derived from SCSI draft standards + * found at http://www.t10.org with the "SCSI Primary Commands-4" (SPC-4) + * being the central point of reference. + * + * Contributions: + * sense key specific field decoding [Trent Piepho 20031116] + * + */ + +#define _POSIX_C_SOURCE 200809L /* for posix_memalign() */ +#define __STDC_FORMAT_MACROS 1 +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <inttypes.h> +#include <errno.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +/* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */ + +#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */ + +FILE * sg_warnings_strm = NULL; /* would like to default to stderr */ + +#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; +} + +#if defined(__GNUC__) || defined(__clang__) +static int scnpr(char * cp, int cp_max_len, const char * fmt, ...) + __attribute__ ((format (printf, 3, 4))); +#else +static int scnpr(char * cp, int cp_max_len, const char * fmt, ...); +#endif + +/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of + * functions. Returns number of chars placed in cp excluding the + * trailing null char. So for cp_max_len > 0 the return value is always + * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are + * written to cp. Note this means that when cp_max_len = 1, this function + * assumes that cp[0] is the null character and does nothing (and returns + * 0). Linux kernel has a similar function called scnprintf(). */ +static int +scnpr(char * cp, int cp_max_len, const char * fmt, ...) +{ + va_list args; + int n; + + if (cp_max_len < 2) + return 0; + va_start(args, fmt); + n = vsnprintf(cp, cp_max_len, fmt, args); + va_end(args); + return (n < cp_max_len) ? n : (cp_max_len - 1); +} + +/* Simple ASCII printable (does not use locale), includes space and excludes + * DEL (0x7f). */ +static inline int my_isprint(int ch) +{ + return ((ch >= ' ') && (ch < 0x7f)); +} + +/* Searches 'arr' for match on 'value' then 'peri_type'. If matches + 'value' but not 'peri_type' then yields first 'value' match entry. + Last element of 'arr' has NULL 'name'. If no match returns NULL. */ +static const struct sg_lib_value_name_t * +get_value_name(const struct sg_lib_value_name_t * arr, int value, + int peri_type) +{ + const struct sg_lib_value_name_t * vp = arr; + const struct sg_lib_value_name_t * holdp; + + if (peri_type < 0) + peri_type = 0; + for (; vp->name; ++vp) { + if (value == vp->value) { + if (peri_type == vp->peri_dev_type) + return vp; + holdp = vp; + while ((vp + 1)->name && (value == (vp + 1)->value)) { + ++vp; + if (peri_type == vp->peri_dev_type) + return vp; + } + return holdp; + } + } + return NULL; +} + +/* If this function is not called, sg_warnings_strm will be NULL and all users + * (mainly fprintf() ) need to check and substitute stderr as required */ +void +sg_set_warnings_strm(FILE * warnings_strm) +{ + sg_warnings_strm = warnings_strm; +} + +#define CMD_NAME_LEN 128 + +void +sg_print_command(const unsigned char * command) +{ + int k, sz; + char buff[CMD_NAME_LEN]; + + sg_get_command_name(command, 0, CMD_NAME_LEN, buff); + buff[CMD_NAME_LEN - 1] = '\0'; + + pr2ws("%s [", buff); + if (SG_VARIABLE_LENGTH_CMD == command[0]) + sz = command[7] + 8; + else + sz = sg_get_command_size(command[0]); + for (k = 0; k < sz; ++k) + pr2ws("%02x ", command[k]); + pr2ws("]\n"); +} + +void +sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff) +{ + const char * ccp = NULL; + bool unknown = false; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + scsi_status &= 0x7e; /* sanitize as much as possible */ + switch (scsi_status) { + case 0: ccp = "Good"; break; + case 0x2: ccp = "Check Condition"; break; + case 0x4: ccp = "Condition Met"; break; + case 0x8: ccp = "Busy"; break; + case 0x10: ccp = "Intermediate (obsolete)"; break; + case 0x14: ccp = "Intermediate-Condition Met (obsolete)"; break; + case 0x18: ccp = "Reservation Conflict"; break; + case 0x22: ccp = "Command Terminated (obsolete)"; break; + case 0x28: ccp = "Task set Full"; break; + case 0x30: ccp = "ACA Active"; break; + case 0x40: ccp = "Task Aborted"; break; + default: + unknown = true; + break; + } + if (unknown) + scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status); + else + scnpr(buff, buff_len, "%s", ccp); +} + +void +sg_print_scsi_status(int scsi_status) +{ + char buff[128]; + + sg_get_scsi_status_str(scsi_status, sizeof(buff) - 1, buff); + buff[sizeof(buff) - 1] = '\0'; + pr2ws("%s ", buff); +} + +/* Get sense key from sense buffer. If successful returns a sense key value + * between 0 and 15. If sense buffer cannot be decode, returns -1 . */ +int +sg_get_sense_key(const unsigned char * sbp, int sb_len) +{ + if ((NULL == sbp) || (sb_len < 2)) + return -1; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + return (sb_len < 3) ? -1 : (sbp[2] & 0xf); + case 0x72: + case 0x73: + return sbp[1] & 0xf; + default: + return -1; + } +} + +/* Yield string associated with sense_key value. Returns 'buff'. */ +char * +sg_get_sense_key_str(int sense_key, int buff_len, char * buff) +{ + if (1 == buff_len) { + buff[0] = '\0'; + return buff; + } + if ((sense_key >= 0) && (sense_key < 16)) + scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]); + else + scnpr(buff, buff_len, "invalid value: 0x%x", sense_key); + return buff; +} + +/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */ +char * +sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff) +{ + int k, num, rlen; + bool found = false; + struct sg_lib_asc_ascq_t * eip; + struct sg_lib_asc_ascq_range_t * ei2p; + + if (1 == buff_len) { + buff[0] = '\0'; + return buff; + } + for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) { + ei2p = &sg_lib_asc_ascq_range[k]; + if ((ei2p->asc == asc) && + (ascq >= ei2p->ascq_min) && + (ascq <= ei2p->ascq_max)) { + found = true; + num = scnpr(buff, buff_len, "Additional sense: "); + rlen = buff_len - num; + scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq); + } + } + if (found) + return buff; + + for (k = 0; sg_lib_asc_ascq[k].text; ++k) { + eip = &sg_lib_asc_ascq[k]; + if (eip->asc == asc && + eip->ascq == ascq) { + found = true; + scnpr(buff, buff_len, "Additional sense: %s", eip->text); + } + } + if (! found) { + if (asc >= 0x80) + scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x " + "(hex)", asc, ascq); + else if (ascq >= 0x80) + scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification " + "ASCQ=%02x (hex)", asc, ascq); + else + scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq); + } + return buff; +} + +/* Attempt to find the first SCSI sense data descriptor that matches the + * given 'desc_type'. If found return pointer to start of sense data + * descriptor; otherwise (including fixed format sense data) returns NULL. */ +const unsigned char * +sg_scsi_sense_desc_find(const unsigned char * sbp, int sb_len, + int desc_type) +{ + int add_sb_len, add_d_len, desc_len, k; + const unsigned char * descp; + + if ((sb_len < 8) || (0 == (add_sb_len = sbp[7]))) + return NULL; + if ((sbp[0] < 0x72) || (sbp[0] > 0x73)) + return NULL; + add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8); + descp = &sbp[8]; + for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) { + descp += desc_len; + add_d_len = (k < (add_sb_len - 1)) ? descp[1]: -1; + desc_len = add_d_len + 2; + if (descp[0] == desc_type) + return descp; + if (add_d_len < 0) /* short descriptor ?? */ + break; + } + return NULL; +} + +/* Returns true if valid bit set, false if valid bit clear. Irrespective the + * information field is written out via 'info_outp' (except when it is + * NULL). Handles both fixed and descriptor sense formats. */ +bool +sg_get_sense_info_fld(const unsigned char * sbp, int sb_len, + uint64_t * info_outp) +{ + const unsigned char * bp; + uint64_t ull; + + if (info_outp) + *info_outp = 0; + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (info_outp) + *info_outp = sg_get_unaligned_be32(sbp + 3); + return !!(sbp[0] & 0x80); + case 0x72: + case 0x73: + bp = sg_scsi_sense_desc_find(sbp, sb_len, 0 /* info desc */); + if (bp && (0xa == bp[1])) { + ull = sg_get_unaligned_be64(bp + 4); + if (info_outp) + *info_outp = ull; + return !!(bp[2] & 0x80); /* since spc3r23 should be set */ + } else + return false; + default: + return false; + } +} + +/* Returns true if fixed format or command specific information descriptor + * is found in the descriptor sense; else false. If available the command + * specific information field (4 byte integer in fixed format, 8 byte + * integer in descriptor format) is written out via 'cmd_spec_outp'. + * Handles both fixed and descriptor sense formats. */ +bool +sg_get_sense_cmd_spec_fld(const unsigned char * sbp, int sb_len, + uint64_t * cmd_spec_outp) +{ + const unsigned char * bp; + + if (cmd_spec_outp) + *cmd_spec_outp = 0; + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (cmd_spec_outp) + *cmd_spec_outp = sg_get_unaligned_be32(sbp + 8); + return true; + case 0x72: + case 0x73: + bp = sg_scsi_sense_desc_find(sbp, sb_len, + 1 /* command specific info desc */); + if (bp && (0xa == bp[1])) { + if (cmd_spec_outp) + *cmd_spec_outp = sg_get_unaligned_be64(bp + 4); + return true; + } else + return false; + default: + return false; + } +} + +/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set. + * In descriptor format if the stream commands descriptor not found + * then returns false. Writes true or false corresponding to these bits to + * the last three arguments if they are non-NULL. */ +bool +sg_get_sense_filemark_eom_ili(const unsigned char * sbp, int sb_len, + bool * filemark_p, bool * eom_p, bool * ili_p) +{ + const unsigned char * bp; + + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (sbp[2] & 0xe0) { + if (filemark_p) + *filemark_p = !!(sbp[2] & 0x80); + if (eom_p) + *eom_p = !!(sbp[2] & 0x40); + if (ili_p) + *ili_p = !!(sbp[2] & 0x20); + return true; + } else + return false; + case 0x72: + case 0x73: + /* Look for stream commands sense data descriptor */ + bp = sg_scsi_sense_desc_find(sbp, sb_len, 4); + if (bp && (bp[1] >= 2)) { + if (bp[3] & 0xe0) { + if (filemark_p) + *filemark_p = !!(bp[3] & 0x80); + if (eom_p) + *eom_p = !!(bp[3] & 0x40); + if (ili_p) + *ili_p = !!(bp[3] & 0x20); + return true; + } + } + return false; + default: + return false; + } +} + +/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also + * returns true if progress indication sense data descriptor found. Places + * progress field from sense data where progress_outp points. If progress + * field is not available returns false and *progress_outp is unaltered. + * Handles both fixed and descriptor sense formats. + * Hint: if true is returned *progress_outp may be multiplied by 100 then + * divided by 65536 to get the percentage completion. */ +bool +sg_get_sense_progress_fld(const unsigned char * sbp, int sb_len, + int * progress_outp) +{ + const unsigned char * bp; + int sk, sk_pr; + + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + sk = (sbp[2] & 0xf); + if ((sb_len < 18) || + ((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk))) + return false; + if (sbp[15] & 0x80) { /* SKSV bit set */ + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(sbp + 16); + return true; + } else + return false; + case 0x72: + case 0x73: + /* sense key specific progress (0x2) or progress descriptor (0xa) */ + sk = (sbp[1] & 0xf); + sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk); + if (sk_pr && ((bp = sg_scsi_sense_desc_find(sbp, sb_len, 2))) && + (0x6 == bp[1]) && (0x80 & bp[4])) { + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(bp + 5); + return true; + } else if (((bp = sg_scsi_sense_desc_find(sbp, sb_len, 0xa))) && + ((0x6 == bp[1]))) { + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(bp + 6); + return true; + } else + return false; + default: + return false; + } +} + +char * +sg_get_pdt_str(int pdt, int buff_len, char * buff) +{ + if ((pdt < 0) || (pdt > 31)) + scnpr(buff, buff_len, "bad pdt"); + else + scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]); + return buff; +} + +int +sg_lib_pdt_decay(int pdt) +{ + if ((pdt < 0) || (pdt > 31)) + return 0; + return sg_lib_pdt_decay_arr[pdt]; +} + +char * +sg_get_trans_proto_str(int tpi, int buff_len, char * buff) +{ + if ((tpi < 0) || (tpi > 15)) + scnpr(buff, buff_len, "bad tpi"); + else + scnpr(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]); + return buff; +} + +#define TRANSPORT_ID_MIN_LEN 24 + +char * +sg_decode_transportid_str(const char * lip, unsigned char * bp, int bplen, + bool only_one, int blen, char * b) +{ + int proto_id, num, k, n, normal_len, tpid_format; + uint64_t ull; + int bump; + + if ((NULL == b) || (blen < 1)) + return b; + else if (1 == blen) { + b[0] = '\0'; + return b; + } + if (NULL == lip) + lip = ""; + bump = TRANSPORT_ID_MIN_LEN; /* should be overwritten in all loop paths */ + for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) { + if ((k > 0) && only_one) + break; + if ((bplen < 24) || (0 != (bplen % 4))) + n += scnpr(b + n, blen - n, "%sTransport Id short or not " + "multiple of 4 [length=%d]:\n", lip, blen); + else + n += scnpr(b + n, blen - n, "%sTransport Id of initiator:\n", + lip); + tpid_format = ((bp[0] >> 6) & 0x3); + proto_id = (bp[0] & 0xf); + normal_len = (bplen > TRANSPORT_ID_MIN_LEN) ? + TRANSPORT_ID_MIN_LEN : bplen; + switch (proto_id) { + case TPROTO_FCP: /* Fibre channel */ + n += scnpr(b + n, blen - n, "%s FCP-2 World Wide Name:\n", lip); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(bp + 8, 8, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SPI: /* Scsi Parallel Interface, obsolete */ + n += scnpr(b + n, blen - n, "%s Parallel SCSI initiator SCSI " + "address: 0x%x\n", lip, sg_get_unaligned_be16(bp + 2)); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += scnpr(b + n, blen - n, "%s relative port number (of " + "corresponding target): 0x%x\n", lip, + sg_get_unaligned_be16(bp + 6)); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SSA: + n += scnpr(b + n, blen - n, "%s SSA (transport id not " + "defined):\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_1394: /* IEEE 1394 */ + n += scnpr(b + n, blen - n, "%s IEEE 1394 EUI-64 name:\n", lip); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(&bp[8], 8, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SRP: /* SCSI over RDMA */ + n += scnpr(b + n, blen - n, "%s RDMA initiator port " + "identifier:\n", lip); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(bp + 8, 16, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ISCSI: + n += scnpr(b + n, blen - n, "%s iSCSI ", lip); + num = sg_get_unaligned_be16(bp + 2); + if (0 == tpid_format) + n += scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]); + else if (1 == tpid_format) + n += scnpr(b + n, blen - n, "world wide unique port id: " + "%.*s\n", num, &bp[4]); + else { + n += scnpr(b + n, blen - n, " [Unexpected TPID format: " + "%d]\n", tpid_format); + n += hex2str(bp, num + 4, lip, 0, blen - n, b + n); + } + bump = (((num + 4) < TRANSPORT_ID_MIN_LEN) ? + TRANSPORT_ID_MIN_LEN : num + 4); + break; + case TPROTO_SAS: + ull = sg_get_unaligned_be64(bp + 4); + n += scnpr(b + n, blen - n, "%s SAS address: 0x%" PRIx64 "\n", + lip, ull); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ADT: /* no TransportID defined by T10 yet */ + n += scnpr(b + n, blen - n, "%s ADT:\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ATA: /* no TransportID defined by T10 yet */ + n += scnpr(b + n, blen - n, "%s ATAPI:\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_UAS: /* no TransportID defined by T10 yet */ + n += scnpr(b + n, blen - n, "%s UAS:\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SOP: + n += scnpr(b + n, blen - n, "%s SOP ", lip); + num = sg_get_unaligned_be16(bp + 2); + if (0 == tpid_format) + n += scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num); + else { + n += scnpr(b + n, blen - n, " [Unexpected TPID format: " + "%d]\n", tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + } + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_PCIE: /* no TransportID defined by T10 yet */ + n += scnpr(b + n, blen - n, "%s PCIE:\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_NONE: /* no TransportID defined by T10 */ + n += scnpr(b + n, blen - n, "%s No specified protocol\n", lip); + /* n += hex2str(bp, ((bplen > 24) ? 24 : bplen), + * lip, 0, blen - n, b + n); */ + bump = TRANSPORT_ID_MIN_LEN; + break; + default: + n += scnpr(b + n, blen - n, "%s unknown protocol id=0x%x " + "TPID format=%d\n", lip, proto_id, tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + } + } + return b; +} + + +static const char * desig_code_set_str_arr[] = +{ + "Reserved [0x0]", + "Binary", + "ASCII", + "UTF-8", + "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]", + "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]", + "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]", +}; + +const char * +sg_get_desig_code_set_str(int val) +{ + if ((val >= 0) && (val < 16)) + return desig_code_set_str_arr[val]; + else + return NULL; +} + +static const char * desig_assoc_str_arr[] = +{ + "Addressed logical unit", + "Target port", /* that received request; unless SCSI ports VPD */ + "Target device that contains addressed lu", + "Reserved [0x3]", +}; + +const char * +sg_get_desig_assoc_str(int val) +{ + if ((val >= 0) && (val < 4)) + return desig_assoc_str_arr[val]; + else + return NULL; +} + +static const char * desig_type_str_arr[] = +{ + "vendor specific [0x0]", + "T10 vendor identification", + "EUI-64 based", + "NAA", + "Relative target port", + "Target port group", /* spc4r09: _primary_ target port group */ + "Logical unit group", + "MD5 logical unit identifier", + "SCSI name string", + "Protocol specific port identifier", /* spc4r36 */ + "UUID identifier", /* spc5r08 */ + "Reserved [0xb]", + "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]", +}; + +const char * +sg_get_desig_type_str(int val) +{ + if ((val >= 0) && (val < 16)) + return desig_type_str_arr[val]; + else + return NULL; +} + +int +sg_get_designation_descriptor_str(const char * lip, const unsigned char * ddp, + int dd_len, bool print_assoc, bool do_long, + int blen, char * b) +{ + int m, p_id, piv, c_set, assoc, desig_type, ci_off, c_id, d_id, naa; + int vsi, k, n, dlen; + const unsigned char * ip; + uint64_t vsei; + uint64_t id_ext; + char e[64]; + const char * cp; + + n = 0; + if (NULL == lip) + lip = ""; + if (dd_len < 4) { + n += scnpr(b + n, blen - n, "%sdesignator desc too short: got " + "length of %d want 4 or more\n", lip, dd_len); + return n; + } + dlen = ddp[3]; + if (dlen > (dd_len - 4)) { + n += scnpr(b + n, blen - n, "%sdesignator too long: says it is %d " + "bytes, but given %d bytes\n", lip, dlen, dd_len - 4); + return n; + } + ip = ddp + 4; + p_id = ((ddp[0] >> 4) & 0xf); + c_set = (ddp[0] & 0xf); + piv = ((ddp[1] & 0x80) ? 1 : 0); + assoc = ((ddp[1] >> 4) & 0x3); + desig_type = (ddp[1] & 0xf); + if (print_assoc && ((cp = sg_get_desig_assoc_str(assoc)))) + n += scnpr(b + n, blen - n, "%s %s:\n", lip, cp); + n += scnpr(b + n, blen - n, "%s designator type: ", lip); + cp = sg_get_desig_type_str(desig_type); + if (cp) + n += scnpr(b + n, blen - n, "%s", cp); + n += scnpr(b + n, blen - n, ", code set: "); + cp = sg_get_desig_code_set_str(c_set); + if (cp) + n += scnpr(b + n, blen - n, "%s", cp); + n += scnpr(b + n, blen - n, "\n"); + if (piv && ((1 == assoc) || (2 == assoc))) + n += scnpr(b + n, blen - n, "%s transport: %s\n", lip, + sg_get_trans_proto_str(p_id, sizeof(e), e)); + /* printf(" associated with the %s\n", sdparm_assoc_arr[assoc]); */ + switch (desig_type) { + case 0: /* vendor specific */ + k = 0; + if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */ + for (k = 0; (k < dlen) && my_isprint(ip[k]); ++k) + ; + if (k >= dlen) + k = 1; + } + if (k) + n += scnpr(b + n, blen - n, "%s vendor specific: %.*s\n", + lip, dlen, ip); + else { + n += scnpr(b + n, blen - n, "%s vendor specific:\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + } + break; + case 1: /* T10 vendor identification */ + n += scnpr(b + n, blen - n, "%s vendor id: %.8s\n", lip, ip); + if (dlen > 8) { + if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */ + n += scnpr(b + n, blen - n, "%s vendor specific: " + "%.*s\n", lip, dlen - 8, ip + 8); + } else { + n += scnpr(b + n, blen - n, "%s vendor specific: 0x", + lip); + for (m = 8; m < dlen; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + } + } + break; + case 2: /* EUI-64 based */ + if (! do_long) { + if ((8 != dlen) && (12 != dlen) && (16 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expect 8, 12 and 16 " + "byte EUI, got %d >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < dlen; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + break; + } + n += scnpr(b + n, blen - n, "%s EUI-64 based %d byte " + "identifier\n", lip, dlen); + if (1 != c_set) { + n += scnpr(b + n, blen - n, "%s << expected binary code_set " + "(1) >>\n", lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + ci_off = 0; + if (16 == dlen) { + ci_off = 8; + id_ext = sg_get_unaligned_be64(ip); + n += scnpr(b + n, blen - n, "%s Identifier extension: 0x%" + PRIx64 "\n", lip, id_ext); + } else if ((8 != dlen) && (12 != dlen)) { + n += scnpr(b + n, blen - n, "%s << can only decode 8, 12 " + "and 16 byte ids >>\n", lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = sg_get_unaligned_be24(ip + ci_off); + n += scnpr(b + n, blen - n, "%s IEEE Company_id: 0x%x\n", lip, + c_id); + vsei = 0; + for (m = 0; m < 5; ++m) { + if (m > 0) + vsei <<= 8; + vsei |= ip[ci_off + 3 + m]; + } + n += scnpr(b + n, blen - n, "%s Vendor Specific Extension " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + if (12 == dlen) { + d_id = sg_get_unaligned_be32(ip + 8); + n += scnpr(b + n, blen - n, "%s Directory ID: 0x%x\n", lip, + d_id); + } + break; + case 3: /* NAA <n> */ + if (1 != c_set) { + n += scnpr(b + n, blen - n, "%s << unexpected code set %d " + "for NAA >>\n", lip, c_set); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + naa = (ip[0] >> 4) & 0xff; + switch (naa) { + case 2: /* NAA 2: IEEE Extended */ + if (8 != dlen) { + n += scnpr(b + n, blen - n, "%s << unexpected NAA 2 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = (((ip[0] & 0xf) << 8) | ip[1]); + c_id = sg_get_unaligned_be24(ip + 2); + vsi = sg_get_unaligned_be24(ip + 5); + if (do_long) { + n += scnpr(b + n, blen - n, "%s NAA 2, vendor specific " + "identifier A: 0x%x\n", lip, d_id); + n += scnpr(b + n, blen - n, "%s IEEE Company_id: 0x%x\n", + lip, c_id); + n += scnpr(b + n, blen - n, "%s vendor specific " + "identifier B: 0x%x\n", lip, vsi); + n += scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "]\n"); + } + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + break; + case 3: /* NAA 3: Locally assigned */ + if (8 != dlen) { + n += scnpr(b + n, blen - n, "%s << unexpected NAA 3 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + if (do_long) + n += scnpr(b + n, blen - n, "%s NAA 3, Locally " + "assigned:\n", lip); + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + break; + case 5: /* NAA 5: IEEE Registered */ + if (8 != dlen) { + n += scnpr(b + n, blen - n, "%s << unexpected NAA 5 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | + (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); + vsei = ip[3] & 0xf; + for (m = 1; m < 5; ++m) { + vsei <<= 8; + vsei |= ip[3 + m]; + } + if (do_long) { + n += scnpr(b + n, blen - n, "%s NAA 5, IEEE " + "Company_id: 0x%x\n", lip, c_id); + n += scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + n += scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "]\n"); + } else { + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + } + break; + case 6: /* NAA 6: IEEE Registered extended */ + if (16 != dlen) { + n += scnpr(b + n, blen - n, "%s << unexpected NAA 6 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | + (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); + vsei = ip[3] & 0xf; + for (m = 1; m < 5; ++m) { + vsei <<= 8; + vsei |= ip[3 + m]; + } + if (do_long) { + n += scnpr(b + n, blen - n, "%s NAA 6, IEEE " + "Company_id: 0x%x\n", lip, c_id); + n += scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + vsei = sg_get_unaligned_be64(ip + 8); + n += scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier Extension: 0x%" PRIx64 "\n", lip, + vsei); + n += scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 16; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "]\n"); + } else { + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 16; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + } + break; + default: + n += scnpr(b + n, blen - n, "%s << unexpected NAA [0x%x] " + ">>\n", lip, naa); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + break; + case 4: /* Relative target port */ + if ((1 != c_set) || (1 != assoc) || (4 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set, target port association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, "", 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += scnpr(b + n, blen - n, "%s Relative target port: 0x%x\n", + lip, d_id); + break; + case 5: /* (primary) Target port group */ + if ((1 != c_set) || (1 != assoc) || (4 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set, target port association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += scnpr(b + n, blen - n, "%s Target port group: 0x%x\n", lip, + d_id); + break; + case 6: /* Logical unit group */ + if ((1 != c_set) || (0 != assoc) || (4 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set, logical unit association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += scnpr(b + n, blen - n, "%s Logical unit group: 0x%x\n", lip, + d_id); + break; + case 7: /* MD5 logical unit identifier */ + if ((1 != c_set) || (0 != assoc)) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set, logical unit association >>\n", lip); + n += hex2str(ip, dlen, "", 1, blen - n, b + n); + break; + } + n += scnpr(b + n, blen - n, "%s MD5 logical unit identifier:\n", + lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + case 8: /* SCSI name string */ + if (3 != c_set) { /* accept ASCII as subset of UTF-8 */ + if (2 == c_set) { + if (do_long) + n += scnpr(b + n, blen - n, "%s << expected UTF-8, " + "use ASCII >>\n", lip); + } else { + n += scnpr(b + n, blen - n, "%s << expected UTF-8 " + "code_set >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + } + n += scnpr(b + n, blen - n, "%s SCSI name string:\n", lip); + /* does %s print out UTF-8 ok?? + * Seems to depend on the locale. Looks ok here with my + * locale setting: en_AU.UTF-8 + */ + n += scnpr(b + n, blen - n, "%s %.*s\n", lip, dlen, + (const char *)ip); + break; + case 9: /* Protocol specific port identifier */ + /* added in spc4r36, PIV must be set, proto_id indicates */ + /* whether UAS (USB) or SOP (PCIe) or ... */ + if (! piv) + n += scnpr(b + n, blen - n, " %s >>>> Protocol specific " + "port identifier expects protocol\n" + "%s identifier to be valid and it is not\n", + lip, lip); + if (TPROTO_UAS == p_id) { + n += scnpr(b + n, blen - n, "%s USB device address: 0x%x\n", + lip, 0x7f & ip[0]); + n += scnpr(b + n, blen - n, "%s USB interface number: " + "0x%x\n", lip, ip[2]); + } else if (TPROTO_SOP == p_id) { + n += scnpr(b + n, blen - n, "%s PCIe routing ID, bus " + "number: 0x%x\n", lip, ip[0]); + n += scnpr(b + n, blen - n, "%s function number: 0x%x\n", + lip, ip[1]); + n += scnpr(b + n, blen - n, "%s [or device number: " + "0x%x, function number: 0x%x]\n", lip, + (0x1f & (ip[1] >> 3)), 0x7 & ip[1]); + } else + n += scnpr(b + n, blen - n, "%s >>>> unexpected protocol " + "indentifier: %s\n%s with Protocol specific " + "port identifier\n", lip, + sg_get_trans_proto_str(p_id, sizeof(e), e), lip); + break; + case 0xa: /* UUID identifier */ + if (1 != c_set) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + if ((1 != ((ip[0] >> 4) & 0xf)) || (18 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expected locally " + "assigned UUID, 16 bytes long >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + n += scnpr(b + n, blen - n, "%s Locally assigned UUID: ", lip); + for (m = 0; m < 16; ++m) { + if ((4 == m) || (6 == m) || (8 == m) || (10 == m)) + n += scnpr(b + n, blen - n, "-"); + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]); + } + n += scnpr(b + n, blen - n, "\n"); + if (do_long) { + n += scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 16; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]); + n += scnpr(b + n, blen - n, "]\n"); + } + break; + default: /* reserved */ + n += scnpr(b + n, blen - n, "%s reserved designator=0x%x\n", lip, + desig_type); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + return n; +} + +static int +decode_sks(const char * lip, const unsigned char * descp, int add_d_len, + int sense_key, bool * processedp, int blen, char * b) +{ + int progress, pr, rem, n; + + n = 0; + if (NULL == lip) + lip = ""; + switch (sense_key) { + case SPC_SK_ILLEGAL_REQUEST: + if (add_d_len < 6) { + n += scnpr(b + n, blen - n, "Field pointer: "); + goto too_short; + } + /* abbreviate to fit on one line */ + n += scnpr(b + n, blen - n, "Field pointer:\n"); + n += scnpr(b + n, blen - n, "%s Error in %s: byte %d", lip, + (descp[4] & 0x40) ? "Command" : + "Data parameters", + sg_get_unaligned_be16(descp + 5)); + if (descp[4] & 0x08) { + n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07); + } else + n += scnpr(b + n, blen - n, "\n"); + break; + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_RECOVERED_ERROR: + n += scnpr(b + n, blen - n, "Actual retry count: "); + if (add_d_len < 6) + goto too_short; + n += scnpr(b + n, blen - n,"%u\n", sg_get_unaligned_be16(descp + 5)); + break; + case SPC_SK_NO_SENSE: + case SPC_SK_NOT_READY: + n += scnpr(b + n, blen - n, "Progress indication: "); + if (add_d_len < 6) + goto too_short; + progress = sg_get_unaligned_be16(descp + 5); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + n += scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem); + break; + case SPC_SK_COPY_ABORTED: + n += scnpr(b + n, blen - n, "Segment pointer:\n"); + if (add_d_len < 6) + goto too_short; + n += scnpr(b + n, blen - n, "%s Relative to start of %s, byte " + "%d", lip, (descp[4] & 0x20) ? "segment descriptor" : + "parameter list", + sg_get_unaligned_be16(descp + 5)); + if (descp[4] & 0x08) + n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07); + else + n += scnpr(b + n, blen - n, "\n"); + break; + case SPC_SK_UNIT_ATTENTION: + n += scnpr(b + n, blen - n, "Unit attention condition queue:\n"); + n += scnpr(b + n, blen - n, "%s overflow flag is %d\n", lip, + !!(descp[4] & 0x1)); + break; + default: + n += scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n", + sense_key); + *processedp = false; + break; + } + return n; + +too_short: + n += scnpr(b + n, blen - n, "%s\n", " >> descriptor too short"); + *processedp = false; + return n; +} + +#define TPGS_STATE_OPTIMIZED 0x0 +#define TPGS_STATE_NONOPTIMIZED 0x1 +#define TPGS_STATE_STANDBY 0x2 +#define TPGS_STATE_UNAVAILABLE 0x3 +#define TPGS_STATE_OFFLINE 0xe +#define TPGS_STATE_TRANSITIONING 0xf + +static int +decode_tpgs_state(int st, char * b, int blen) +{ + switch (st) { + case TPGS_STATE_OPTIMIZED: + return scnpr(b, blen, "active/optimized"); + case TPGS_STATE_NONOPTIMIZED: + return scnpr(b, blen, "active/non optimized"); + case TPGS_STATE_STANDBY: + return scnpr(b, blen, "standby"); + case TPGS_STATE_UNAVAILABLE: + return scnpr(b, blen, "unavailable"); + case TPGS_STATE_OFFLINE: + return scnpr(b, blen, "offline"); + case TPGS_STATE_TRANSITIONING: + return scnpr(b, blen, "transitioning between states"); + default: + return scnpr(b, blen, "unknown: 0x%x", st); + } +} + +static int +uds_referral_descriptor_str(char * b, int blen, const unsigned char * dp, + int alen, const char * lip) +{ + int n = 0; + int dlen = alen - 2; + int k, j, g, f, tpgd; + const unsigned char * tp; + uint64_t ull; + char c[40]; + + if (NULL == lip) + lip = ""; + n += scnpr(b + n, blen - n, "%s Not all referrals: %d\n", lip, + !!(dp[2] & 0x1)); + dp += 4; + for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) { + tpgd = dp[3]; + g = (tpgd * 4) + 20; + n += scnpr(b + n, blen - n, "%s Descriptor %d\n", lip, f); + if ((k + g) > dlen) { + n += scnpr(b + n, blen - n, "%s truncated descriptor, " + "stop\n", lip); + return n; + } + ull = sg_get_unaligned_be64(dp + 4); + n += scnpr(b + n, blen - n, "%s first uds LBA: 0x%" PRIx64 "\n", + lip, ull); + ull = sg_get_unaligned_be64(dp + 12); + n += scnpr(b + n, blen - n, "%s last uds LBA: 0x%" PRIx64 "\n", + lip, ull); + for (j = 0; j < tpgd; ++j) { + tp = dp + 20 + (j * 4); + decode_tpgs_state(tp[0] & 0xf, c, sizeof(c)); + n += scnpr(b + n, blen - n, "%s tpg: %d state: %s\n", + lip, sg_get_unaligned_be16(tp + 2), c); + } + } + return n; +} + +static const char * dd_usage_reason_str_arr[] = { + "Unknown", + "resend this and further commands to:", + "resend this command to:", + "new subsiduary lu added to this administrative lu:", + "administrative lu associated with a preferred binding:", + }; + + +/* Decode descriptor format sense descriptors (assumes sense buffer is + * in descriptor format) */ +int +sg_get_sense_descriptors_str(const char * lip, const unsigned char * sbp, + int sb_len, int blen, char * b) +{ + int add_sb_len, add_d_len, desc_len, k, j, sense_key; + int n, progress, pr, rem; + bool processed; + const unsigned char * descp; + const char * dtsp = " >> descriptor too short"; + const char * eccp = "Extended copy command"; + const char * ddp = "destination device"; + char z[64]; + + if ((NULL == b) || (blen <= 0)) + return 0; + b[0] = '\0'; + if (lip) + scnpr(z, sizeof(z), "%.60s ", lip); + else + scnpr(z, sizeof(z), " "); + if ((sb_len < 8) || (0 == (add_sb_len = sbp[7]))) + return 0; + add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8); + sense_key = (sbp[1] & 0xf); + + for (descp = (sbp + 8), k = 0, n = 0; + (k < add_sb_len) && (n < blen); + k += desc_len, descp += desc_len) { + add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1; + if ((k + add_d_len + 2) > add_sb_len) + add_d_len = add_sb_len - k - 2; + desc_len = add_d_len + 2; + n += scnpr(b + n, blen - n, "%s Descriptor type: ", lip); + processed = true; + switch (descp[0]) { + case 0: + n += scnpr(b + n, blen - n, "Information: "); + if ((add_d_len >= 10) && (0x80 & descp[2])) { + n += scnpr(b + n, blen - n, "0x"); + for (j = 0; j < 8; ++j) + n += scnpr(b + n, blen - n, "%02x", descp[4 + j]); + n += scnpr(b + n, blen - n, "\n"); + } else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 1: + n += scnpr(b + n, blen - n, "Command specific: "); + if (add_d_len >= 10) { + n += scnpr(b + n, blen - n, "0x"); + for (j = 0; j < 8; ++j) + n += scnpr(b + n, blen - n, "%02x", descp[4 + j]); + n += scnpr(b + n, blen - n, "\n"); + } else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 2: /* Sense Key Specific */ + n += scnpr(b + n, blen - n, "Sense key specific: "); + n += decode_sks(lip, descp, add_d_len, sense_key, &processed, + blen - n, b + n); + break; + case 3: + n += scnpr(b + n, blen - n, "Field replaceable unit code: "); + if (add_d_len >= 2) + n += scnpr(b + n, blen - n, "0x%x\n", descp[3]); + else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 4: + n += scnpr(b + n, blen - n, "Stream commands: "); + if (add_d_len >= 2) { + if (descp[3] & 0x80) + n += scnpr(b + n, blen - n, "FILEMARK"); + if (descp[3] & 0x40) + n += scnpr(b + n, blen - n, "End Of Medium (EOM)"); + if (descp[3] & 0x20) + n += scnpr(b + n, blen - n, "Incorrect Length Indicator " + "(ILI)"); + n += scnpr(b + n, blen - n, "\n"); + } else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 5: + n += scnpr(b + n, blen - n, "Block commands: "); + if (add_d_len >= 2) + n += scnpr(b + n, blen - n, "Incorrect Length Indicator " + "(ILI) %s\n", (descp[3] & 0x20) ? "set" : "clear"); + else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 6: + n += scnpr(b + n, blen - n, "OSD object identification\n"); + processed = false; + break; + case 7: + n += scnpr(b + n, blen - n, "OSD response integrity check " + "value\n"); + processed = false; + break; + case 8: + n += scnpr(b + n, blen - n, "OSD attribute identification\n"); + processed = false; + break; + case 9: /* this is defined in SAT (SAT-2) */ + n += scnpr(b + n, blen - n, "ATA Status Return: "); + if (add_d_len >= 12) { + int extend, count; + + extend = descp[2] & 1; + count = descp[5] + (extend ? (descp[4] << 8) : 0); + n += scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s" + " count=0x%x ", extend, descp[3], lip, + count); + if (extend) + n += scnpr(b + n, blen - n, + "lba=0x%02x%02x%02x%02x%02x%02x ", + descp[10], descp[8], descp[6], descp[11], + descp[9], descp[7]); + else + n += scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ", + descp[11], descp[9], descp[7]); + n += scnpr(b + n, blen - n, "device=0x%x status=0x%x\n", + descp[12], descp[13]); + } else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 0xa: + /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */ + n += scnpr(b + n, blen - n, "Another progress indication: "); + if (add_d_len < 6) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + progress = sg_get_unaligned_be16(descp + 6); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + n += scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem); + n += scnpr(b + n, blen - n, "%s [sense_key=0x%x " + "asc,ascq=0x%x,0x%x]\n", lip, descp[2], descp[3], + descp[4]); + break; + case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */ + n += scnpr(b + n, blen - n, "User data segment referral: "); + if (add_d_len < 2) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + n += scnpr(b + n, blen - n, "\n"); + n += uds_referral_descriptor_str(b + n, blen - n, descp, + add_d_len, lip); + break; + case 0xc: /* Added in SPC-4 rev 28 */ + n += scnpr(b + n, blen - n, "Forwarded sense data\n"); + if (add_d_len < 2) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + n += scnpr(b + n, blen - n, "%s FSDT: %s\n", lip, + (descp[2] & 0x80) ? "set" : "clear"); + j = descp[2] & 0xf; + n += scnpr(b + n, blen - n, "%s Sense data source: ", lip); + switch (j) { + case 0: + n += scnpr(b + n, blen - n, "%s source device\n", eccp); + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + n += scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1); + break; + default: + n += scnpr(b + n, blen - n, "unknown [%d]\n", j); + } + { + char c[480]; + + sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c); + c[sizeof(c) - 1] = '\0'; + n += scnpr(b + n, blen - n, "%s Forwarded status: %s\n", + lip, c); + if (add_d_len > 2) { + /* recursing; hope not to get carried away */ + n += scnpr(b + n, blen - n, "%s vvvvvvvvvvvvvvvv\n", lip); + sg_get_sense_str(lip, descp + 4, add_d_len - 2, false, + sizeof(c), c); + n += scnpr(b + n, blen - n, "%s", c); + n += scnpr(b + n, blen - n, "%s ^^^^^^^^^^^^^^^^\n", lip); + } + } + break; + case 0xd: /* Added in SBC-3 rev 36d */ + /* this descriptor combines descriptors 0, 1, 2 and 3 */ + n += scnpr(b + n, blen - n, "Direct-access block device\n"); + if (add_d_len < 28) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + if (0x20 & descp[2]) + n += scnpr(b + n, blen - n, "%s ILI (incorrect length " + "indication) set\n", lip); + if (0x80 & descp[4]) { + n += scnpr(b + n, blen - n, "%s Sense key specific: ", + lip); + n += decode_sks(lip, descp, add_d_len, sense_key, &processed, + blen - n, b + n); + } + n += scnpr(b + n, blen - n, "%s Field replaceable unit code: " + "0x%x\n", lip, descp[7]); + if (0x80 & descp[2]) { + n += scnpr(b + n, blen - n, "%s Information: 0x", lip); + for (j = 0; j < 8; ++j) + n += scnpr(b + n, blen - n, "%02x", descp[8 + j]); + n += scnpr(b + n, blen - n, "\n"); + } + n += scnpr(b + n, blen - n, "%s Command specific: 0x", lip); + for (j = 0; j < 8; ++j) + n += scnpr(b + n, blen - n, "%02x", descp[16 + j]); + n += scnpr(b + n, blen - n, "\n"); + break; + case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */ + n += scnpr(b + n, blen - n, "Device designation\n"); + j = (int)(sizeof(dd_usage_reason_str_arr) / + sizeof(dd_usage_reason_str_arr[0])); + if (descp[3] < j) + n += scnpr(b + n, blen - n, "%s Usage reason: %s\n", lip, + dd_usage_reason_str_arr[descp[3]]); + else + n += scnpr(b + n, blen - n, "%s Usage reason: " + "reserved[%d]\n", lip, descp[3]); + n += sg_get_designation_descriptor_str(z, descp + 4, descp[1] - 2, + true, false, blen - n, + b + n); + break; + case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */ + n += scnpr(b + n, blen - n, "Microcode activation "); + if (add_d_len < 6) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + progress = sg_get_unaligned_be16(descp + 6); + n += scnpr(b + n, blen - n, "time: "); + if (0 == progress) + n += scnpr(b + n, blen - n, "unknown\n"); + else + n += scnpr(b + n, blen - n, "%d seconds\n", progress); + break; + default: + if (descp[0] >= 0x80) + n += scnpr(b + n, blen - n, "Vendor specific [0x%x]\n", + descp[0]); + else + n += scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]); + processed = false; + break; + } + if (! processed) { + if (add_d_len > 0) { + n += scnpr(b + n, blen - n, "%s ", lip); + for (j = 0; j < add_d_len; ++j) { + if ((j > 0) && (0 == (j % 24))) + n += scnpr(b + n, blen - n, "\n%s ", lip); + n += scnpr(b + n, blen - n, "%02x ", descp[j + 2]); + } + n += scnpr(b + n, blen - n, "\n"); + } + } + if (add_d_len < 0) + n += scnpr(b + n, blen - n, "%s short descriptor\n", lip); + } + return n; +} + +/* Decode SAT ATA PASS-THROUGH fixed format sense. Shows "+" after 'count' + * and/or 'lba' values to indicate that not all data in those fields is shown. + * That extra field information may be available in the ATA pass-through + * results log page parameter with the corresponding 'log_index'. */ +static int +sg_get_sense_sat_pt_fixed_str(const char * lip, const unsigned char * sp, + int slen, int blen, char * b) +{ + int n = 0; + bool extend, count_upper_nz, lba_upper_nz; + + if ((blen < 1) || (slen < 12)) + return n; + if (NULL == lip) + lip = ""; + if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2])) + n += scnpr(b + n, blen - n, "%s >> expected Sense key: Recovered " + "Error ??\n", lip); + /* Fixed sense command-specific information field starts at sp + 8 */ + extend = !!(0x80 & sp[8]); + count_upper_nz = !!(0x40 & sp[8]); + lba_upper_nz = !!(0x20 & sp[8]); + /* Fixed sense information field starts at sp + 3 */ + n += scnpr(b + n, blen - n, "%s error=0x%x, status=0x%x, device=0x%x, " + "count(7:0)=0x%x%c\n", lip, sp[3], sp[4], sp[5], sp[6], + (count_upper_nz ? '+' : ' ')); + n += scnpr(b + n, blen - n, "%s extend=%d, log_index=0x%x, " + "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", lip, (int)extend, + (0xf & sp[8]), sp[9], sp[10], sp[11], + (lba_upper_nz ? '+' : ' ')); + return n; +} + +/* Fetch sense information */ +int +sg_get_sense_str(const char * lip, const unsigned char * sbp, int sb_len, + bool raw_sinfo, int cblen, char * cbp) +{ + bool descriptor_format = false; + bool sdat_ovfl = false; + bool valid; + int len, progress, n, r, pr, rem, blen; + unsigned int info; + uint8_t resp_code; + const char * ebp = NULL; + char ebuff[64]; + char b[256]; + struct sg_scsi_sense_hdr ssh; + + if ((NULL == cbp) || (cblen <= 0)) + return 0; + else if (1 == cblen) { + cbp[0] = '\0'; + return 0; + } + blen = sizeof(b); + n = 0; + if (NULL == lip) + lip = ""; + if ((NULL == sbp) || (sb_len < 1)) { + n += scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip); + return n; + } + resp_code = 0x7f & sbp[0]; + valid = !!(sbp[0] & 0x80); + len = sb_len; + if (sg_scsi_normalize_sense(sbp, sb_len, &ssh)) { + switch (ssh.response_code) { + case 0x70: /* fixed, current */ + ebp = "Fixed format, current"; + len = (sb_len > 7) ? (sbp[7] + 8) : sb_len; + len = (len > sb_len) ? sb_len : len; + sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false; + break; + case 0x71: /* fixed, deferred */ + /* error related to a previous command */ + ebp = "Fixed format, <<<deferred>>>"; + len = (sb_len > 7) ? (sbp[7] + 8) : sb_len; + len = (len > sb_len) ? sb_len : len; + sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false; + break; + case 0x72: /* descriptor, current */ + descriptor_format = true; + ebp = "Descriptor format, current"; + sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false; + break; + case 0x73: /* descriptor, deferred */ + descriptor_format = true; + ebp = "Descriptor format, <<<deferred>>>"; + sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false; + break; + case 0x0: + ebp = "Response code: 0x0 (?)"; + break; + default: + scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x", + ssh.response_code); + ebp = ebuff; + break; + } + n += scnpr(cbp + n, cblen - n, "%s%s; Sense key: %s\n", lip, ebp, + sg_lib_sense_key_desc[ssh.sense_key]); + if (sdat_ovfl) + n += scnpr(cbp + n, cblen - n, "%s<<<Sense data overflow>>>\n", + lip); + if (descriptor_format) { + n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + n += sg_get_sense_descriptors_str(lip, sbp, len, + cblen - n, cbp + n); + } else if ((len > 12) && (0 == ssh.asc) && + (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) { + /* SAT ATA PASS-THROUGH fixed format */ + n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + n += sg_get_sense_sat_pt_fixed_str(lip, sbp, len, + cblen - n, cbp + n); + } else if (len > 2) { /* fixed format */ + if (len > 12) + n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + r = 0; + if (strlen(lip) > 0) + r += scnpr(b + r, blen - r, "%s", lip); + if (len > 6) { + info = sg_get_unaligned_be32(sbp + 3); + if (valid) + r += scnpr(b + r, blen - r, " Info fld=0x%x [%u] ", + info, info); + else if (info > 0) + r += scnpr(b + r, blen - r, " Valid=0, Info fld=0x%x " + "[%u] ", info, info); + } else + info = 0; + if (sbp[2] & 0xe0) { + if (sbp[2] & 0x80) + r += scnpr(b + r, blen - r, " FMK"); + /* current command has read a filemark */ + if (sbp[2] & 0x40) + r += scnpr(b + r, blen - r, " EOM"); + /* end-of-medium condition exists */ + if (sbp[2] & 0x20) + r += scnpr(b + r, blen - r, " ILI"); + /* incorrect block length requested */ + r += scnpr(b + r, blen - r, "\n"); + } else if (valid || (info > 0)) + r += scnpr(b + r, blen - r, "\n"); + if ((len >= 14) && sbp[14]) + r += scnpr(b + r, blen - r, "%s Field replaceable unit " + "code: %d\n", lip, sbp[14]); + if ((len >= 18) && (sbp[15] & 0x80)) { + /* sense key specific decoding */ + switch (ssh.sense_key) { + case SPC_SK_ILLEGAL_REQUEST: + r += scnpr(b + r, blen - r, "%s Sense Key Specific: " + "Error in %s: byte %d", lip, + ((sbp[15] & 0x40) ? "Command" : + "Data parameters"), + sg_get_unaligned_be16(sbp + 16)); + if (sbp[15] & 0x08) + r += scnpr(b + r, blen - r, " bit %d\n", + sbp[15] & 0x07); + else + r += scnpr(b + r, blen - r, "\n"); + break; + case SPC_SK_NO_SENSE: + case SPC_SK_NOT_READY: + progress = sg_get_unaligned_be16(sbp + 16); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + r += scnpr(b + r, blen - r, "%s Progress indication: " + "%d.%02d%%\n", lip, pr, rem); + break; + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_RECOVERED_ERROR: + r += scnpr(b + r, blen - r, "%s Actual retry count: " + "0x%02x%02x\n", lip, sbp[16], sbp[17]); + break; + case SPC_SK_COPY_ABORTED: + r += scnpr(b + r, blen - r, "%s Segment pointer: ", lip); + r += scnpr(b + r, blen - r, "Relative to start of %s, " + "byte %d", ((sbp[15] & 0x20) ? + "segment descriptor" : "parameter list"), + sg_get_unaligned_be16(sbp + 16)); + if (sbp[15] & 0x08) + r += scnpr(b + r, blen - r, " bit %d\n", + sbp[15] & 0x07); + else + r += scnpr(b + r, blen - r, "\n"); + break; + case SPC_SK_UNIT_ATTENTION: + r += scnpr(b + r, blen - r, "%s Unit attention " + "condition queue: ", lip); + r += scnpr(b + r, blen - r, "overflow flag is %d\n", + !!(sbp[15] & 0x1)); + break; + default: + r += scnpr(b + r, blen - r, "%s Sense_key: 0x%x " + "unexpected\n", lip, ssh.sense_key); + break; + } + } + if (r > 0) + n += scnpr(cbp + n, cblen - n, "%s", b); + } else + n += scnpr(cbp + n, cblen - n, "%s fixed descriptor length " + "too short, len=%d\n", lip, len); + } else { /* unable to normalise sense buffer, something irregular */ + if (sb_len < 4) { /* Too short */ + n += scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 " + "byte minimum)\n", lip); + goto check_raw; + } + if (0x7f == resp_code) { /* Vendor specific */ + n += scnpr(cbp + n, cblen - n, "%sVendor specific sense buffer, " + "in hex:\n", lip); + n += hex2str(sbp, sb_len, lip, -1, cblen - n, cbp + n); + return n; /* no need to check raw, just output in hex */ + } + /* non-extended SCSI-1 sense data ?? */ + r = 0; + if (strlen(lip) > 0) + r += scnpr(b + r, blen - r, "%s", lip); + r += scnpr(b + r, blen - r, "Probably uninitialized data.\n%s Try " + "to view as SCSI-1 non-extended sense:\n", lip); + r += scnpr(b + r, blen - r, " AdValid=%d Error class=%d Error " + "code=%d\n", valid, ((sbp[0] >> 4) & 0x7), + (sbp[0] & 0xf)); + if (valid) + scnpr(b + r, blen - r, "%s lba=0x%x\n", lip, + sg_get_unaligned_be24(sbp + 1) & 0x1fffff); + n += scnpr(cbp + n, cblen - n, "%s\n", b); + len = sb_len; + if (len > 32) + len = 32; /* trim in case there is a lot of rubbish */ + } +check_raw: + if (raw_sinfo) { + char z[64]; + + n += scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex):\n", + lip); + if (n >= (cblen - 1)) + return n; + scnpr(z, sizeof(z), "%.50s ", lip); + n += hex2str(sbp, len, z, -1, cblen - n, cbp + n); + } + return n; +} + +/* Print sense information */ +void +sg_print_sense(const char * leadin, const unsigned char * sbp, int sb_len, + bool raw_sinfo) +{ + uint32_t pg_sz = sg_get_page_size(); + char *cp; + uint8_t *free_cp; + + cp = (char *)sg_memalign(pg_sz, pg_sz, &free_cp, 0); + if (NULL == cp) + return; + sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp); + pr2ws("%s", cp); + free(free_cp); +} + +/* Following examines exit_status and outputs a clear error message to + * warnings_strm (usually stderr) if one is known and returns true. + * Otherwise it doesn't print anything and returns false. Note that + * if exit_status==0 then returns true but prints nothing and if + * exit_status<0 ("some error occurred") false is returned. If leadin is + * non-NULL then it is printed before the error message. */ +bool +sg_if_can2stderr(const char * leadin, int exit_status) +{ + const char * s = leadin ? leadin : ""; + + if (exit_status < 0) + return false; + else if (0 == exit_status) + return true; + + switch (exit_status) { + case SG_LIB_CAT_NOT_READY: /* 2 */ + pr2ws("%sDevice not ready\n", s); + return true; + case SG_LIB_CAT_MEDIUM_HARD: /* 3 */ + pr2ws("%sMedium or hardware error\n", s); /* 3 sense keys: Medium, */ + return true; /* hardware error or 'Blank check' for tapes */ + case SG_LIB_CAT_UNIT_ATTENTION: /* 6 */ + pr2ws("%sDevice reported 'Unit attention'\n", s); + return true; + case SG_LIB_CAT_DATA_PROTECT: /* 7 */ + pr2ws("%sDevice reported 'Data protect', read-only?\n", s); + return true; + case SG_LIB_CAT_COPY_ABORTED: /* 10 */ + pr2ws("%sCopy aborted\n", s); + return true; + case SG_LIB_CAT_ABORTED_COMMAND: /* 11 */ + pr2ws("%sCommand aborted\n", s); + return true; + case SG_LIB_CAT_MISCOMPARE: /* 14 */ + pr2ws("%sMiscompare\n", s); + return true; + case SG_LIB_CAT_RES_CONFLICT: /* 24 */ + pr2ws("%sReservation conflict\n", s); + return true; + case SG_LIB_CAT_BUSY: /* 26 */ + pr2ws("%sDevice is busy, try again\n", s); + return true; + case SG_LIB_CAT_TASK_ABORTED: /* 29 */ + pr2ws("%sTask aborted\n", s); + return true; + case SG_LIB_CAT_TIMEOUT: /* 33 */ + pr2ws("%sTime out\n", s); + return true; + case SG_LIB_CAT_PROTECTION: /* 40 */ + pr2ws("%sProtection error\n", s); + return true; + case SG_LIB_NVME_STATUS: /* 48 */ + pr2ws("%sNVMe error (non-zero status)\n", s); + return true; + case SG_LIB_OS_BASE_ERR + EACCES: /* 50 + */ + pr2ws("%sPermission denied\n", s); + return true; + case SG_LIB_OS_BASE_ERR + ENOMEM: + pr2ws("%sUtility unable to allocate memory\n", s); + return true; + case SG_LIB_OS_BASE_ERR + ENOTTY: + pr2ws("%sInappropriate I/O control operation\n", s); + return true; + case SG_LIB_OS_BASE_ERR + EPERM: + pr2ws("%sNot permitted\n", s); + return true; + case SG_LIB_OS_BASE_ERR + EINTR: + pr2ws("%sInterrupted system call\n", s); + return true; + case SG_LIB_OS_BASE_ERR + EIO: + pr2ws("%sInput/output error\n", s); + return true; + case SG_LIB_OS_BASE_ERR + ENODEV: + pr2ws("%sNo such device\n", s); + return true; + case SG_LIB_OS_BASE_ERR + ENOENT: + pr2ws("%sNo such file or directory\n", s); + return true; + default: + return false; + } + return false; +} + +/* If os_err_num is within bounds then the returned value is 'os_err_num + + * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0 + * is returned. */ +int +sg_convert_errno(int os_err_num) +{ + if (os_err_num <= 0) { + if (os_err_num < -1) + return -1; + return os_err_num; + } + if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR)) + return SG_LIB_OS_BASE_ERR + os_err_num; + return -1; +} + +/* See description in sg_lib.h header file */ +bool +sg_scsi_normalize_sense(const unsigned char * sbp, int sb_len, + struct sg_scsi_sense_hdr * sshp) +{ + uint8_t resp_code; + if (sshp) + memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr)); + if ((NULL == sbp) || (sb_len < 1)) + return false; + resp_code = 0x7f & sbp[0]; + if ((resp_code < 0x70) || (resp_code > 0x73)) + return false; + if (sshp) { + sshp->response_code = resp_code; + if (sshp->response_code >= 0x72) { /* descriptor format */ + if (sb_len > 1) + sshp->sense_key = (0xf & sbp[1]); + if (sb_len > 2) + sshp->asc = sbp[2]; + if (sb_len > 3) + sshp->ascq = sbp[3]; + if (sb_len > 7) + sshp->additional_length = sbp[7]; + } else { /* fixed format */ + if (sb_len > 2) + sshp->sense_key = (0xf & sbp[2]); + if (sb_len > 7) { + sb_len = (sb_len < (sbp[7] + 8)) ? sb_len : (sbp[7] + 8); + if (sb_len > 12) + sshp->asc = sbp[12]; + if (sb_len > 13) + sshp->ascq = sbp[13]; + } + } + } + return true; +} + +/* Returns a SG_LIB_CAT_* value. If cannot decode sense buffer (sbp) or a + * less common sense key then return SG_LIB_CAT_SENSE .*/ +int +sg_err_category_sense(const unsigned char * sbp, int sb_len) +{ + struct sg_scsi_sense_hdr ssh; + + if ((sbp && (sb_len > 2)) && + (sg_scsi_normalize_sense(sbp, sb_len, &ssh))) { + switch (ssh.sense_key) { /* 0 to 0x1f */ + case SPC_SK_NO_SENSE: + return SG_LIB_CAT_NO_SENSE; + case SPC_SK_RECOVERED_ERROR: + return SG_LIB_CAT_RECOVERED; + case SPC_SK_NOT_READY: + return SG_LIB_CAT_NOT_READY; + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_BLANK_CHECK: + return SG_LIB_CAT_MEDIUM_HARD; + case SPC_SK_UNIT_ATTENTION: + return SG_LIB_CAT_UNIT_ATTENTION; + /* used to return SG_LIB_CAT_MEDIA_CHANGED when ssh.asc==0x28 */ + case SPC_SK_ILLEGAL_REQUEST: + if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) + return SG_LIB_CAT_INVALID_OP; + else + return SG_LIB_CAT_ILLEGAL_REQ; + break; + case SPC_SK_ABORTED_COMMAND: + if (0x10 == ssh.asc) + return SG_LIB_CAT_PROTECTION; + else + return SG_LIB_CAT_ABORTED_COMMAND; + case SPC_SK_MISCOMPARE: + return SG_LIB_CAT_MISCOMPARE; + case SPC_SK_DATA_PROTECT: + return SG_LIB_CAT_DATA_PROTECT; + case SPC_SK_COPY_ABORTED: + return SG_LIB_CAT_COPY_ABORTED; + case SPC_SK_COMPLETED: + case SPC_SK_VOLUME_OVERFLOW: + return SG_LIB_CAT_SENSE; + default: + ; /* reserved and vendor specific sense keys fall through */ + } + } + return SG_LIB_CAT_SENSE; +} + +/* Beware: gives wrong answer for variable length command (opcode=0x7f) */ +int +sg_get_command_size(unsigned char opcode) +{ + switch ((opcode >> 5) & 0x7) { + case 0: + return 6; + case 1: case 2: case 6: case 7: + return 10; + case 3: case 5: + return 12; + break; + case 4: + return 16; + default: + return 10; + } +} + +void +sg_get_command_name(const unsigned char * cmdp, int peri_type, int buff_len, + char * buff) +{ + int service_action; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + if (NULL == cmdp) { + scnpr(buff, buff_len, "%s", "<null> command pointer"); + return; + } + service_action = (SG_VARIABLE_LENGTH_CMD == cmdp[0]) ? + sg_get_unaligned_be16(cmdp + 8) : (cmdp[1] & 0x1f); + sg_get_opcode_sa_name(cmdp[0], service_action, peri_type, buff_len, buff); +} + +struct op_code2sa_t { + int op_code; + int pdt_match; /* -1->all; 0->disk,ZBC,RCB, 1->tape+adc+smc */ + struct sg_lib_value_name_t * arr; + const char * prefix; +}; + +static struct op_code2sa_t op_code2sa_arr[] = { + {SG_VARIABLE_LENGTH_CMD, -1, sg_lib_variable_length_arr, NULL}, + {SG_MAINTENANCE_IN, -1, sg_lib_maint_in_arr, NULL}, + {SG_MAINTENANCE_OUT, -1, sg_lib_maint_out_arr, NULL}, + {SG_SERVICE_ACTION_IN_12, -1, sg_lib_serv_in12_arr, NULL}, + {SG_SERVICE_ACTION_OUT_12, -1, sg_lib_serv_out12_arr, NULL}, + {SG_SERVICE_ACTION_IN_16, -1, sg_lib_serv_in16_arr, NULL}, + {SG_SERVICE_ACTION_OUT_16, -1, sg_lib_serv_out16_arr, NULL}, + {SG_SERVICE_ACTION_BIDI, -1, sg_lib_serv_bidi_arr, NULL}, + {SG_PERSISTENT_RESERVE_IN, -1, sg_lib_pr_in_arr, "Persistent reserve in"}, + {SG_PERSISTENT_RESERVE_OUT, -1, sg_lib_pr_out_arr, + "Persistent reserve out"}, + {SG_3PARTY_COPY_OUT, -1, sg_lib_xcopy_sa_arr, NULL}, + {SG_3PARTY_COPY_IN, -1, sg_lib_rec_copy_sa_arr, NULL}, + {SG_READ_BUFFER, -1, sg_lib_read_buff_arr, "Read buffer(10)"}, + {SG_READ_BUFFER_16, -1, sg_lib_read_buff_arr, "Read buffer(16)"}, + {SG_READ_ATTRIBUTE, -1, sg_lib_read_attr_arr, "Read attribute"}, + {SG_READ_POSITION, 1, sg_lib_read_pos_arr, "Read position"}, + {SG_SANITIZE, 0, sg_lib_sanitize_sa_arr, "Sanitize"}, + {SG_WRITE_BUFFER, -1, sg_lib_write_buff_arr, "Write buffer"}, + {SG_ZONING_IN, 0, sg_lib_zoning_in_arr, NULL}, + {SG_ZONING_OUT, 0, sg_lib_zoning_out_arr, NULL}, + {0xffff, -1, NULL, NULL}, +}; + +void +sg_get_opcode_sa_name(unsigned char cmd_byte0, int service_action, + int peri_type, int buff_len, char * buff) +{ + int d_pdt; + const struct sg_lib_value_name_t * vnp; + const struct op_code2sa_t * osp; + char b[80]; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + + if (peri_type < 0) + peri_type = 0; + d_pdt = sg_lib_pdt_decay(peri_type); + for (osp = op_code2sa_arr; osp->arr; ++osp) { + if ((int)cmd_byte0 == osp->op_code) { + if ((osp->pdt_match < 0) || (d_pdt == osp->pdt_match)) { + vnp = get_value_name(osp->arr, service_action, peri_type); + if (vnp) { + if (osp->prefix) + scnpr(buff, buff_len, "%s, %s", osp->prefix, + vnp->name); + else + scnpr(buff, buff_len, "%s", vnp->name); + } else { + sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b); + scnpr(buff, buff_len, "%s service action=0x%x", b, + service_action); + } + } else + sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff); + return; + } + } + sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff); +} + +void +sg_get_opcode_name(unsigned char cmd_byte0, int peri_type, int buff_len, + char * buff) +{ + const struct sg_lib_value_name_t * vnp; + int grp; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + if (SG_VARIABLE_LENGTH_CMD == cmd_byte0) { + scnpr(buff, buff_len, "%s", "Variable length"); + return; + } + grp = (cmd_byte0 >> 5) & 0x7; + switch (grp) { + case 0: + case 1: + case 2: + case 4: + case 5: + vnp = get_value_name(sg_lib_normal_opcodes, cmd_byte0, peri_type); + if (vnp) + scnpr(buff, buff_len, "%s", vnp->name); + else + scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0); + break; + case 3: + scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0); + break; + case 6: + case 7: + scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0); + break; + default: + scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0); + break; + } +} + +/* Iterates to next designation descriptor in the device identification + * VPD page. The 'initial_desig_desc' should point to start of first + * descriptor with 'page_len' being the number of valid bytes in that + * and following descriptors. To start, 'off' should point to a negative + * value, thereafter it should point to the value yielded by the previous + * call. If 0 returned then 'initial_desig_desc + *off' should be a valid + * descriptor; returns -1 if normal end condition and -2 for an abnormal + * termination. Matches association, designator_type and/or code_set when + * any of those values are greater than or equal to zero. */ +int +sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len, + int * off, int m_assoc, int m_desig_type, int m_code_set) +{ + bool fltr = ((m_assoc >= 0) || (m_desig_type >= 0) || (m_code_set >= 0)); + int k = *off; + const unsigned char * bp = initial_desig_desc; + + while ((k + 3) < page_len) { + k = (k < 0) ? 0 : (k + bp[k + 3] + 4); + if ((k + 4) > page_len) + break; + if (fltr) { + if (m_code_set >= 0) { + if ((bp[k] & 0xf) != m_code_set) + continue; + } + if (m_assoc >= 0) { + if (((bp[k + 1] >> 4) & 0x3) != m_assoc) + continue; + } + if (m_desig_type >= 0) { + if ((bp[k + 1] & 0xf) != m_desig_type) + continue; + } + } + *off = k; + return 0; + } + return (k == page_len) ? -1 : -2; +} + +static const char * const bad_sense_cat = "Bad sense category"; + +/* Yield string associated with sense category. Returns 'buff' (or pointer + * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then + * yield "Sense category: <sense_cat>" string. */ +const char * +sg_get_category_sense_str(int sense_cat, int buff_len, char * buff, + int verbose) +{ + int n; + + if (NULL == buff) + return bad_sense_cat; + if (buff_len <= 0) + return buff; + switch (sense_cat) { + case SG_LIB_CAT_CLEAN: /* 0 */ + scnpr(buff, buff_len, "No errors"); + break; + case SG_LIB_SYNTAX_ERROR: /* 1 */ + scnpr(buff, buff_len, "Syntax error"); + break; + case SG_LIB_CAT_NOT_READY: /* 2 */ + n = scnpr(buff, buff_len, "Not ready"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_MEDIUM_HARD: /* 3 */ + n = scnpr(buff, buff_len, "Medium or hardware error"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key (plus blank check)"); + break; + case SG_LIB_CAT_ILLEGAL_REQ: /* 5 */ + n = scnpr(buff, buff_len, "Illegal request"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key, apart from Invalid " + "opcode"); + break; + case SG_LIB_CAT_UNIT_ATTENTION: /* 6 */ + n = scnpr(buff, buff_len, "Unit attention"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_DATA_PROTECT: /* 7 */ + n = scnpr(buff, buff_len, "Data protect"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key, write protected " + "media?"); + break; + case SG_LIB_CAT_INVALID_OP: /* 9 */ + n = scnpr(buff, buff_len, "Illegal request, invalid opcode"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_COPY_ABORTED: /* 10 */ + n = scnpr(buff, buff_len, "Copy aborted"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_ABORTED_COMMAND: /* 11 */ + n = scnpr(buff, buff_len, "Aborted command"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key, other than " + "protection related (asc=0x10)"); + break; + case SG_LIB_CAT_MISCOMPARE: /* 14 */ + n = scnpr(buff, buff_len, "Miscompare"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_FILE_ERROR: /* 15 */ + scnpr(buff, buff_len, "File error"); + break; + case SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO: /* 17 */ + scnpr(buff, buff_len, "Illegal request with info"); + break; + case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO: /* 18 */ + scnpr(buff, buff_len, "Medium or hardware error with info"); + break; + case SG_LIB_CAT_NO_SENSE: /* 20 */ + n = scnpr(buff, buff_len, "No sense key"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " probably additional sense " + "information"); + break; + case SG_LIB_CAT_RECOVERED: /* 21 */ + n = scnpr(buff, buff_len, "Recovered error"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_RES_CONFLICT: /* 24 */ + n = scnpr(buff, buff_len, "Reservation conflict"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_CONDITION_MET: /* 25 */ + n = scnpr(buff, buff_len, "Condition met"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_BUSY: /* 26 */ + n = scnpr(buff, buff_len, "Busy"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_TS_FULL: /* 27 */ + n = scnpr(buff, buff_len, "Task set full"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_ACA_ACTIVE: /* 28 */ + n = scnpr(buff, buff_len, "ACA active"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_TASK_ABORTED: /* 29 */ + n = scnpr(buff, buff_len, "Task aborted"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_TIMEOUT: /* 33 */ + scnpr(buff, buff_len, "SCSI command timeout"); + break; + case SG_LIB_CAT_PROTECTION: /* 40 */ + n = scnpr(buff, buff_len, "Aborted command, protection"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " information (PI) problem"); + break; + case SG_LIB_CAT_PROTECTION_WITH_INFO: /* 41 */ + n = scnpr(buff, buff_len, "Aborted command with info, protection"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " information (PI) problem"); + break; + case SG_LIB_CAT_MALFORMED: /* 97 */ + n = scnpr(buff, buff_len, "Malformed response"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " to SCSI command"); + break; + case SG_LIB_CAT_SENSE: /* 98 */ + n = scnpr(buff, buff_len, "Some other sense data problem"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, ", try '-v' option for more " + "information"); + break; + case SG_LIB_CAT_OTHER: /* 99 */ + n = scnpr(buff, buff_len, "Some other error/warning has occurred"); + if ((0 == verbose) && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, ", possible transport of driver " + "issue"); + break; + default: + if ((sense_cat > SG_LIB_OS_BASE_ERR) && + (sense_cat < (SG_LIB_OS_BASE_ERR + 47))) { + int k = sense_cat - SG_LIB_OS_BASE_ERR; + + n = scnpr(buff, buff_len, "OS error: %s [%d]", safe_strerror(k), + k); + } else { + n = scnpr(buff, buff_len, "Sense category: %d", sense_cat); + if ((0 == verbose) && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, ", try '-v' option for more " + "information"); + } + break; + } + return buff; +} + +static const char * sg_sfs_spc_reserved = "SPC Reserved"; +static const char * sg_sfs_sbc_reserved = "SBC Reserved"; +static const char * sg_sfs_ssc_reserved = "SSC Reserved"; +static const char * sg_sfs_zbc_reserved = "ZBC Reserved"; +static const char * sg_sfs_reserved = "Reserved"; + +/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31) + * returns pointer to string (same as 'buff') associated with 'sfs_code'. + * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match + * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL + * then where it points is set to true if a match is found else it is set to + * false. If 'buff' is not NULL then in the case of a match a descriptive + * string is written to 'buff' while if there is not a not then a string + * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC + * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL. + * Example: + * char b[64]; + * ... + * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0)); + */ +const char * +sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, char * buff, + bool * foundp, int verbose) +{ + const struct sg_lib_value_name_t * vnp = NULL; + int n = 0; + int my_pdt; + + if ((NULL == buff) || (buff_len < 1)) { + if (foundp) + *foundp = false; + return NULL; + } else if (1 == buff_len) { + buff[0] = '\0'; + if (foundp) + *foundp = false; + return NULL; + } + my_pdt = ((peri_type < -1) || (peri_type > 0x1f)) ? -2 : peri_type; + vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt); + if (vnp && (-2 != my_pdt)) { + if (peri_type != vnp->peri_dev_type) + vnp = NULL; /* shouldn't really happen */ + } + if (foundp) + *foundp = vnp ? true : false; + if (sfs_code < 0x100) { /* SPC Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "SPC %s", vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved); + } else if (sfs_code < 0x200) { /* SBC Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "SBC %s", vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved); + } else if (sfs_code < 0x300) { /* SSC Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "SSC %s", vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved); + } else if (sfs_code < 0x400) { /* ZBC Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "ZBC %s", vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved); + } else { /* Other SCSI Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "[unrecognized PDT] %s", + vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_reserved); + + } + if (verbose > 4) + pr2serr("%s: length of returned string (n) %d\n", __func__, n); + return buff; +} + +/* This is a heuristic that takes into account the command bytes and length + * to decide whether the presented unstructured sequence of bytes could be + * a SCSI command. If so it returns true otherwise false. Vendor specific + * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed + * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The + * only SCSI commands considered above 16 bytes of length are the Variable + * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e). + * Both have an inbuilt length field which can be cross checked with clen. + * No NVMe commands (64 bytes long plus some extra added by some OSes) have + * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS + * structures that are sent across the wire. The FIS register structure is + * used to move a command from a SATA host to device, but the ATA 'command' + * is not the first byte. So it is harder to say what will happen if a + * FIS structure is presented as a SCSI command, hopfully there is a low + * probability this function will yield true in that case. */ +bool +sg_is_scsi_cdb(const uint8_t * cdbp, int clen) +{ + int ilen, sa; + uint8_t opcode; + uint8_t top3bits; + + if (clen < 6) + return false; + opcode = cdbp[0]; + top3bits = opcode >> 5; + if (0x3 == top3bits) { + if ((clen < 12) || (clen % 4)) + return false; /* must be modulo 4 and 12 or more bytes */ + switch (opcode) { + case 0x7e: /* Extended cdb (XCDB) */ + ilen = 4 + sg_get_unaligned_be16(cdbp + 2); + return (ilen == clen); + case 0x7f: /* Variable Length cdb */ + ilen = 8 + cdbp[7]; + sa = sg_get_unaligned_be16(cdbp + 8); + /* service action (sa) 0x0 is reserved */ + return ((ilen == clen) && sa); + default: + return false; + } + } else if (clen <= 16) { + switch (clen) { + case 6: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x0 == top3bits); /* 6 byte cdb */ + case 10: + if (top3bits > 0x5) /* vendor */ + return true; + return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */ + case 16: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x4 == top3bits); /* 16 byte cdb */ + case 12: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x5 == top3bits); /* 12 byte cdb */ + default: + return false; + } + } + /* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or + * opcode > 0x7f). */ + return false; +} + +/* Yield string associated with NVMe command status value in sct_sc. It + * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25 + * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC). + * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found + * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated. + * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/ +char * +sg_get_nvme_cmd_status_str(uint16_t sct_sc, int b_len, char * b) +{ + int k; + uint16_t s = 0x3ff & sct_sc; + const struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr; + + if ((b_len <= 0) || (NULL == b)) + return b; + else if (1 == b_len) { + b[0] = '\0'; + return b; + } + for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) { + if (s == (uint16_t)vp->value) { + strncpy(b, vp->name, b_len); + b[b_len - 1] = '\0'; + return b; + } + } + if (k >= 1000) + pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n", + __func__); + snprintf(b, b_len, "Reserved [0x%x]", sct_sc); + return b; +} + +/* Attempts to map NVMe status value ((SCT << 8) | SC) to SCSI status, + * sense_key, asc and ascq tuple. If successful returns true and writes to + * non-NULL pointer arguments; otherwise returns false. */ +bool +sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p, + uint8_t * asc_p, uint8_t * ascq_p) +{ + int k, ind; + uint16_t s = 0x3ff & sct_sc; + struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr; + struct sg_lib_4tuple_u8 * mp = sg_lib_scsi_status_sense_arr; + + for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) { + if (s == (uint16_t)vp->value) + break; + } + if (k >= 1000) { + pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n", + __func__); + return false; + } + if (NULL == vp->name) + return false; + ind = vp->peri_dev_type; + + + for (k = 0; (0xff != mp->t2) && k < 1000; ++k, ++mp) + ; /* count entries for valid index range */ + if (k >= 1000) { + pr2ws("%s: where is sentinel for sg_lib_scsi_status_sense_arr ??\n", + __func__); + return false; + } else if (ind >= k) + return false; + mp = sg_lib_scsi_status_sense_arr + ind; + if (status_p) + *status_p = mp->t1; + if (sk_p) + *sk_p = mp->t2; + if (asc_p) + *asc_p = mp->t3; + if (ascq_p) + *ascq_p = mp->t4; + return true; +} + +/* safe_strerror() contributed by Clayton Weaver <cgweav at email dot com> + * Allows for situation in which strerror() is given a wild value (or the + * C library is incomplete) and returns NULL. Still not thread safe. + */ + +static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ', + 'e', 'r', 'r', 'n', 'o', ':', ' ', 0}; + +char * +safe_strerror(int errnum) +{ + size_t len; + char * errstr; + + if (errnum < 0) + errnum = -errnum; + errstr = strerror(errnum); + if (NULL == errstr) { + len = strlen(safe_errbuf); + scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum); + return safe_errbuf; + } + return errstr; +} + +static void +trimTrailingSpaces(char * b) +{ + int k; + + for (k = ((int)strlen(b) - 1); k >= 0; --k) { + if (' ' != b[k]) + break; + } + if ('\0' != b[k + 1]) + b[k + 1] = '\0'; +} + +/* Note the ASCII-hex output goes to stdout. [Most other output from functions + * in this file go to sg_warnings_strm (default stderr).] + * 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 16 ASCII-hex bytes + * = 0 in addition, the bytes are listed in ASCII to the right + * < 0 only the ASCII-hex bytes are listed (i.e. without address) */ +static void +dStrHexFp(const char* str, int len, int no_ascii, FILE * fp) +{ + const char * p = str; + const char * formatstr; + unsigned char c; + char buff[82]; + int a = 0; + int bpstart = 5; + const int cpstart = 60; + int cpos = cpstart; + int bpos = bpstart; + int i, k, blen; + + if (len <= 0) + return; + blen = (int)sizeof(buff); + if (0 == no_ascii) /* address at left and ASCII at right */ + formatstr = "%.76s\n"; + else /* previously when > 0 str was "%.58s\n" */ + formatstr = "%s\n"; /* when < 0 str was: "%.48s\n" */ + memset(buff, ' ', 80); + buff[80] = '\0'; + if (no_ascii < 0) { + bpstart = 0; + bpos = bpstart; + for (k = 0; k < len; k++) { + c = *p++; + if (bpos == (bpstart + (8 * 3))) + bpos++; + scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c); + buff[bpos + 2] = ' '; + if ((k > 0) && (0 == ((k + 1) % 16))) { + trimTrailingSpaces(buff); + fprintf(fp, formatstr, buff); + bpos = bpstart; + memset(buff, ' ', 80); + } else + bpos += 3; + } + if (bpos > bpstart) { + buff[bpos + 2] = '\0'; + trimTrailingSpaces(buff); + fprintf(fp, "%s\n", buff); + } + return; + } + /* no_ascii>=0, start each line with address (offset) */ + k = scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + + for (i = 0; i < len; i++) { + c = *p++; + bpos += 3; + if (bpos == (bpstart + (9 * 3))) + bpos++; + scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c); + buff[bpos + 2] = ' '; + if (no_ascii) + buff[cpos++] = ' '; + else { + if (! my_isprint(c)) + c = '.'; + buff[cpos++] = c; + } + if (cpos > (cpstart + 15)) { + if (no_ascii) + trimTrailingSpaces(buff); + fprintf(fp, formatstr, buff); + bpos = bpstart; + cpos = cpstart; + a += 16; + memset(buff, ' ', 80); + k = scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + } + } + if (cpos > cpstart) { + buff[cpos] = '\0'; + if (no_ascii) + trimTrailingSpaces(buff); + fprintf(fp, "%s\n", buff); + } +} + +void +dStrHex(const char* str, int len, int no_ascii) +{ + dStrHexFp(str, len, no_ascii, stdout); +} + +void +dStrHexErr(const char* str, int len, int no_ascii) +{ + dStrHexFp(str, len, no_ascii, + (sg_warnings_strm ? sg_warnings_strm : stderr)); +} + +#define DSHS_LINE_BLEN 160 +#define DSHS_BPL 16 + +/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space + * separated) to 'b' not to exceed 'b_len' characters. Each line + * starts with 'leadin' (NULL for no leadin) and there are 16 bytes + * per line with an extra space between the 8th and 9th bytes. 'format' + * is 0 for repeat in printable ASCII ('.' for non printable) to + * right of each line; 1 don't (so just output ASCII hex). Returns + * number of bytes written to 'b' excluding the trailing '\0'. */ +int +dStrHexStr(const char * str, int len, const char * leadin, int format, + int b_len, char * b) +{ + unsigned char c; + int bpstart, bpos, k, n, prior_ascii_len; + bool want_ascii; + char buff[DSHS_LINE_BLEN + 2]; + char a[DSHS_BPL + 1]; + const char * p = str; + + if (len <= 0) { + if (b_len > 0) + b[0] = '\0'; + return 0; + } + if (b_len <= 0) + return 0; + want_ascii = !format; + if (want_ascii) { + memset(a, ' ', DSHS_BPL); + a[DSHS_BPL] = '\0'; + } + if (leadin) { + bpstart = strlen(leadin); + /* Cap leadin at (DSHS_LINE_BLEN - 70) characters */ + if (bpstart > (DSHS_LINE_BLEN - 70)) + bpstart = DSHS_LINE_BLEN - 70; + } else + bpstart = 0; + bpos = bpstart; + prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1; + n = 0; + memset(buff, ' ', DSHS_LINE_BLEN); + buff[DSHS_LINE_BLEN] = '\0'; + if (bpstart > 0) + memcpy(buff, leadin, bpstart); + for (k = 0; k < len; k++) { + c = *p++; + if (bpos == (bpstart + ((DSHS_BPL / 2) * 3))) + bpos++; /* for extra space in middle of each line's hex */ + scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x", + (int)(unsigned char)c); + buff[bpos + 2] = ' '; + if (want_ascii) + a[k % DSHS_BPL] = my_isprint(c) ? c : '.'; + if ((k > 0) && (0 == ((k + 1) % DSHS_BPL))) { + trimTrailingSpaces(buff); + if (want_ascii) { + n += scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len, + buff, a); + memset(a, ' ', DSHS_BPL); + } else + n += scnpr(b + n, b_len - n, "%s\n", buff); + if (n >= (b_len - 1)) + return n; + memset(buff, ' ', DSHS_LINE_BLEN); + bpos = bpstart; + if (bpstart > 0) + memcpy(buff, leadin, bpstart); + } else + bpos += 3; + } + if (bpos > bpstart) { + trimTrailingSpaces(buff); + if (want_ascii) + n += scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len, + buff, a); + else + n += scnpr(b + n, b_len - n, "%s\n", buff); + } + return n; +} + +void +hex2stdout(const uint8_t * b_str, int len, int no_ascii) +{ + dStrHex((const char *)b_str, len, no_ascii); +} + +void +hex2stderr(const uint8_t * b_str, int len, int no_ascii) +{ + dStrHexErr((const char *)b_str, len, no_ascii); +} + +int +hex2str(const uint8_t * b_str, int len, const char * leadin, int format, + int b_len, char * b) +{ + return dStrHexStr((const char *)b_str, len, leadin, format, b_len, b); +} + +/* Returns true when executed on big endian machine; else returns false. + * Useful for displaying ATA identify words (which need swapping on a + * big endian machine). */ +bool +sg_is_big_endian() +{ + union u_t { + uint16_t s; + unsigned char c[sizeof(uint16_t)]; + } u; + + u.s = 0x0102; + return (u.c[0] == 0x01); /* The lowest address contains + the most significant byte */ +} + +bool +sg_all_zeros(const uint8_t * bp, int b_len) +{ + if ((NULL == bp) || (b_len <= 0)) + return false; + for (--b_len; b_len >= 0; --b_len) { + if (0x0 != bp[b_len]) + return false; + } + return true; +} + +bool +sg_all_ffs(const uint8_t * bp, int b_len) +{ + if ((NULL == bp) || (b_len <= 0)) + return false; + for (--b_len; b_len >= 0; --b_len) { + if (0xff != bp[b_len]) + return false; + } + return true; +} + +static uint16_t +swapb_uint16(uint16_t u) +{ + uint16_t r; + + r = (u >> 8) & 0xff; + r |= ((u & 0xff) << 8); + return r; +} + +/* Note the ASCII-hex output goes to stdout. [Most other output from functions + * in this file go to sg_warnings_strm (default stderr).] + * 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 8 ASCII-hex 16 bit words + * = 0 in addition, the ASCI bytes pairs are listed to the right + * = -1 only the ASCII-hex words are listed (i.e. without address) + * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin" + * < -2 same as -1 + * If 'swapb' is true then bytes in each word swapped. Needs to be set + * for ATA IDENTIFY DEVICE response on big-endian machines. */ +void +dWordHex(const uint16_t* words, int num, int no_ascii, bool swapb) +{ + const uint16_t * p = words; + uint16_t c; + char buff[82]; + unsigned char upp, low; + int a = 0; + const int bpstart = 3; + const int cpstart = 52; + int cpos = cpstart; + int bpos = bpstart; + int i, k, blen; + + if (num <= 0) + return; + blen = (int)sizeof(buff); + memset(buff, ' ', 80); + buff[80] = '\0'; + if (no_ascii < 0) { + for (k = 0; k < num; k++) { + c = *p++; + if (swapb) + c = swapb_uint16(c); + bpos += 5; + scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c); + buff[bpos + 4] = ' '; + if ((k > 0) && (0 == ((k + 1) % 8))) { + if (-2 == no_ascii) + printf("%.39s\n", buff +8); + else + printf("%.47s\n", buff); + bpos = bpstart; + memset(buff, ' ', 80); + } + } + if (bpos > bpstart) { + if (-2 == no_ascii) + printf("%.39s\n", buff +8); + else + printf("%.47s\n", buff); + } + return; + } + /* no_ascii>=0, start each line with address (offset) */ + k = scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + + for (i = 0; i < num; i++) { + c = *p++; + if (swapb) + c = swapb_uint16(c); + bpos += 5; + scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c); + buff[bpos + 4] = ' '; + if (no_ascii) { + buff[cpos++] = ' '; + buff[cpos++] = ' '; + buff[cpos++] = ' '; + } else { + upp = (c >> 8) & 0xff; + low = c & 0xff; + if (! my_isprint(upp)) + upp = '.'; + buff[cpos++] = upp; + if (! my_isprint(low)) + low = '.'; + buff[cpos++] = low; + buff[cpos++] = ' '; + } + if (cpos > (cpstart + 23)) { + printf("%.76s\n", buff); + bpos = bpstart; + cpos = cpstart; + a += 8; + memset(buff, ' ', 80); + k = scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + } + } + if (cpos > cpstart) + printf("%.76s\n", buff); +} + +/* If the number in 'buf' can be decoded or the multiplier is unknown + * then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal + * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)). + * Main (SI) multipliers supported: K, M, G. Ignore leading spaces and + * tabs; accept comma, hyphen, space, tab and hash as terminator. */ +int +sg_get_num(const char * buf) +{ + int res, num, n, len; + unsigned int unum; + char * cp; + const char * b; + char c = 'c'; + char c2 = '\0'; /* keep static checker happy */ + char c3 = '\0'; /* keep static checker happy */ + char lb[16]; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + n = strspn(buf, " \t"); + if (n > 0) { + if (n == len) + return -1; + buf += n; + len -= n; + } + /* following hack to keep C++ happy */ + cp = strpbrk((char *)buf, " \t,#-"); + if (cp) { + len = cp - buf; + n = (int)sizeof(lb) - 1; + len = (len < n) ? len : n; + memcpy(lb, buf, len); + lb[len] = '\0'; + b = lb; + } else + b = buf; + if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) { + res = sscanf(b + 2, "%x", &unum); + num = unum; + } else if ('H' == toupper((int)b[len - 1])) { + res = sscanf(b, "%x", &unum); + num = unum; + } else + res = sscanf(b, "%d%c%c%c", &num, &c, &c2, &c3); + if (res < 1) + return -1LL; + else if (1 == res) + return num; + else { + if (res > 2) + c2 = toupper((int)c2); + if (res > 3) + c3 = toupper((int)c3); + switch (toupper((int)c)) { + case 'C': + return num; + case 'W': + return num * 2; + case 'B': + return num * 512; + case 'K': + if (2 == res) + return num * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1024; + return -1; + case 'M': + if (2 == res) + return num * 1048576; + if (('B' == c2) || ('D' == c2)) + return num * 1000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1048576; + return -1; + case 'G': + if (2 == res) + return num * 1073741824; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1073741824; + return -1; + case 'X': + cp = (char *)strchr(b, 'x'); + if (NULL == cp) + cp = (char *)strchr(b, 'X'); + if (cp) { + n = sg_get_num(cp + 1); + if (-1 != n) + return num * n; + } + return -1; + default: + pr2ws("unrecognized multiplier\n"); + return -1; + } + } +} + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. */ +int +sg_get_num_nomult(const char * buf) +{ + int res, len, num; + unsigned int unum; + char * commap; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + commap = (char *)strchr(buf + 1, ','); + if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { + res = sscanf(buf + 2, "%x", &unum); + num = unum; + } else if (commap && ('H' == toupper((int)*(commap - 1)))) { + res = sscanf(buf, "%x", &unum); + num = unum; + } else if ((NULL == commap) && ('H' == toupper((int)buf[len - 1]))) { + res = sscanf(buf, "%x", &unum); + num = unum; + } else + res = sscanf(buf, "%d", &num); + if (1 == res) + return num; + else + return -1; +} + +/* If the number in 'buf' can be decoded or the multiplier is unknown + * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a decimal + * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)). + * Main (SI) multipliers supported: K, M, G, T, P. Ignore leading spaces + * and tabs; accept comma, hyphen, space, tab and hash as terminator. */ +int64_t +sg_get_llnum(const char * buf) +{ + int res, len, n; + int64_t num, ll; + uint64_t unum; + char * cp; + const char * b; + char c = 'c'; + char c2 = '\0'; /* keep static checker happy */ + char c3 = '\0'; /* keep static checker happy */ + char lb[32]; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1LL; + len = strlen(buf); + n = strspn(buf, " \t"); + if (n > 0) { + if (n == len) + return -1LL; + buf += n; + len -= n; + } + /* following hack to keep C++ happy */ + cp = strpbrk((char *)buf, " \t,#-"); + if (cp) { + len = cp - buf; + n = (int)sizeof(lb) - 1; + len = (len < n) ? len : n; + memcpy(lb, buf, len); + lb[len] = '\0'; + b = lb; + } else + b = buf; + if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) { + res = sscanf(b + 2, "%" SCNx64 , &unum); + num = unum; + } else if ('H' == toupper((int)b[len - 1])) { + res = sscanf(b, "%" SCNx64 , &unum); + num = unum; + } else + res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3); + if (res < 1) + return -1LL; + else if (1 == res) + return num; + else { + if (res > 2) + c2 = toupper((int)c2); + if (res > 3) + c3 = toupper((int)c3); + switch (toupper((int)c)) { + case 'C': + return num; + case 'W': + return num * 2; + case 'B': + return num * 512; + case 'K': + if (2 == res) + return num * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1024; + return -1LL; + case 'M': + if (2 == res) + return num * 1048576; + if (('B' == c2) || ('D' == c2)) + return num * 1000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1048576; + return -1LL; + case 'G': + if (2 == res) + return num * 1073741824; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1073741824; + return -1LL; + case 'T': + if (2 == res) + return num * 1099511627776LL; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000000LL; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1099511627776LL; + return -1LL; + case 'P': + if (2 == res) + return num * 1099511627776LL * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000000LL * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1099511627776LL * 1024; + return -1LL; + case 'X': + cp = (char *)strchr(b, 'x'); + if (NULL == cp) + cp = (char *)strchr(b, 'X'); + if (cp) { + ll = sg_get_llnum(cp + 1); + if (-1LL != ll) + return num * ll; + } + return -1LL; + default: + pr2ws("unrecognized multiplier\n"); + return -1LL; + } + } +} + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int64_t +sg_get_llnum_nomult(const char * buf) +{ + int res, len; + int64_t num; + uint64_t unum; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { + res = sscanf(buf + 2, "%" SCNx64 "", &unum); + num = unum; + } else if ('H' == toupper(buf[len - 1])) { + res = sscanf(buf, "%" SCNx64 "", &unum); + num = unum; + } else + res = sscanf(buf, "%" SCNd64 "", &num); + return (1 == res) ? num : -1; +} + +/* Extract character sequence from ATA words as in the model string + * in a IDENTIFY DEVICE response. Returns number of characters + * written to 'ochars' before 0 character is found or 'num' words + * are processed. */ +int +sg_ata_get_chars(const uint16_t * word_arr, int start_word, + int num_words, bool is_big_endian, char * ochars) +{ + int k; + uint16_t s; + char a, b; + char * op = ochars; + + for (k = start_word; k < (start_word + num_words); ++k) { + s = word_arr[k]; + if (is_big_endian) { + a = s & 0xff; + b = (s >> 8) & 0xff; + } else { + a = (s >> 8) & 0xff; + b = s & 0xff; + } + if (a == 0) + break; + *op++ = a; + if (b == 0) + break; + *op++ = b; + } + return op - ochars; +} + +int +pr2serr(const char * fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + n = vfprintf(stderr, fmt, args); + va_end(args); + return n; +} + +#ifdef SG_LIB_FREEBSD +#include <sys/param.h> +#elif defined(SG_LIB_WIN32) +#include <windows.h> + +static bool got_page_size = false; +static uint32_t win_page_size; +#endif + +uint32_t +sg_get_page_size(void) +{ +#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) + return sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */ +#elif defined(SG_LIB_WIN32) + if (! got_page_size) { + SYSTEM_INFO si; + + GetSystemInfo(&si); + win_page_size = si.dwPageSize; + got_page_size = true; + } + return win_page_size; +#elif defined(SG_LIB_FREEBSD) + return PAGE_SIZE; +#else + return 4096; /* give up, pick likely figure */ +#endif +} + +/* Returns pointer to heap (or NULL) that is aligned to a align_to byte + * boundary. Sends back *buff_to_free pointer in third argument that may be + * different from the return value. If it is different then the *buff_to_free + * pointer should be freed (rather than the returned value) when the heap is + * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all + * returned heap to zeros. If num_bytes is 0 then set to page size. */ +uint8_t * +sg_memalign(uint32_t num_bytes, uint32_t align_to, uint8_t ** buff_to_free, + bool vb) +{ + size_t psz; + uint8_t * res; + + if (buff_to_free) /* make sure buff_to_free is NULL if alloc fails */ + *buff_to_free = NULL; + psz = (align_to > 0) ? align_to : sg_get_page_size(); + if (0 == num_bytes) + num_bytes = psz; /* ugly to handle otherwise */ + +#ifdef HAVE_POSIX_MEMALIGN + { + int err; + void * wp = NULL; + + err = posix_memalign(&wp, psz, num_bytes); + if (err || (NULL == wp)) { + pr2ws("%s: posix_memalign: error [%d], out of memory?\n", + __func__, err); + return NULL; + } + memset(wp, 0, num_bytes); + if (buff_to_free) + *buff_to_free = (uint8_t *)wp; + res = (uint8_t *)wp; + if (vb) { + pr2ws("%s: posix_ma, len=%d, ", __func__, num_bytes); + if (buff_to_free) + pr2ws("wrkBuffp=%p, ", (void *)res); + pr2ws("psz=%u, rp=%p\n", (unsigned int)psz, (void *)res); + } + return res; + } +#else + { + void * wrkBuff; + sg_uintptr_t align_1 = psz - 1; + + wrkBuff = (uint8_t *)calloc(num_bytes + psz, 1); + if (NULL == wrkBuff) { + if (buff_to_free) + *buff_to_free = NULL; + return NULL; + } else if (buff_to_free) + *buff_to_free = (uint8_t *)wrkBuff; + res = (uint8_t *)(void *) + (((sg_uintptr_t)wrkBuff + align_1) & (~align_1)); + if (vb) { + pr2ws("%s: hack, len=%d, ", __func__, num_bytes); + if (buff_to_free) + pr2ws("buff_to_free=%p, ", wrkBuff); + pr2ws("align_1=%lu, rp=%p\n", align_1, (void *)res); + } + return res; + } +#endif +} + +const char * +sg_lib_version() +{ + return sg_lib_version_str; +} + + +#ifdef SG_LIB_MINGW +/* Non Unix OSes distinguish between text and binary files. + Set text mode on fd. Does nothing in Unix. Returns negative number on + failure. */ + +#include <unistd.h> +#include <fcntl.h> + +int +sg_set_text_mode(int fd) +{ + return setmode(fd, O_TEXT); +} + +/* Set binary mode on fd. Does nothing in Unix. Returns negative number on + failure. */ +int +sg_set_binary_mode(int fd) +{ + return setmode(fd, O_BINARY); +} + +#else +/* For Unix the following functions are dummies. */ +int +sg_set_text_mode(int fd) +{ + return fd; /* fd should be >= 0 */ +} + +int +sg_set_binary_mode(int fd) +{ + return fd; +} + +#endif diff --git a/tools/sg_write_buffer/sg_lib_data.c b/tools/sg_write_buffer/sg_lib_data.c new file mode 100644 index 0000000..211054d --- /dev/null +++ b/tools/sg_write_buffer/sg_lib_data.c @@ -0,0 +1,1684 @@ +/* + * Copyright (c) 2007-2018 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. + */ + +#include <stdlib.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#else +#define SG_SCSI_STRINGS 1 +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" + + +const char * sg_lib_version_str = "2.38 20180122";/* spc5r17, sbc4r15 */ + + +/* indexed by pdt; those that map to own index do not decay */ +int sg_lib_pdt_decay_arr[32] = { + PDT_DISK, PDT_TAPE, PDT_TAPE /* printer */, PDT_PROCESSOR, + PDT_DISK /* WO */, PDT_MMC, PDT_SCANNER, PDT_DISK /* optical */, + PDT_MCHANGER, PDT_COMMS, 0xa, 0xb, + PDT_SAC, PDT_SES, PDT_DISK /* rbc */, PDT_OCRW, + PDT_BCC, PDT_OSD, PDT_TAPE /* adc */, PDT_SMD, + PDT_DISK /* zbc */, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, PDT_WLUN, PDT_UNKNOWN +}; + +#ifdef SG_SCSI_STRINGS +struct sg_lib_value_name_t sg_lib_normal_opcodes[] = { + {0, 0, "Test Unit Ready"}, + {0x1, 0, "Rezero Unit"}, + {0x1, PDT_TAPE, "Rewind"}, + {0x3, 0, "Request Sense"}, + {0x4, 0, "Format Unit"}, + {0x4, PDT_TAPE, "Format medium"}, + {0x4, PDT_PRINTER, "Format"}, + {0x5, 0, "Read Block Limits"}, + {0x7, 0, "Reassign Blocks"}, + {0x7, PDT_MCHANGER, "Initialize element status"}, + {0x8, 0, "Read(6)"}, /* obsolete in sbc3r30 */ + {0x8, PDT_PROCESSOR, "Receive"}, + {0xa, 0, "Write(6)"}, /* obsolete in sbc3r30 */ + {0xa, PDT_PRINTER, "Print"}, + {0xa, PDT_PROCESSOR, "Send"}, + {0xb, 0, "Seek(6)"}, + {0xb, PDT_TAPE, "Set capacity"}, + {0xb, PDT_PRINTER, "Slew and print"}, + {0xf, 0, "Read reverse(6)"}, + {0x10, 0, "Write filemarks(6)"}, + {0x10, PDT_PRINTER, "Synchronize buffer"}, + {0x11, 0, "Space(6)"}, + {0x12, 0, "Inquiry"}, + {0x13, 0, "Verify(6)"}, /* SSC */ + {0x14, 0, "Recover buffered data"}, + {0x15, 0, "Mode select(6)"}, /* SBC-3 r31 recommends Mode select(10) */ + {0x16, 0, "Reserve(6)"}, /* obsolete in SPC-4 r11 */ + {0x16, PDT_MCHANGER, "Reserve element(6)"}, + {0x17, 0, "Release(6)"}, /* obsolete in SPC-4 r11 */ + {0x17, PDT_MCHANGER, "Release element(6)"}, + {0x18, 0, "Copy"}, /* obsolete in SPC-4 r11 */ + {0x19, 0, "Erase(6)"}, + {0x1a, 0, "Mode sense(6)"}, /* SBC-3 r31 recommends Mode sense(10) */ + {0x1b, 0, "Start stop unit"}, + {0x1b, PDT_TAPE, "Load unload"}, + {0x1b, PDT_ADC, "Load unload"}, + {0x1b, PDT_PRINTER, "Stop print"}, + {0x1c, 0, "Receive diagnostic results"}, + {0x1d, 0, "Send diagnostic"}, + {0x1e, 0, "Prevent allow medium removal"}, + {0x23, 0, "Read Format capacities"}, + {0x24, 0, "Set window"}, + {0x25, 0, "Read capacity(10)"}, + /* SBC-3 r31 recommends Read capacity(16) */ + {0x25, PDT_OCRW, "Read card capacity"}, + {0x28, 0, "Read(10)"}, /* SBC-3 r31 recommends Read(16) */ + {0x29, 0, "Read generation"}, + {0x2a, 0, "Write(10)"}, /* SBC-3 r31 recommends Write(16) */ + {0x2b, 0, "Seek(10)"}, + {0x2b, PDT_TAPE, "Locate(10)"}, + {0x2b, PDT_MCHANGER, "Position to element"}, + {0x2c, 0, "Erase(10)"}, + {0x2d, 0, "Read updated block"}, + {0x2e, 0, "Write and verify(10)"}, + /* SBC-3 r31 recommends Write and verify(16) */ + {0x2f, 0, "Verify(10)"}, /* SBC-3 r31 recommends Verify(16) */ + {0x30, 0, "Search data high(10)"}, + {0x31, 0, "Search data equal(10)"}, + {0x32, 0, "Search data low(10)"}, + {0x33, 0, "Set limits(10)"}, + {0x34, 0, "Pre-fetch(10)"}, /* SBC-3 r31 recommends Pre-fetch(16) */ + {0x34, PDT_TAPE, "Read position"}, + {0x35, 0, "Synchronize cache(10)"}, + /* SBC-3 r31 recommends Synchronize cache(16) */ + {0x36, 0, "Lock unlock cache(10)"}, + {0x37, 0, "Read defect data(10)"}, + /* SBC-3 r31 recommends Read defect data(12) */ + {0x37, PDT_MCHANGER, "Initialize element status with range"}, + {0x38, 0, "Medium scan"}, + {0x39, 0, "Compare"}, /* obsolete in SPC-4 r11 */ + {0x3a, 0, "Copy and verify"}, /* obsolete in SPC-4 r11 */ + {0x3b, 0, "Write buffer"}, + {0x3c, 0, "Read buffer(10)"}, + {0x3d, 0, "Update block"}, + {0x3e, 0, "Read long(10)"}, /* obsolete in SBC-4 r7 */ + {0x3f, 0, "Write long(10)"}, /* SBC-3 r31 recommends Write long(16) */ + {0x40, 0, "Change definition"}, /* obsolete in SPC-4 r11 */ + {0x41, 0, "Write same(10)"}, /* SBC-3 r31 recommends Write same(16) */ + {0x42, 0, "Unmap"}, /* added SPC-4 rev 18 */ + {0x42, PDT_MMC, "Read sub-channel"}, + {0x43, PDT_MMC, "Read TOC/PMA/ATIP"}, + {0x44, 0, "Report density support"}, + {0x45, PDT_MMC, "Play audio(10)"}, + {0x46, PDT_MMC, "Get configuration"}, + {0x47, PDT_MMC, "Play audio msf"}, + {0x48, 0, "Sanitize"}, + {0x4a, PDT_MMC, "Get event status notification"}, + {0x4b, PDT_MMC, "Pause/resume"}, + {0x4c, 0, "Log select"}, + {0x4d, 0, "Log sense"}, + {0x4e, 0, "Stop play/scan"}, + {0x50, 0, "Xdwrite(10)"}, /* obsolete in SBC-3 r31 */ + {0x51, 0, "Xpwrite(10)"}, /* obsolete in SBC-4 r15 */ + {0x51, PDT_MMC, "Read disk information"}, + {0x52, 0, "Xdread(10)"}, /* obsolete in SBC-3 r31 */ + {0x52, PDT_MMC, "Read track information"}, + {0x53, 0, "Xdwriteread(10)"}, /* obsolete in SBC-4 r15 */ + {0x54, 0, "Send OPC information"}, + {0x55, 0, "Mode select(10)"}, + {0x56, 0, "Reserve(10)"}, /* obsolete in SPC-4 r11 */ + {0x56, PDT_MCHANGER, "Reserve element(10)"}, + {0x57, 0, "Release(10)"}, /* obsolete in SPC-4 r11 */ + {0x57, PDT_MCHANGER, "Release element(10)"}, + {0x58, 0, "Repair track"}, + {0x5a, 0, "Mode sense(10)"}, + {0x5b, 0, "Close track/session"}, + {0x5c, 0, "Read buffer capacity"}, + {0x5d, 0, "Send cue sheet"}, + {0x5e, 0, "Persistent reserve in"}, + {0x5f, 0, "Persistent reserve out"}, + {0x7e, 0, "Extended cdb (XCBD)"}, /* added in SPC-4 r12 */ + {0x80, 0, "Xdwrite extended(16)"}, /* obsolete in SBC-4 r15 */ + {0x80, PDT_TAPE, "Write filemarks(16)"}, + {0x81, 0, "Rebuild(16)"}, + {0x81, PDT_TAPE, "Read reverse(16)"}, + {0x82, 0, "Regenerate(16)"}, + {0x83, 0, "Third party copy out"}, /* Extended copy, before spc4r34 */ + /* Following was "Receive copy results", before spc4r34 */ + {0x84, 0, "Third party copy in"}, + {0x85, 0, "ATA pass-through(16)"}, /* was 0x98 in spc3 rev21c */ + {0x86, 0, "Access control in"}, + {0x87, 0, "Access control out"}, + {0x88, 0, "Read(16)"}, + {0x89, 0, "Compare and write"}, + {0x8a, 0, "Write(16)"}, + {0x8b, 0, "Orwrite(16)"}, + {0x8c, 0, "Read attribute"}, + {0x8d, 0, "Write attribute"}, + {0x8e, 0, "Write and verify(16)"}, + {0x8f, 0, "Verify(16)"}, + {0x90, 0, "Pre-fetch(16)"}, + {0x91, 0, "Synchronize cache(16)"}, + {0x91, PDT_TAPE, "Space(16)"}, + {0x92, 0, "Lock unlock cache(16)"}, + {0x92, PDT_TAPE, "Locate(16)"}, + {0x93, 0, "Write same(16)"}, + {0x93, PDT_TAPE, "Erase(16)"}, + {0x94, PDT_ZBC, "ZBC out"}, /* new sbc4r04, has service actions */ + {0x95, PDT_ZBC, "ZBC in"}, /* new sbc4r04, has service actions */ + {0x9a, 0, "Write stream(16)"}, /* added sbc4r07 */ + {0x9b, 0, "Read buffer(16)"}, /* added spc5r02 */ + {0x9c, 0, "Write atomic(16)"}, + {0x9d, 0, "Service action bidirectional"}, /* added spc4r35 */ + {0x9e, 0, "Service action in(16)"}, + {0x9f, 0, "Service action out(16)"}, + {0xa0, 0, "Report luns"}, + {0xa1, 0, "ATA pass-through(12)"}, + {0xa1, PDT_MMC, "Blank"}, + {0xa2, 0, "Security protocol in"}, + {0xa3, 0, "Maintenance in"}, + {0xa3, PDT_MMC, "Send key"}, + {0xa4, 0, "Maintenance out"}, + {0xa4, PDT_MMC, "Report key"}, + {0xa5, 0, "Move medium"}, + {0xa5, PDT_MMC, "Play audio(12)"}, + {0xa6, 0, "Exchange medium"}, + {0xa6, PDT_MMC, "Load/unload medium"}, + {0xa7, 0, "Move medium attached"}, + {0xa7, PDT_MMC, "Set read ahead"}, + {0xa8, 0, "Read(12)"}, /* SBC-3 r31 recommends Read(16) */ + {0xa9, 0, "Service action out(12)"}, + {0xaa, 0, "Write(12)"}, /* SBC-3 r31 recommends Write(16) */ + {0xab, 0, "Service action in(12)"}, + {0xac, 0, "erase(12)"}, + {0xac, PDT_MMC, "Get performance"}, + {0xad, PDT_MMC, "Read DVD/BD structure"}, + {0xae, 0, "Write and verify(12)"}, + /* SBC-3 r31 recommends Write and verify(16) */ + {0xaf, 0, "Verify(12)"}, /* SBC-3 r31 recommends Verify(16) */ + {0xb0, 0, "Search data high(12)"}, + {0xb1, 0, "Search data equal(12)"}, + {0xb1, PDT_MCHANGER, "Open/close import/export element"}, + {0xb2, 0, "Search data low(12)"}, + {0xb3, 0, "Set limits(12)"}, + {0xb4, 0, "Read element status attached"}, + {0xb5, 0, "Security protocol out"}, + {0xb5, PDT_MCHANGER, "Request volume element address"}, + {0xb6, 0, "Send volume tag"}, + {0xb6, PDT_MMC, "Set streaming"}, + {0xb7, 0, "Read defect data(12)"}, + {0xb8, 0, "Read element status"}, + {0xb9, 0, "Read CD msf"}, + {0xba, 0, "Redundancy group in"}, + {0xba, PDT_MMC, "Scan"}, + {0xbb, 0, "Redundancy group out"}, + {0xbb, PDT_MMC, "Set CD speed"}, + {0xbc, 0, "Spare in"}, + {0xbd, 0, "Spare out"}, + {0xbd, PDT_MMC, "Mechanism status"}, + {0xbe, 0, "Volume set in"}, + {0xbe, PDT_MMC, "Read CD"}, + {0xbf, 0, "Volume set out"}, + {0xbf, PDT_MMC, "Send DVD/BD structure"}, + {0xffff, 0, NULL}, +}; + +/* Read buffer(10) [0x3c] and Read buffer(16) [0x9b] service actions (sa), + * need prefix */ +struct sg_lib_value_name_t sg_lib_read_buff_arr[] = { + {0x0, 0, "combined header and data [or multiple modes]"}, + {0x2, 0, "data"}, + {0x3, 0, "descriptor"}, + {0xa, 0, "read data from echo buffer"}, + {0xb, 0, "echo buffer descriptor"}, + {0x1a, 0, "enable expander comms protocol and echo buffer"}, + {0x1c, 0, "error history"}, + {0xffff, 0, NULL}, +}; + +/* Write buffer [0x3b] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_write_buff_arr[] = { + {0x0, 0, "combined header and data [or multiple modes]"}, + {0x2, 0, "data"}, + {0x4, 0, "download microcode and activate"}, + {0x5, 0, "download microcode, save, and activate"}, + {0x6, 0, "download microcode with offsets and activate"}, + {0x7, 0, "download microcode with offsets, save, and activate"}, + {0xa, 0, "write data to echo buffer"}, + {0xd, 0, "download microcode with offsets, select activation events, " + "save and defer activate"}, + {0xe, 0, "download microcode with offsets, save and defer activate"}, + {0xf, 0, "activate deferred microcode"}, + {0x1a, 0, "enable expander comms protocol and echo buffer"}, + {0x1b, 0, "disable expander comms protocol"}, + {0x1c, 0, "download application client error history"}, + {0xffff, 0, NULL}, +}; + +/* Read position (SSC) [0x34] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_read_pos_arr[] = { + {0x0, PDT_TAPE, "short form - block id"}, + {0x1, PDT_TAPE, "short form - vendor specific"}, + {0x6, PDT_TAPE, "long form"}, + {0x8, PDT_TAPE, "extended form"}, + {0xffff, 0, NULL}, +}; + +/* Maintenance in [0xa3] service actions */ +struct sg_lib_value_name_t sg_lib_maint_in_arr[] = { + {0x0, PDT_SAC, "Report assigned/unassigned p_extent"}, + {0x1, PDT_SAC, "Report component device"}, + {0x2, PDT_SAC, "Report component device attachments"}, + {0x3, PDT_SAC, "Report peripheral device"}, + {0x4, PDT_SAC, "Report peripheral device associations"}, + {0x5, 0, "Report identifying information"}, + /* was "Report device identifier" prior to spc4r07 */ + {0x6, PDT_SAC, "Report states"}, + {0x7, PDT_SAC, "Report device identification"}, + {0x8, PDT_SAC, "Report unconfigured capacity"}, + {0x9, PDT_SAC, "Report supported configuration method"}, + {0xa, 0, "Report target port groups"}, + {0xb, 0, "Report aliases"}, + {0xc, 0, "Report supported operation codes"}, + {0xd, 0, "Report supported task management functions"}, + {0xe, 0, "Report priority"}, + {0xf, 0, "Report timestamp"}, + {0x10, 0, "Management protocol in"}, + {0x1d, PDT_DISK, "Report provisioning initialization pattern"}, + /* added in sbc4r07, shares sa 0x1d with ssc5r01 (tape) */ + {0x1d, PDT_TAPE, "Receive recommended access order"}, + {0x1e, PDT_TAPE, "Read dynamic runtime attribute"}, + {0x1e, PDT_ADC, "Report automation device attributes"}, + {0x1f, 0, "Maintenance in vendor specific"}, + {0xffff, 0, NULL}, +}; + +/* Maintenance out [0xa4] service actions */ +struct sg_lib_value_name_t sg_lib_maint_out_arr[] = { + {0x0, PDT_SAC, "Add peripheral device / component device"}, + {0x1, PDT_SAC, "Attach to component device"}, + {0x2, PDT_SAC, "Exchange p_extent"}, + {0x3, PDT_SAC, "Exchange peripheral device / component device"}, + {0x4, PDT_SAC, "Instruct component device"}, + {0x5, PDT_SAC, "Remove peripheral device / component device"}, + {0x6, 0, "Set identifying information"}, + /* was "Set device identifier" prior to spc4r07 */ + {0x7, PDT_SAC, "Break peripheral device / component device"}, + {0xa, 0, "Set target port groups"}, + {0xb, 0, "Change aliases"}, + {0xc, 0, "Remove I_T nexus"}, + {0xe, 0, "Set priority"}, + {0xf, 0, "Set timestamp"}, + {0x10, 0, "Management protocol out"}, + {0x1d, PDT_TAPE, "Generate recommended access order"}, + {0x1e, PDT_TAPE, "write dynamic runtime attribute"}, + {0x1e, PDT_ADC, "Set automation device attributes"}, + {0x1f, 0, "Maintenance out vendor specific"}, + {0xffff, 0, NULL}, +}; + +/* Sanitize [0x48] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = { + {0x1, 0, "overwrite"}, + {0x2, 0, "block erase"}, + {0x3, 0, "cryptographic erase"}, + {0x1f, 0, "exit failure mode"}, + {0xffff, 0, NULL}, +}; + +/* Service action in(12) [0xab] service actions */ +struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { + {0x1, 0, "Read media serial number"}, + {0xffff, 0, NULL}, +}; + +/* Service action out(12) [0xa9] service actions */ +struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { + {0xff, 0, "Impossible command name"}, + {0xffff, 0, NULL}, +}; + +/* Service action in(16) [0x9e] service actions */ +struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { + {0xf, 0, "Receive binding report"}, /* added spc5r11 */ + {0x10, 0, "Read capacity(16)"}, + {0x11, 0, "Read long(16)"}, /* obsolete in SBC-4 r7 */ + {0x12, 0, "Get LBA status(16)"}, /* 32 byte variant added in sbc4r14 */ + {0x13, 0, "Report referrals"}, + {0x14, 0, "Stream control"}, + {0x15, 0, "Background control"}, + {0x16, 0, "Get stream status"}, + {0x17, 0, "Get physical element status"}, /* added sbc4r13 */ + {0x18, 0, "Remove element and truncate"}, /* added sbc4r13 */ + {0xffff, 0, NULL}, +}; + +/* Service action out(16) [0x9f] service actions */ +struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { + {0x0b, 0, "Test bind"}, /* added spc5r13 */ + {0x0c, 0, "Prepare bind report"}, /* added spc5r11 */ + {0x0d, 0, "Set affiliation"}, + {0x0e, 0, "Bind"}, + {0x0f, 0, "Unbind"}, + {0x11, 0, "Write long(16)"}, + {0x12, 0, "Write scattered(16)"}, /* added sbc4r11 */ + {0x14, PDT_ZBC, "Reset write pointer"}, + {0x1f, PDT_ADC, "Notify data transfer device(16)"}, + {0xffff, 0, NULL}, +}; + +/* Service action bidirectional [0x9d] service actions */ +struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { + {0xffff, 0, NULL}, +}; + +/* Persistent reserve in [0x5e] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { + {0x0, 0, "read keys"}, + {0x1, 0, "read reservation"}, + {0x2, 0, "report capabilities"}, + {0x3, 0, "read full status"}, + {0xffff, 0, NULL}, +}; + +/* Persistent reserve out [0x5f] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { + {0x0, 0, "register"}, + {0x1, 0, "reserve"}, + {0x2, 0, "release"}, + {0x3, 0, "clear"}, + {0x4, 0, "preempt"}, + {0x5, 0, "preempt and abort"}, + {0x6, 0, "register and ignore existing key"}, + {0x7, 0, "register and move"}, + {0x8, 0, "replace lost reservation"}, + {0xffff, 0, NULL}, +}; + +/* Third party copy in [0x83] service actions + * Opcode 'Receive copy results' was renamed 'Third party copy in' in spc4r34 + * LID1 is an abbreviation of List Identifier length of 1 byte */ +struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { + {0x0, 0, "Extended copy(LID1)"}, + {0x1, 0, "Extended copy(LID4)"}, + {0x10, 0, "Populate token"}, + {0x11, 0, "Write using token"}, + {0x1c, 0, "Copy operation abort"}, + {0xffff, 0, NULL}, +}; + +/* Third party copy out [0x84] service actions + * Opcode 'Extended copy' was renamed 'Third party copy out' in spc4r34 + * LID4 is an abbreviation of List Identifier length of 4 bytes */ +struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { + {0x0, 0, "Receive copy status(LID1)"}, + {0x1, 0, "Receive copy data(LID1)"}, + {0x3, 0, "Receive copy operating parameters"}, + {0x4, 0, "Receive copy failure details(LID1)"}, + {0x5, 0, "Receive copy status(LID4)"}, + {0x6, 0, "Receive copy data(LID4)"}, + {0x7, 0, "Receive ROD token information"}, + {0x8, 0, "Report all ROD tokens"}, + {0xffff, 0, NULL}, +}; + +/* Variable length cdb [0x7f] service actions (more than 16 bytes long) */ +struct sg_lib_value_name_t sg_lib_variable_length_arr[] = { + {0x1, 0, "Rebuild(32)"}, + {0x2, 0, "Regenerate(32)"}, + {0x3, 0, "Xdread(32)"}, /* obsolete in SBC-3 r31 */ + {0x4, 0, "Xdwrite(32)"}, /* obsolete in SBC-3 r31 */ + {0x5, 0, "Xdwrite extended(32)"}, /* obsolete in SBC-4 r15 */ + {0x6, 0, "Xpwrite(32)"}, /* obsolete in SBC-4 r15 */ + {0x7, 0, "Xdwriteread(32)"}, /* obsolete in SBC-4 r15 */ + {0x8, 0, "Xdwrite extended(64)"}, /* obsolete in SBC-4 r15 */ + {0x9, 0, "Read(32)"}, + {0xa, 0, "Verify(32)"}, + {0xb, 0, "Write(32)"}, + {0xc, 0, "Write and verify(32)"}, + {0xd, 0, "Write same(32)"}, + {0xe, 0, "Orwrite(32)"}, /* added sbc3r25 */ + {0xf, 0, "Atomic write(32)"}, /* added sbc4r02 */ + {0x10, 0, "Write stream(32)"}, /* added sbc4r07 */ + {0x11, 0, "Write scattered(32)"}, /* added sbc4r11 */ + {0x12, 0, "Get LBA status(32)"}, /* added sbc4r14 */ + {0x1800, 0, "Receive credential"}, + {0x1ff0, 0, "ATA pass-through(32)"},/* added sat4r05 */ + {0x8801, 0, "Format OSD (osd)"}, + {0x8802, 0, "Create (osd)"}, + {0x8803, 0, "List (osd)"}, + {0x8805, 0, "Read (osd)"}, + {0x8806, 0, "Write (osd)"}, + {0x8807, 0, "Append (osd)"}, + {0x8808, 0, "Flush (osd)"}, + {0x880a, 0, "Remove (osd)"}, + {0x880b, 0, "Create partition (osd)"}, + {0x880c, 0, "Remove partition (osd)"}, + {0x880e, 0, "Get attributes (osd)"}, + {0x880f, 0, "Set attributes (osd)"}, + {0x8812, 0, "Create and write (osd)"}, + {0x8815, 0, "Create collection (osd)"}, + {0x8816, 0, "Remove collection (osd)"}, + {0x8817, 0, "List collection (osd)"}, + {0x8818, 0, "Set key (osd)"}, + {0x8819, 0, "Set master key (osd)"}, + {0x881a, 0, "Flush collection (osd)"}, + {0x881b, 0, "Flush partition (osd)"}, + {0x881c, 0, "Flush OSD (osd)"}, + {0x8880, 0, "Object structure check (osd-2)"}, + {0x8881, 0, "Format OSD (osd-2)"}, + {0x8882, 0, "Create (osd-2)"}, + {0x8883, 0, "List (osd-2)"}, + {0x8884, 0, "Punch (osd-2)"}, + {0x8885, 0, "Read (osd-2)"}, + {0x8886, 0, "Write (osd-2)"}, + {0x8887, 0, "Append (osd-2)"}, + {0x8888, 0, "Flush (osd-2)"}, + {0x8889, 0, "Clear (osd-2)"}, + {0x888a, 0, "Remove (osd-2)"}, + {0x888b, 0, "Create partition (osd-2)"}, + {0x888c, 0, "Remove partition (osd-2)"}, + {0x888e, 0, "Get attributes (osd-2)"}, + {0x888f, 0, "Set attributes (osd-2)"}, + {0x8892, 0, "Create and write (osd-2)"}, + {0x8895, 0, "Create collection (osd-2)"}, + {0x8896, 0, "Remove collection (osd-2)"}, + {0x8897, 0, "List collection (osd-2)"}, + {0x8898, 0, "Set key (osd-2)"}, + {0x8899, 0, "Set master key (osd-2)"}, + {0x889a, 0, "Flush collection (osd-2)"}, + {0x889b, 0, "Flush partition (osd-2)"}, + {0x889c, 0, "Flush OSD (osd-2)"}, + {0x88a0, 0, "Query (osd-2)"}, + {0x88a1, 0, "Remove member objects (osd-2)"}, + {0x88a2, 0, "Get member attributes (osd-2)"}, + {0x88a3, 0, "Set member attributes (osd-2)"}, + {0x88b1, 0, "Read map (osd-2)"}, + {0x8f7c, 0, "Perform SCSI command (osd-2)"}, + {0x8f7d, 0, "Perform task management function (osd-2)"}, + {0x8f7e, 0, "Perform SCSI command (osd)"}, + {0x8f7f, 0, "Perform task management function (osd)"}, + {0xffff, 0, NULL}, +}; + +/* Zoning out [0x94] service actions */ +struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = { + {0x1, PDT_ZBC, "Close zone"}, + {0x2, PDT_ZBC, "Finish zone"}, + {0x3, PDT_ZBC, "Open zone"}, + {0x4, PDT_ZBC, "Reset write pointer"}, + {0xffff, 0, NULL}, +}; + +/* Zoning in [0x95] service actions */ +struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = { + {0x0, PDT_ZBC, "Report zones"}, + {0xffff, 0, NULL}, +}; + +/* Read attribute [0x8c] service actions */ +struct sg_lib_value_name_t sg_lib_read_attr_arr[] = { + {0x0, 0, "attribute values"}, + {0x1, 0, "attribute list"}, + {0x2, 0, "logical volume list"}, + {0x3, 0, "partition list"}, + {0x5, 0, "supported attributes"}, + {0xffff, 0, NULL}, +}; + +#else /* SG_SCSI_STRINGS */ + +struct sg_lib_value_name_t sg_lib_normal_opcodes[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_read_buff_arr[] = { /* opcode 0x3c */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_write_buff_arr[] = { /* opcode 0x3b */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_maint_in_arr[] = { /* opcode 0xa3 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_maint_out_arr[] = { /* opcode 0xa4 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = { /* opcode 0x94 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { /* opcode 0xab */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { /* opcode 0xa9 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { /* opcode 0x9e */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { /* opcode 0x9f */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { /* opcode 0x9d */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { /* opcode 0x5e */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { /* opcode 0x5f */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { /* opcode 0x83 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { /* opcode 0x84 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_variable_length_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_read_attr_arr[] = { + {0xffff, 0, NULL}, +}; + +#endif /* SG_SCSI_STRINGS */ + +/* A conveniently formatted list of SCSI ASC/ASCQ codes and their + * corresponding text can be found at: www.t10.org/lists/asc-num.txt + * The following should match asc-num.txt dated 20150423 */ + +#ifdef SG_SCSI_STRINGS +struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] = +{ + {0x40,0x01,0x7f,"Ram failure [0x%x]"}, + {0x40,0x80,0xff,"Diagnostic failure on component [0x%x]"}, + {0x41,0x01,0xff,"Data path failure [0x%x]"}, + {0x42,0x01,0xff,"Power-on or self-test failure [0x%x]"}, + {0x4d,0x00,0xff,"Tagged overlapped commands [0x%x]"}, + {0x70,0x00,0xff,"Decompression exception short algorithm id of 0x%x"}, + {0, 0, 0, NULL} +}; + +struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] = +{ + {0x00,0x00,"No additional sense information"}, + {0x00,0x01,"Filemark detected"}, + {0x00,0x02,"End-of-partition/medium detected"}, + {0x00,0x03,"Setmark detected"}, + {0x00,0x04,"Beginning-of-partition/medium detected"}, + {0x00,0x05,"End-of-data detected"}, + {0x00,0x06,"I/O process terminated"}, + {0x00,0x07,"Programmable early warning detected"}, + {0x00,0x11,"Audio play operation in progress"}, + {0x00,0x12,"Audio play operation paused"}, + {0x00,0x13,"Audio play operation successfully completed"}, + {0x00,0x14,"Audio play operation stopped due to error"}, + {0x00,0x15,"No current audio status to return"}, + {0x00,0x16,"operation in progress"}, + {0x00,0x17,"Cleaning requested"}, + {0x00,0x18,"Erase operation in progress"}, + {0x00,0x19,"Locate operation in progress"}, + {0x00,0x1a,"Rewind operation in progress"}, + {0x00,0x1b,"Set capacity operation in progress"}, + {0x00,0x1c,"Verify operation in progress"}, + {0x00,0x1d,"ATA pass through information available"}, + {0x00,0x1e,"Conflicting SA creation request"}, + {0x00,0x1f,"Logical unit transitioning to another power condition"}, + {0x00,0x20,"Extended copy information available"}, + {0x00,0x21,"Atomic command aborted due to ACA"}, + {0x00,0x22,"Deferred microcode is pending"}, + {0x01,0x00,"No index/sector signal"}, + {0x02,0x00,"No seek complete"}, + {0x03,0x00,"Peripheral device write fault"}, + {0x03,0x01,"No write current"}, + {0x03,0x02,"Excessive write errors"}, + {0x04,0x00,"Logical unit not ready, cause not reportable"}, + {0x04,0x01,"Logical unit is in process of becoming ready"}, + {0x04,0x02,"Logical unit not ready, " + "initializing command required"}, + {0x04,0x03,"Logical unit not ready, " + "manual intervention required"}, + {0x04,0x04,"Logical unit not ready, format in progress"}, + {0x04,0x05,"Logical unit not ready, rebuild in progress"}, + {0x04,0x06,"Logical unit not ready, recalculation in progress"}, + {0x04,0x07,"Logical unit not ready, operation in progress"}, + {0x04,0x08,"Logical unit not ready, long write in progress"}, + {0x04,0x09,"Logical unit not ready, self-test in progress"}, + {0x04,0x0a,"Logical unit " + "not accessible, asymmetric access state transition"}, + {0x04,0x0b,"Logical unit " + "not accessible, target port in standby state"}, + {0x04,0x0c,"Logical unit " + "not accessible, target port in unavailable state"}, + {0x04,0x0d,"Logical unit not ready, structure check required"}, + {0x04,0x0e,"Logical unit not ready, security session in progress"}, + {0x04,0x10,"Logical unit not ready, " + "auxiliary memory not accessible"}, + {0x04,0x11,"Logical unit not ready, " + "notify (enable spinup) required"}, + {0x04,0x12,"Logical unit not ready, offline"}, + {0x04,0x13,"Logical unit not ready, SA creation in progress"}, + {0x04,0x14,"Logical unit not ready, space allocation in progress"}, + {0x04,0x15,"Logical unit not ready, robotics disabled"}, + {0x04,0x16,"Logical unit not ready, configuration required"}, + {0x04,0x17,"Logical unit not ready, calibration required"}, + {0x04,0x18,"Logical unit not ready, a door is open"}, + {0x04,0x19,"Logical unit not ready, operating in sequential mode"}, + {0x04,0x1a,"Logical unit not ready, start stop unit command in progress"}, + {0x04,0x1b,"Logical unit not ready, sanitize in progress"}, + {0x04,0x1c,"Logical unit not ready, additional power use not yet " + "granted"}, + {0x04,0x1d,"Logical unit not ready, configuration in progress"}, + {0x04,0x1e,"Logical unit not ready, microcode activation required"}, + {0x04,0x1f,"Logical unit not ready, microcode download required"}, + {0x04,0x20,"Logical unit not ready, logical unit reset required"}, + {0x04,0x21,"Logical unit not ready, hard reset required"}, + {0x04,0x22,"Logical unit not ready, power cycle required"}, + {0x04,0x23,"Logical unit not ready, affiliation required"}, + {0x05,0x00,"Logical unit does not respond to selection"}, + {0x06,0x00,"No reference position found"}, + {0x07,0x00,"Multiple peripheral devices selected"}, + {0x08,0x00,"Logical unit communication failure"}, + {0x08,0x01,"Logical unit communication time-out"}, + {0x08,0x02,"Logical unit communication parity error"}, + {0x08,0x03,"Logical unit communication CRC error (Ultra-DMA/32)"}, + {0x08,0x04,"Unreachable copy target"}, + {0x09,0x00,"Track following error"}, + {0x09,0x01,"Tracking servo failure"}, + {0x09,0x02,"Focus servo failure"}, + {0x09,0x03,"Spindle servo failure"}, + {0x09,0x04,"Head select fault"}, + {0x09,0x05,"Vibration induced tracking error"}, + {0x0A,0x00,"Error log overflow"}, + {0x0B,0x00,"Warning"}, + {0x0B,0x01,"Warning - specified temperature exceeded"}, + {0x0B,0x02,"Warning - enclosure degraded"}, + {0x0B,0x03,"Warning - background self-test failed"}, + {0x0B,0x04,"Warning - background pre-scan detected medium error"}, + {0x0B,0x05,"Warning - background medium scan detected medium error"}, + {0x0B,0x06,"Warning - non-volatile cache now volatile"}, + {0x0B,0x07,"Warning - degraded power to non-volatile cache"}, + {0x0B,0x08,"Warning - power loss expected"}, + {0x0B,0x09,"Warning - device statistics notification active"}, + {0x0B,0x0A,"Warning - high critical temperature limit exceeded"}, + {0x0B,0x0B,"Warning - low critical temperature limit exceeded"}, + {0x0B,0x0C,"Warning - high operating temperature limit exceeded"}, + {0x0B,0x0D,"Warning - low operating temperature limit exceeded"}, + {0x0B,0x0E,"Warning - high critical humidity limit exceeded"}, + {0x0B,0x0F,"Warning - low critical humidity limit exceeded"}, + {0x0B,0x10,"Warning - high operating humidity limit exceeded"}, + {0x0B,0x11,"Warning - low operating humidity limit exceeded"}, + {0x0B,0x12,"Warning - microcode security at risk"}, + {0x0B,0x13,"Warning - microcode digital signature validation failure"}, + {0x0C,0x00,"Write error"}, + {0x0C,0x01,"Write error - recovered with auto reallocation"}, + {0x0C,0x02,"Write error - auto reallocation failed"}, + {0x0C,0x03,"Write error - recommend reassignment"}, + {0x0C,0x04,"Compression check miscompare error"}, + {0x0C,0x05,"Data expansion occurred during compression"}, + {0x0C,0x06,"Block not compressible"}, + {0x0C,0x07,"Write error - recovery needed"}, + {0x0C,0x08,"Write error - recovery failed"}, + {0x0C,0x09,"Write error - loss of streaming"}, + {0x0C,0x0A,"Write error - padding blocks added"}, + {0x0C,0x0B,"Auxiliary memory write error"}, + {0x0C,0x0C,"Write error - unexpected unsolicited data"}, + {0x0C,0x0D,"Write error - not enough unsolicited data"}, + {0x0C,0x0E,"Multiple write errors"}, + {0x0C,0x0F,"Defects in error window"}, + {0x0C,0x10,"Incomplete multiple atomic write operations"}, + {0x0C,0x11,"Write error - recovery scan needed"}, + {0x0C,0x12,"Write error - insufficient zone resources"}, + {0x0D,0x00,"Error detected by third party temporary initiator"}, + {0x0D,0x01,"Third party device failure"}, + {0x0D,0x02,"Copy target device not reachable"}, + {0x0D,0x03,"Incorrect copy target device type"}, + {0x0D,0x04,"Copy target device data underrun"}, + {0x0D,0x05,"Copy target device data overrun"}, + {0x0E,0x00,"Invalid information unit"}, + {0x0E,0x01,"Information unit too short"}, + {0x0E,0x02,"Information unit too long"}, + {0x0E,0x03,"Invalid field in command information unit"}, + {0x10,0x00,"Id CRC or ECC error"}, + {0x10,0x01,"Logical block guard check failed"}, + {0x10,0x02,"Logical block application tag check failed"}, + {0x10,0x03,"Logical block reference tag check failed"}, + {0x10,0x04,"Logical block protection error on recover buffered data"}, + {0x10,0x05,"Logical block protection method error"}, + {0x11,0x00,"Unrecovered read error"}, + {0x11,0x01,"Read retries exhausted"}, + {0x11,0x02,"Error too long to correct"}, + {0x11,0x03,"Multiple read errors"}, + {0x11,0x04,"Unrecovered read error - auto reallocate failed"}, + {0x11,0x05,"L-EC uncorrectable error"}, + {0x11,0x06,"CIRC unrecovered error"}, + {0x11,0x07,"Data re-synchronization error"}, + {0x11,0x08,"Incomplete block read"}, + {0x11,0x09,"No gap found"}, + {0x11,0x0A,"Miscorrected error"}, + {0x11,0x0B,"Unrecovered read error - recommend reassignment"}, + {0x11,0x0C,"Unrecovered read error - recommend rewrite the data"}, + {0x11,0x0D,"De-compression CRC error"}, + {0x11,0x0E,"Cannot decompress using declared algorithm"}, + {0x11,0x0F,"Error reading UPC/EAN number"}, + {0x11,0x10,"Error reading ISRC number"}, + {0x11,0x11,"Read error - loss of streaming"}, + {0x11,0x12,"Auxiliary memory read error"}, + {0x11,0x13,"Read error - failed retransmission request"}, + {0x11,0x14,"Read error - LBA marked bad by application client"}, + {0x11,0x15,"Write after sanitize required"}, + {0x12,0x00,"Address mark not found for id field"}, + {0x13,0x00,"Address mark not found for data field"}, + {0x14,0x00,"Recorded entity not found"}, + {0x14,0x01,"Record not found"}, + {0x14,0x02,"Filemark or setmark not found"}, + {0x14,0x03,"End-of-data not found"}, + {0x14,0x04,"Block sequence error"}, + {0x14,0x05,"Record not found - recommend reassignment"}, + {0x14,0x06,"Record not found - data auto-reallocated"}, + {0x14,0x07,"Locate operation failure"}, + {0x15,0x00,"Random positioning error"}, + {0x15,0x01,"Mechanical positioning error"}, + {0x15,0x02,"Positioning error detected by read of medium"}, + {0x16,0x00,"Data synchronization mark error"}, + {0x16,0x01,"Data sync error - data rewritten"}, + {0x16,0x02,"Data sync error - recommend rewrite"}, + {0x16,0x03,"Data sync error - data auto-reallocated"}, + {0x16,0x04,"Data sync error - recommend reassignment"}, + {0x17,0x00,"Recovered data with no error correction applied"}, + {0x17,0x01,"Recovered data with retries"}, + {0x17,0x02,"Recovered data with positive head offset"}, + {0x17,0x03,"Recovered data with negative head offset"}, + {0x17,0x04,"Recovered data with retries and/or circ applied"}, + {0x17,0x05,"Recovered data using previous sector id"}, + {0x17,0x06,"Recovered data without ECC - data auto-reallocated"}, + {0x17,0x07,"Recovered data without ECC - recommend reassignment"}, + {0x17,0x08,"Recovered data without ECC - recommend rewrite"}, + {0x17,0x09,"Recovered data without ECC - data rewritten"}, + {0x18,0x00,"Recovered data with error correction applied"}, + {0x18,0x01,"Recovered data with error corr. & retries applied"}, + {0x18,0x02,"Recovered data - data auto-reallocated"}, + {0x18,0x03,"Recovered data with CIRC"}, + {0x18,0x04,"Recovered data with L-EC"}, + {0x18,0x05,"Recovered data - recommend reassignment"}, + {0x18,0x06,"Recovered data - recommend rewrite"}, + {0x18,0x07,"Recovered data with ECC - data rewritten"}, + {0x18,0x08,"Recovered data with linking"}, + {0x19,0x00,"Defect list error"}, + {0x19,0x01,"Defect list not available"}, + {0x19,0x02,"Defect list error in primary list"}, + {0x19,0x03,"Defect list error in grown list"}, + {0x1A,0x00,"Parameter list length error"}, + {0x1B,0x00,"Synchronous data transfer error"}, + {0x1C,0x00,"Defect list not found"}, + {0x1C,0x01,"Primary defect list not found"}, + {0x1C,0x02,"Grown defect list not found"}, + {0x1D,0x00,"Miscompare during verify operation"}, + {0x1D,0x01,"Miscompare verify of unmapped lba"}, + {0x1E,0x00,"Recovered id with ECC correction"}, + {0x1F,0x00,"Partial defect list transfer"}, + {0x20,0x00,"Invalid command operation code"}, + {0x20,0x01,"Access denied - initiator pending-enrolled"}, + {0x20,0x02,"Access denied - no access rights"}, + {0x20,0x03,"Access denied - invalid mgmt id key"}, + {0x20,0x04,"Illegal command while in write capable state"}, + {0x20,0x05,"Write type operation while in read capable state (obs)"}, + {0x20,0x06,"Illegal command while in explicit address mode"}, + {0x20,0x07,"Illegal command while in implicit address mode"}, + {0x20,0x08,"Access denied - enrollment conflict"}, + {0x20,0x09,"Access denied - invalid LU identifier"}, + {0x20,0x0A,"Access denied - invalid proxy token"}, + {0x20,0x0B,"Access denied - ACL LUN conflict"}, + {0x20,0x0C,"Illegal command when not in append-only mode"}, + {0x20,0x0D,"Not an administrative logical unit"}, + {0x20,0x0E,"Not a subsidiary logical unit"}, + {0x20,0x0F,"Not a conglomerate logical unit"}, + {0x21,0x00,"Logical block address out of range"}, + {0x21,0x01,"Invalid element address"}, + {0x21,0x02,"Invalid address for write"}, + {0x21,0x03,"Invalid write crossing layer jump"}, + {0x21,0x04,"Unaligned write command"}, + {0x21,0x05,"Write boundary violation"}, + {0x21,0x06,"Attempt to read invalid data"}, + {0x21,0x07,"Read boundary violation"}, + {0x21,0x08,"Misaligned write command"}, + {0x22,0x00,"Illegal function (use 20 00, 24 00, or 26 00)"}, + {0x23,0x00,"Invalid token operation, cause not reportable"}, + {0x23,0x01,"Invalid token operation, unsupported token type"}, + {0x23,0x02,"Invalid token operation, remote token usage not supported"}, + {0x23,0x03,"invalid token operation, remote rod token creation not " + "supported"}, + {0x23,0x04,"Invalid token operation, token unknown"}, + {0x23,0x05,"Invalid token operation, token corrupt"}, + {0x23,0x06,"Invalid token operation, token revoked"}, + {0x23,0x07,"Invalid token operation, token expired"}, + {0x23,0x08,"Invalid token operation, token cancelled"}, + {0x23,0x09,"Invalid token operation, token deleted"}, + {0x23,0x0a,"Invalid token operation, invalid token length"}, + {0x24,0x00,"Invalid field in cdb"}, + {0x24,0x01,"CDB decryption error"}, + {0x24,0x02,"Invalid cdb field while in explicit block model (obs)"}, + {0x24,0x03,"Invalid cdb field while in implicit block model (obs)"}, + {0x24,0x04,"Security audit value frozen"}, + {0x24,0x05,"Security working key frozen"}, + {0x24,0x06,"Nonce not unique"}, + {0x24,0x07,"Nonce timestamp out of range"}, + {0x24,0x08,"Invalid xcdb"}, + {0x24,0x09,"Invalid fast format"}, + {0x25,0x00,"Logical unit not supported"}, + {0x26,0x00,"Invalid field in parameter list"}, + {0x26,0x01,"Parameter not supported"}, + {0x26,0x02,"Parameter value invalid"}, + {0x26,0x03,"Threshold parameters not supported"}, + {0x26,0x04,"Invalid release of persistent reservation"}, + {0x26,0x05,"Data decryption error"}, + {0x26,0x06,"Too many target descriptors"}, + {0x26,0x07,"Unsupported target descriptor type code"}, + {0x26,0x08,"Too many segment descriptors"}, + {0x26,0x09,"Unsupported segment descriptor type code"}, + {0x26,0x0A,"Unexpected inexact segment"}, + {0x26,0x0B,"Inline data length exceeded"}, + {0x26,0x0C,"Invalid operation for copy source or destination"}, + {0x26,0x0D,"Copy segment granularity violation"}, + {0x26,0x0E,"Invalid parameter while port is enabled"}, + {0x26,0x0F,"Invalid data-out buffer integrity check value"}, + {0x26,0x10,"Data decryption key fail limit reached"}, + {0x26,0x11,"Incomplete key-associated data set"}, + {0x26,0x12,"Vendor specific key reference not found"}, + {0x26,0x13,"Application tag mode page is invalid"}, + {0x26,0x14,"Tape stream mirroring prevented"}, + {0x26,0x15,"Copy source or copy destination not authorized"}, + {0x27,0x00,"Write protected"}, + {0x27,0x01,"Hardware write protected"}, + {0x27,0x02,"Logical unit software write protected"}, + {0x27,0x03,"Associated write protect"}, + {0x27,0x04,"Persistent write protect"}, + {0x27,0x05,"Permanent write protect"}, + {0x27,0x06,"Conditional write protect"}, + {0x27,0x07,"Space allocation failed write protect"}, + {0x27,0x08,"Zone is read only"}, + {0x28,0x00,"Not ready to ready change, medium may have changed"}, + {0x28,0x01,"Import or export element accessed"}, + {0x28,0x02,"Format-layer may have changed"}, + {0x28,0x03,"Import/export element accessed, medium changed"}, + {0x29,0x00,"Power on, reset, or bus device reset occurred"}, + {0x29,0x01,"Power on occurred"}, + {0x29,0x02,"SCSI bus reset occurred"}, + {0x29,0x03,"Bus device reset function occurred"}, + {0x29,0x04,"Device internal reset"}, + {0x29,0x05,"Transceiver mode changed to single-ended"}, + {0x29,0x06,"Transceiver mode changed to lvd"}, + {0x29,0x07,"I_T nexus loss occurred"}, + {0x2A,0x00,"Parameters changed"}, + {0x2A,0x01,"Mode parameters changed"}, + {0x2A,0x02,"Log parameters changed"}, + {0x2A,0x03,"Reservations preempted"}, + {0x2A,0x04,"Reservations released"}, + {0x2A,0x05,"Registrations preempted"}, + {0x2A,0x06,"Asymmetric access state changed"}, + {0x2A,0x07,"Implicit asymmetric access state transition failed"}, + {0x2A,0x08,"Priority changed"}, + {0x2A,0x09,"Capacity data has changed"}, + {0x2A,0x0c, "Error recovery attributes have changed"}, + {0x2A,0x0d, "Data encryption capabilities changed"}, + {0x2A,0x10,"Timestamp changed"}, + {0x2A,0x11,"Data encryption parameters changed by another i_t nexus"}, + {0x2A,0x12,"Data encryption parameters changed by vendor specific event"}, + {0x2A,0x13,"Data encryption key instance counter has changed"}, + {0x2A,0x0a,"Error history i_t nexus cleared"}, + {0x2A,0x0b,"Error history snapshot released"}, + {0x2A,0x14,"SA creation capabilities data has changed"}, + {0x2A,0x15,"Medium removal prevention preempted"}, + {0x2A,0x16,"Zone reset write pointer recommended"}, + {0x2B,0x00,"Copy cannot execute since host cannot disconnect"}, + {0x2C,0x00,"Command sequence error"}, + {0x2C,0x01,"Too many windows specified"}, + {0x2C,0x02,"Invalid combination of windows specified"}, + {0x2C,0x03,"Current program area is not empty"}, + {0x2C,0x04,"Current program area is empty"}, + {0x2C,0x05,"Illegal power condition request"}, + {0x2C,0x06,"Persistent prevent conflict"}, + {0x2C,0x07,"Previous busy status"}, + {0x2C,0x08,"Previous task set full status"}, + {0x2C,0x09,"Previous reservation conflict status"}, + {0x2C,0x0A,"Partition or collection contains user objects"}, + {0x2C,0x0B,"Not reserved"}, + {0x2C,0x0C,"ORWRITE generation does not match"}, + {0x2C,0x0D,"Reset write pointer not allowed"}, + {0x2C,0x0E,"Zone is offline"}, + {0x2C,0x0F,"Stream not open"}, + {0x2C,0x10,"Unwritten data in zone"}, + {0x2C,0x11,"Descriptor format sense data required"}, + {0x2D,0x00,"Overwrite error on update in place"}, + {0x2E,0x00,"Insufficient time for operation"}, + {0x2E,0x01,"Command timeout before processing"}, + {0x2E,0x02,"Command timeout during processing"}, + {0x2E,0x03,"Command timeout during processing due to error recovery"}, + {0x2F,0x00,"Commands cleared by another initiator"}, + {0x2F,0x01,"Commands cleared by power loss notification"}, + {0x2F,0x02,"Commands cleared by device server"}, + {0x2F,0x03,"Some commands cleared by queuing layer event"}, + {0x30,0x00,"Incompatible medium installed"}, + {0x30,0x01,"Cannot read medium - unknown format"}, + {0x30,0x02,"Cannot read medium - incompatible format"}, + {0x30,0x03,"Cleaning cartridge installed"}, + {0x30,0x04,"Cannot write medium - unknown format"}, + {0x30,0x05,"Cannot write medium - incompatible format"}, + {0x30,0x06,"Cannot format medium - incompatible medium"}, + {0x30,0x07,"Cleaning failure"}, + {0x30,0x08,"Cannot write - application code mismatch"}, + {0x30,0x09,"Current session not fixated for append"}, + {0x30,0x0A,"Cleaning request rejected"}, + {0x30,0x0B,"Cleaning tape expired"}, + {0x30,0x0C,"WORM medium - overwrite attempted"}, + {0x30,0x0D,"WORM medium - integrity check"}, + {0x30,0x10,"Medium not formatted"}, + {0x30,0x11,"Incompatible volume type"}, + {0x30,0x12,"Incompatible volume qualifier"}, + {0x30,0x13,"Cleaning volume expired"}, + {0x31,0x00,"Medium format corrupted"}, + {0x31,0x01,"Format command failed"}, + {0x31,0x02,"Zoned formatting failed due to spare linking"}, + {0x31,0x03,"Sanitize command failed"}, + {0x32,0x00,"No defect spare location available"}, + {0x32,0x01,"Defect list update failure"}, + {0x33,0x00,"Tape length error"}, + {0x34,0x00,"Enclosure failure"}, + {0x35,0x00,"Enclosure services failure"}, + {0x35,0x01,"Unsupported enclosure function"}, + {0x35,0x02,"Enclosure services unavailable"}, + {0x35,0x03,"Enclosure services transfer failure"}, + {0x35,0x04,"Enclosure services transfer refused"}, + {0x35,0x05,"Enclosure services checksum error"}, + {0x36,0x00,"Ribbon, ink, or toner failure"}, + {0x37,0x00,"Rounded parameter"}, + {0x38,0x00,"Event status notification"}, + {0x38,0x02,"Esn - power management class event"}, + {0x38,0x04,"Esn - media class event"}, + {0x38,0x06,"Esn - device busy class event"}, + {0x38,0x07,"Thin provisioning soft threshold reached"}, + {0x39,0x00,"Saving parameters not supported"}, + {0x3A,0x00,"Medium not present"}, + {0x3A,0x01,"Medium not present - tray closed"}, + {0x3A,0x02,"Medium not present - tray open"}, + {0x3A,0x03,"Medium not present - loadable"}, + {0x3A,0x04,"Medium not present - medium auxiliary memory accessible"}, + {0x3B,0x00,"Sequential positioning error"}, + {0x3B,0x01,"Tape position error at beginning-of-medium"}, + {0x3B,0x02,"Tape position error at end-of-medium"}, + {0x3B,0x03,"Tape or electronic vertical forms unit not ready"}, + {0x3B,0x04,"Slew failure"}, + {0x3B,0x05,"Paper jam"}, + {0x3B,0x06,"Failed to sense top-of-form"}, + {0x3B,0x07,"Failed to sense bottom-of-form"}, + {0x3B,0x08,"Reposition error"}, + {0x3B,0x09,"Read past end of medium"}, + {0x3B,0x0A,"Read past beginning of medium"}, + {0x3B,0x0B,"Position past end of medium"}, + {0x3B,0x0C,"Position past beginning of medium"}, + {0x3B,0x0D,"Medium destination element full"}, + {0x3B,0x0E,"Medium source element empty"}, + {0x3B,0x0F,"End of medium reached"}, + {0x3B,0x11,"Medium magazine not accessible"}, + {0x3B,0x12,"Medium magazine removed"}, + {0x3B,0x13,"Medium magazine inserted"}, + {0x3B,0x14,"Medium magazine locked"}, + {0x3B,0x15,"Medium magazine unlocked"}, + {0x3B,0x16,"Mechanical positioning or changer error"}, + {0x3B,0x17,"Read past end of user object"}, + {0x3B,0x18,"Element disabled"}, + {0x3B,0x19,"Element enabled"}, + {0x3B,0x1a,"Data transfer device removed"}, + {0x3B,0x1b,"Data transfer device inserted"}, + {0x3B,0x1c,"Too many logical objects on partition to support operation"}, + {0x3D,0x00,"Invalid bits in identify message"}, + {0x3E,0x00,"Logical unit has not self-configured yet"}, + {0x3E,0x01,"Logical unit failure"}, + {0x3E,0x02,"Timeout on logical unit"}, + {0x3E,0x03,"Logical unit failed self-test"}, + {0x3E,0x04,"Logical unit unable to update self-test log"}, + {0x3F,0x00,"Target operating conditions have changed"}, + {0x3F,0x01,"Microcode has been changed"}, + {0x3F,0x02,"Changed operating definition"}, + {0x3F,0x03,"Inquiry data has changed"}, + {0x3F,0x04,"Component device attached"}, + {0x3F,0x05,"Device identifier changed"}, + {0x3F,0x06,"Redundancy group created or modified"}, + {0x3F,0x07,"Redundancy group deleted"}, + {0x3F,0x08,"Spare created or modified"}, + {0x3F,0x09,"Spare deleted"}, + {0x3F,0x0A,"Volume set created or modified"}, + {0x3F,0x0B,"Volume set deleted"}, + {0x3F,0x0C,"Volume set deassigned"}, + {0x3F,0x0D,"Volume set reassigned"}, + {0x3F,0x0E,"Reported luns data has changed"}, + {0x3F,0x0F,"Echo buffer overwritten"}, + {0x3F,0x10,"Medium loadable"}, + {0x3F,0x11,"Medium auxiliary memory accessible"}, + {0x3F,0x12,"iSCSI IP address added"}, + {0x3F,0x13,"iSCSI IP address removed"}, + {0x3F,0x14,"iSCSI IP address changed"}, + {0x3F,0x15,"Inspect referrals sense descriptors"}, + {0x3F,0x16,"Microcode has been changed without reset"}, + {0x3F,0x17,"Zone transition to full"}, + {0x3F,0x18,"Bind completed"}, + {0x3F,0x19,"Bind redirected"}, + {0x3F,0x1A,"Subsidiary binding changed"}, + + /* + * ASC 0x40, 0x41 and 0x42 overridden by "additional2" array entries + * for ascq > 1. Preferred error message for this group is + * "Diagnostic failure on component nn (80h-ffh)". + */ + {0x40,0x00,"Ram failure (should use 40 nn)"}, + {0x41,0x00,"Data path failure (should use 40 nn)"}, + {0x42,0x00,"Power-on or self-test failure (should use 40 nn)"}, + + {0x43,0x00,"Message error"}, + {0x44,0x00,"Internal target failure"}, + {0x44,0x01,"Persistent reservation information lost"}, + {0x44,0x71,"ATA device failed Set Features"}, + {0x45,0x00,"Select or reselect failure"}, + {0x46,0x00,"Unsuccessful soft reset"}, + {0x47,0x00,"SCSI parity error"}, + {0x47,0x01,"Data phase CRC error detected"}, + {0x47,0x02,"SCSI parity error detected during st data phase"}, + {0x47,0x03,"Information unit iuCRC error detected"}, + {0x47,0x04,"Asynchronous information protection error detected"}, + {0x47,0x05,"Protocol service CRC error"}, + {0x47,0x06,"Phy test function in progress"}, + {0x47,0x7F,"Some commands cleared by iSCSI protocol event"}, + {0x48,0x00,"Initiator detected error message received"}, + {0x49,0x00,"Invalid message error"}, + {0x4A,0x00,"Command phase error"}, + {0x4B,0x00,"Data phase error"}, + {0x4B,0x01,"Invalid target port transfer tag received"}, + {0x4B,0x02,"Too much write data"}, + {0x4B,0x03,"Ack/nak timeout"}, + {0x4B,0x04,"Nak received"}, + {0x4B,0x05,"Data offset error"}, + {0x4B,0x06,"Initiator response timeout"}, + {0x4B,0x07,"Connection lost"}, + {0x4B,0x08,"Data-in buffer overflow - data buffer size"}, + {0x4B,0x09,"Data-in buffer overflow - data buffer descriptor area"}, + {0x4B,0x0A,"Data-in buffer error"}, + {0x4B,0x0B,"Data-out buffer overflow - data buffer size"}, + {0x4B,0x0C,"Data-out buffer overflow - data buffer descriptor area"}, + {0x4B,0x0D,"Data-out buffer error"}, + {0x4B,0x0E,"PCIe fabric error"}, + {0x4B,0x0f,"PCIe completion timeout"}, + {0x4B,0x10,"PCIe completer abort"}, + {0x4B,0x11,"PCIe poisoned tlp received"}, + {0x4B,0x12,"PCIe ecrc check failed"}, + {0x4B,0x13,"PCIe unsupported request"}, + {0x4B,0x14,"PCIe acs violation"}, + {0x4B,0x15,"PCIe tlp prefix blocked"}, + {0x4C,0x00,"Logical unit failed self-configuration"}, + /* + * ASC 0x4D overridden by an "additional2" array entry + * so there is no need to have them here. + */ + /* {0x4D,0x00,"Tagged overlapped commands (nn = queue tag)"}, */ + + {0x4E,0x00,"Overlapped commands attempted"}, + {0x50,0x00,"Write append error"}, + {0x50,0x01,"Write append position error"}, + {0x50,0x02,"Position error related to timing"}, + {0x51,0x00,"Erase failure"}, + {0x51,0x01,"Erase failure - incomplete erase operation detected"}, + {0x52,0x00,"Cartridge fault"}, + {0x53,0x00,"Media load or eject failed"}, + {0x53,0x01,"Unload tape failure"}, + {0x53,0x02,"Medium removal prevented"}, + {0x53,0x03,"Medium removal prevented by data transfer element"}, + {0x53,0x04,"Medium thread or unthread failure"}, + {0x53,0x05,"Volume identifier invalid"}, + {0x53,0x06,"Volume identifier missing"}, + {0x53,0x07,"Duplicate volume identifier"}, + {0x53,0x08,"Element status unknown"}, + {0x53,0x09,"Data transfer device error - load failed"}, + {0x53,0x0A,"Data transfer device error - unload failed"}, + {0x53,0x0B,"Data transfer device error - unload missing"}, + {0x53,0x0C,"Data transfer device error - eject failed"}, + {0x53,0x0D,"Data transfer device error - library communication failed"}, + {0x54,0x00,"SCSI to host system interface failure"}, + {0x55,0x00,"System resource failure"}, + {0x55,0x01,"System buffer full"}, + {0x55,0x02,"Insufficient reservation resources"}, + {0x55,0x03,"Insufficient resources"}, + {0x55,0x04,"Insufficient registration resources"}, + {0x55,0x05,"Insufficient access control resources"}, + {0x55,0x06,"Auxiliary memory out of space"}, + {0x55,0x07,"Quota error"}, + {0x55,0x08,"Maximum number of supplemental decryption keys exceeded"}, + {0x55,0x09,"Medium auxiliary memory not accessible"}, + {0x55,0x0a,"Data currently unavailable"}, + {0x55,0x0b,"Insufficient power for operation"}, + {0x55,0x0c,"Insufficient resources to create rod"}, + {0x55,0x0d,"Insufficient resources to create rod token"}, + {0x55,0x0e,"Insufficient zone resources"}, + {0x55,0x0f,"Insufficient zone resources to complete write"}, + {0x55,0x10,"Maximum number of streams open"}, + {0x55,0x11,"Insufficient resources to bind"}, + {0x57,0x00,"Unable to recover table-of-contents"}, + {0x58,0x00,"Generation does not exist"}, + {0x59,0x00,"Updated block read"}, + {0x5A,0x00,"Operator request or state change input"}, + {0x5A,0x01,"Operator medium removal request"}, + {0x5A,0x02,"Operator selected write protect"}, + {0x5A,0x03,"Operator selected write permit"}, + {0x5B,0x00,"Log exception"}, + {0x5B,0x01,"Threshold condition met"}, + {0x5B,0x02,"Log counter at maximum"}, + {0x5B,0x03,"Log list codes exhausted"}, + {0x5C,0x00,"Rpl status change"}, + {0x5C,0x01,"Spindles synchronized"}, + {0x5C,0x02,"Spindles not synchronized"}, + {0x5D,0x00,"Failure prediction threshold exceeded"}, + {0x5D,0x01,"Media failure prediction threshold exceeded"}, + {0x5D,0x02,"Logical unit failure prediction threshold exceeded"}, + {0x5D,0x03,"spare area exhaustion prediction threshold exceeded"}, + {0x5D,0x10,"Hardware impending failure general hard drive failure"}, + {0x5D,0x11,"Hardware impending failure drive error rate too high" }, + {0x5D,0x12,"Hardware impending failure data error rate too high" }, + {0x5D,0x13,"Hardware impending failure seek error rate too high" }, + {0x5D,0x14,"Hardware impending failure too many block reassigns"}, + {0x5D,0x15,"Hardware impending failure access times too high" }, + {0x5D,0x16,"Hardware impending failure start unit times too high" }, + {0x5D,0x17,"Hardware impending failure channel parametrics"}, + {0x5D,0x18,"Hardware impending failure controller detected"}, + {0x5D,0x19,"Hardware impending failure throughput performance"}, + {0x5D,0x1A,"Hardware impending failure seek time performance"}, + {0x5D,0x1B,"Hardware impending failure spin-up retry count"}, + {0x5D,0x1C,"Hardware impending failure drive calibration retry count"}, + {0x5D,0x1D,"Hardware impending failure power loss protection circuit"}, + {0x5D,0x20,"Controller impending failure general hard drive failure"}, + {0x5D,0x21,"Controller impending failure drive error rate too high" }, + {0x5D,0x22,"Controller impending failure data error rate too high" }, + {0x5D,0x23,"Controller impending failure seek error rate too high" }, + {0x5D,0x24,"Controller impending failure too many block reassigns"}, + {0x5D,0x25,"Controller impending failure access times too high" }, + {0x5D,0x26,"Controller impending failure start unit times too high" }, + {0x5D,0x27,"Controller impending failure channel parametrics"}, + {0x5D,0x28,"Controller impending failure controller detected"}, + {0x5D,0x29,"Controller impending failure throughput performance"}, + {0x5D,0x2A,"Controller impending failure seek time performance"}, + {0x5D,0x2B,"Controller impending failure spin-up retry count"}, + {0x5D,0x2C,"Controller impending failure drive calibration retry count"}, + {0x5D,0x30,"Data channel impending failure general hard drive failure"}, + {0x5D,0x31,"Data channel impending failure drive error rate too high" }, + {0x5D,0x32,"Data channel impending failure data error rate too high" }, + {0x5D,0x33,"Data channel impending failure seek error rate too high" }, + {0x5D,0x34,"Data channel impending failure too many block reassigns"}, + {0x5D,0x35,"Data channel impending failure access times too high" }, + {0x5D,0x36,"Data channel impending failure start unit times too high" }, + {0x5D,0x37,"Data channel impending failure channel parametrics"}, + {0x5D,0x38,"Data channel impending failure controller detected"}, + {0x5D,0x39,"Data channel impending failure throughput performance"}, + {0x5D,0x3A,"Data channel impending failure seek time performance"}, + {0x5D,0x3B,"Data channel impending failure spin-up retry count"}, + {0x5D,0x3C,"Data channel impending failure drive calibration retry count"}, + {0x5D,0x40,"Servo impending failure general hard drive failure"}, + {0x5D,0x41,"Servo impending failure drive error rate too high" }, + {0x5D,0x42,"Servo impending failure data error rate too high" }, + {0x5D,0x43,"Servo impending failure seek error rate too high" }, + {0x5D,0x44,"Servo impending failure too many block reassigns"}, + {0x5D,0x45,"Servo impending failure access times too high" }, + {0x5D,0x46,"Servo impending failure start unit times too high" }, + {0x5D,0x47,"Servo impending failure channel parametrics"}, + {0x5D,0x48,"Servo impending failure controller detected"}, + {0x5D,0x49,"Servo impending failure throughput performance"}, + {0x5D,0x4A,"Servo impending failure seek time performance"}, + {0x5D,0x4B,"Servo impending failure spin-up retry count"}, + {0x5D,0x4C,"Servo impending failure drive calibration retry count"}, + {0x5D,0x50,"Spindle impending failure general hard drive failure"}, + {0x5D,0x51,"Spindle impending failure drive error rate too high" }, + {0x5D,0x52,"Spindle impending failure data error rate too high" }, + {0x5D,0x53,"Spindle impending failure seek error rate too high" }, + {0x5D,0x54,"Spindle impending failure too many block reassigns"}, + {0x5D,0x55,"Spindle impending failure access times too high" }, + {0x5D,0x56,"Spindle impending failure start unit times too high" }, + {0x5D,0x57,"Spindle impending failure channel parametrics"}, + {0x5D,0x58,"Spindle impending failure controller detected"}, + {0x5D,0x59,"Spindle impending failure throughput performance"}, + {0x5D,0x5A,"Spindle impending failure seek time performance"}, + {0x5D,0x5B,"Spindle impending failure spin-up retry count"}, + {0x5D,0x5C,"Spindle impending failure drive calibration retry count"}, + {0x5D,0x60,"Firmware impending failure general hard drive failure"}, + {0x5D,0x61,"Firmware impending failure drive error rate too high" }, + {0x5D,0x62,"Firmware impending failure data error rate too high" }, + {0x5D,0x63,"Firmware impending failure seek error rate too high" }, + {0x5D,0x64,"Firmware impending failure too many block reassigns"}, + {0x5D,0x65,"Firmware impending failure access times too high" }, + {0x5D,0x66,"Firmware impending failure start unit times too high" }, + {0x5D,0x67,"Firmware impending failure channel parametrics"}, + {0x5D,0x68,"Firmware impending failure controller detected"}, + {0x5D,0x69,"Firmware impending failure throughput performance"}, + {0x5D,0x6A,"Firmware impending failure seek time performance"}, + {0x5D,0x6B,"Firmware impending failure spin-up retry count"}, + {0x5D,0x6C,"Firmware impending failure drive calibration retry count"}, + {0x5D,0x73,"Media impending failure endurance limit met"}, + {0x5D,0xFF,"Failure prediction threshold exceeded (false)"}, + {0x5E,0x00,"Low power condition on"}, + {0x5E,0x01,"Idle condition activated by timer"}, + {0x5E,0x02,"Standby condition activated by timer"}, + {0x5E,0x03,"Idle condition activated by command"}, + {0x5E,0x04,"Standby condition activated by command"}, + {0x5E,0x05,"Idle_b condition activated by timer"}, + {0x5E,0x06,"Idle_b condition activated by command"}, + {0x5E,0x07,"Idle_c condition activated by timer"}, + {0x5E,0x08,"Idle_c condition activated by command"}, + {0x5E,0x09,"Standby_y condition activated by timer"}, + {0x5E,0x0a,"Standby_y condition activated by command"}, + {0x5E,0x41,"Power state change to active"}, + {0x5E,0x42,"Power state change to idle"}, + {0x5E,0x43,"Power state change to standby"}, + {0x5E,0x45,"Power state change to sleep"}, + {0x5E,0x47,"Power state change to device control"}, + {0x60,0x00,"Lamp failure"}, + {0x61,0x00,"Video acquisition error"}, + {0x61,0x01,"Unable to acquire video"}, + {0x61,0x02,"Out of focus"}, + {0x62,0x00,"Scan head positioning error"}, + {0x63,0x00,"End of user area encountered on this track"}, + {0x63,0x01,"Packet does not fit in available space"}, + {0x64,0x00,"Illegal mode for this track"}, + {0x64,0x01,"Invalid packet size"}, + {0x65,0x00,"Voltage fault"}, + {0x66,0x00,"Automatic document feeder cover up"}, + {0x66,0x01,"Automatic document feeder lift up"}, + {0x66,0x02,"Document jam in automatic document feeder"}, + {0x66,0x03,"Document miss feed automatic in document feeder"}, + {0x67,0x00,"Configuration failure"}, + {0x67,0x01,"Configuration of incapable logical units failed"}, + {0x67,0x02,"Add logical unit failed"}, + {0x67,0x03,"Modification of logical unit failed"}, + {0x67,0x04,"Exchange of logical unit failed"}, + {0x67,0x05,"Remove of logical unit failed"}, + {0x67,0x06,"Attachment of logical unit failed"}, + {0x67,0x07,"Creation of logical unit failed"}, + {0x67,0x08,"Assign failure occurred"}, + {0x67,0x09,"Multiply assigned logical unit"}, + {0x67,0x0A,"Set target port groups command failed"}, + {0x67,0x0B,"ATA device feature not enabled"}, + {0x67,0x0C,"Command rejected"}, + {0x67,0x0D,"Explicit bind not allowed"}, + {0x68,0x00,"Logical unit not configured"}, + {0x68,0x01,"Subsidiary logical unit not configured"}, + {0x69,0x00,"Data loss on logical unit"}, + {0x69,0x01,"Multiple logical unit failures"}, + {0x69,0x02,"Parity/data mismatch"}, + {0x6A,0x00,"Informational, refer to log"}, + {0x6B,0x00,"State change has occurred"}, + {0x6B,0x01,"Redundancy level got better"}, + {0x6B,0x02,"Redundancy level got worse"}, + {0x6C,0x00,"Rebuild failure occurred"}, + {0x6D,0x00,"Recalculate failure occurred"}, + {0x6E,0x00,"Command to logical unit failed"}, + {0x6F,0x00,"Copy protection key exchange failure - authentication " + "failure"}, + {0x6F,0x01,"Copy protection key exchange failure - key not present"}, + {0x6F,0x02,"Copy protection key exchange failure - key not established"}, + {0x6F,0x03,"Read of scrambled sector without authentication"}, + {0x6F,0x04,"Media region code is mismatched to logical unit region"}, + {0x6F,0x05,"Drive region must be permanent/region reset count error"}, + {0x6F,0x06,"Insufficient block count for binding nonce recording"}, + {0x6F,0x07,"Conflict in binding nonce recording"}, + {0x6F,0x08,"Insufficient permission"}, + {0x6F,0x09,"Invalid drive-host pairing server"}, + {0x6F,0x0A,"Drive-host pairing suspended"}, + /* + * ASC 0x70 overridden by an "additional2" array entry + * so there is no need to have them here. + */ + /* {0x70,0x00,"Decompression exception short algorithm id of nn"}, */ + + {0x71,0x00,"Decompression exception long algorithm id"}, + {0x72,0x00,"Session fixation error"}, + {0x72,0x01,"Session fixation error writing lead-in"}, + {0x72,0x02,"Session fixation error writing lead-out"}, + {0x72,0x03,"Session fixation error - incomplete track in session"}, + {0x72,0x04,"Empty or partially written reserved track"}, + {0x72,0x05,"No more track reservations allowed"}, + {0x72,0x06,"RMZ extension is not allowed"}, + {0x72,0x07,"No more test zone extensions are allowed"}, + {0x73,0x00,"CD control error"}, + {0x73,0x01,"Power calibration area almost full"}, + {0x73,0x02,"Power calibration area is full"}, + {0x73,0x03,"Power calibration area error"}, + {0x73,0x04,"Program memory area update failure"}, + {0x73,0x05,"Program memory area is full"}, + {0x73,0x06,"RMA/PMA is almost full"}, + {0x73,0x10,"Current power calibration area almost full"}, + {0x73,0x11,"Current power calibration area is full"}, + {0x73,0x17,"RDZ is full"}, + {0x74,0x00,"Security error"}, + {0x74,0x01,"Unable to decrypt data"}, + {0x74,0x02,"Unencrypted data encountered while decrypting"}, + {0x74,0x03,"Incorrect data encryption key"}, + {0x74,0x04,"Cryptographic integrity validation failed"}, + {0x74,0x05,"Error decrypting data"}, + {0x74,0x06,"Unknown signature verification key"}, + {0x74,0x07,"Encryption parameters not useable"}, + {0x74,0x08,"Digital signature validation failure"}, + {0x74,0x09,"Encryption mode mismatch on read"}, + {0x74,0x0a,"Encrypted block not raw read enabled"}, + {0x74,0x0b,"Incorrect Encryption parameters"}, + {0x74,0x0c,"Unable to decrypt parameter list"}, + {0x74,0x0d,"Encryption algorithm disabled"}, + {0x74,0x10,"SA creation parameter value invalid"}, + {0x74,0x11,"SA creation parameter value rejected"}, + {0x74,0x12,"Invalid SA usage"}, + {0x74,0x21,"Data encryption configuration prevented"}, + {0x74,0x30,"SA creation parameter not supported"}, + {0x74,0x40,"Authentication failed"}, + {0x74,0x61,"External data encryption key manager access error"}, + {0x74,0x62,"External data encryption key manager error"}, + {0x74,0x63,"External data encryption key not found"}, + {0x74,0x64,"External data encryption request not authorized"}, + {0x74,0x6e,"External data encryption control timeout"}, + {0x74,0x6f,"External data encryption control error"}, + {0x74,0x71,"Logical unit access not authorized"}, + {0x74,0x79,"Security conflict in translated device"}, + {0, 0, NULL} +}; + +#else /* SG_SCSI_STRINGS */ + +struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] = +{ + {0, 0, 0, NULL} +}; + +struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] = +{ + {0, 0, NULL} +}; +#endif /* SG_SCSI_STRINGS */ + +const char * sg_lib_sense_key_desc[] = { + "No Sense", /* Filemark, ILI and/or EOM; progress + indication (during FORMAT); power + condition sensing (REQUEST SENSE) */ + "Recovered Error", /* The last command completed successfully + but used error correction */ + "Not Ready", /* The addressed target is not ready */ + "Medium Error", /* Data error detected on the medium */ + "Hardware Error", /* Controller or device failure */ + "Illegal Request", + "Unit Attention", /* Removable medium was changed, or + the target has been reset */ + "Data Protect", /* Access to the data is blocked */ + "Blank Check", /* Reached unexpected written or unwritten + region of the medium */ + "Vendor specific(9)", /* Vendor specific */ + "Copy Aborted", /* COPY or COMPARE was aborted */ + "Aborted Command", /* The target aborted the command */ + "Equal", /* SEARCH DATA found data equal (obsolete) */ + "Volume Overflow", /* Medium full with data to be written */ + "Miscompare", /* Source data and data on the medium + do not agree */ + "Completed" /* may occur for successful cmd (spc4r23) */ +}; + +const char * sg_lib_pdt_strs[32] = { /* should have 2**5 elements */ + /* 0 */ "disk", + "tape", + "printer", /* obsolete, spc5r01 */ + "processor", /* often SAF-TE device, copy manager */ + "write once optical disk", /* obsolete, spc5r01 */ + /* 5 */ "cd/dvd", + "scanner", /* obsolete */ + "optical memory device", + "medium changer", + "communications", /* obsolete */ + /* 0xa */ "graphics [0xa]", /* obsolete */ + "graphics [0xb]", /* obsolete */ + "storage array controller", + "enclosure services device", + "simplified direct access device", + "optical card reader/writer device", + /* 0x10 */ "bridge controller commands", + "object based storage", + "automation/driver interface", + "security manager device", /* obsolete, spc5r01 */ + "zoned block commands", + "0x15", "0x16", "0x17", "0x18", + "0x19", "0x1a", "0x1b", "0x1c", "0x1d", + "well known logical unit", + "no physical device on this lu", +}; + +const char * sg_lib_transport_proto_strs[] = +{ + "Fibre Channel Protocol for SCSI (FCP-4)", + "SCSI Parallel Interface (SPI-5)", /* obsolete in spc5r01 */ + "Serial Storage Architecture SCSI-3 Protocol (SSA-S3P)", + "Serial Bus Protocol for IEEE 1394 (SBP-3)", + "SCSI RDMA Protocol (SRP)", + "Internet SCSI (iSCSI)", + "Serial Attached SCSI Protocol (SPL-4)", + "Automation/Drive Interface Transport (ADT-2)", + "AT Attachment Interface (ACS-2)", /* 0x8 */ + "USB Attached SCSI (UAS-2)", + "SCSI over PCI Express (SOP)", + "PCIe", /* added in spc5r02 */ + "Oxc", "Oxd", "Oxe", + "No specific protocol" +}; + +/* SCSI Feature Sets array. code->value, pdt->peri_dev_type (-1 for SPC) */ +struct sg_lib_value_name_t sg_lib_scsi_feature_sets[] = +{ + {SCSI_FS_SPC_DISCOVERY_2016, -1, "Discovery 2016"}, + {SCSI_FS_SBC_BASE_2010, PDT_DISK, "SBC Base 2010"}, + {SCSI_FS_SBC_BASE_2016, PDT_DISK, "SBC Base 2016"}, + {SCSI_FS_SBC_BASIC_PROV_2016, PDT_DISK, "Basic provisioning 2016"}, + {SCSI_FS_SBC_DRIVE_MAINT_2016, PDT_DISK, "Drive maintenance 2016"}, + {0x0, 0, NULL}, /* 0x0 is reserved sfs; trailing sentinel */ +}; + +#if (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) + +/* .value is completion queue's DW3 as follows: ((DW3 >> 17) & 0x3ff) + * .peri_dev_type is an index for the sg_lib_scsi_status_sense_arr[] + * .name is taken from NVMe 1.3a document, section 4.6.1.2.1 with less + * capitalization. + * NVMe term bits 31:17 of DW3 in the completion field as the "Status + * Field" (SF). Bit 31 is "Do not retry" (DNR) and bit 30 is "More" (M). + * Bits 29:28 are reserved, bit 27:25 are the "Status Code Type" (SCT) + * and bits 24:17 are the Status Code (SC). This table is in ascending + * order of its .value field so a binary search could be done on it. */ +struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] = +{ + /* Generic command status values, Status Code Type (SCT): 0h + * Lowest 8 bits are the Status Code (SC), in this case: + * 00h - 7Fh: Applicable to Admin Command Set, or across multiple + * command sets + * 80h - BFh: I/O Command Set Specific status codes + * c0h - FFh: I/O Vendor Specific status codes */ + {0x0, 0, "Successful completion"}, + {0x1, 1, "Invalid command opcode"}, + {0x2, 2, "Invalid field in command"}, + {0x3, 2, "Command id conflict"}, + {0x4, 3, "Data transfer error"}, + {0x5, 4, "Command aborted due to power loss notication"}, + {0x6, 5, "Internal error"}, + {0x7, 6, "Command abort requested"}, + {0x8, 6, "Command aborted due to SQ deletion"}, + {0x9, 6, "Command aborted due to failed fused command"}, + {0xa, 6, "Command aborted due to missing fused command"}, + {0xb, 7, "Invalid namespace or format"}, + {0xc, 5, "Command sequence error"}, + {0xd, 5, "Invalid SGL segment descriptor"}, + {0xe, 5, "Invalid number of SGL descriptors"}, + {0xf, 5, "Data SGL length invalid"}, + {0x10, 5, "Matadata SGL length invalid"}, + {0x11, 5, "SGL descriptor type invalid"}, + {0x12, 5, "Invalid use of controller memory buffer"}, + {0x13, 5, "PRP offset invalid"}, + {0x14, 2, "Atomic write unit exceeded"}, + {0x15, 8, "Operation denied"}, + {0x16, 5, "SGL offset invalid"}, + {0x17, 5, "Reserved [0x17]"}, + {0x18, 5, "Host identifier inconsistent format"}, + {0x19, 5, "Keep alive timeout expired"}, + {0x1a, 5, "Keep alive timeout invalid"}, + {0x1b, 6, "Command aborted due to Preempt and Abort"}, + {0x1c, 10, "Sanitize failed"}, + {0x1d, 11, "Sanitize in progress"}, + {0x1e, 5, "SGL data block granularity invalid"}, + {0x1f, 5, "Command not supported for queue in CMB"}, + + /* Generic command status values, NVM (I/O) Command Set */ + {0x80, 12, "LBA out of range"}, + {0x81, 3, "Capacity exceeded"}, + {0x82, 13, "Namespace not ready"}, + {0x83, 14, "Reservation conflict"}, + {0x84, 15, "Format in progress"}, + /* 0xc0 - 0xff: vendor specific */ + + /* Command specific status values, Status Code Type (SCT): 1h */ + {0x100, 5, "Completion queue invalid"}, + {0x101, 5, "Invalid queue identifier"}, + {0x102, 5, "Invalid queue size"}, + {0x103, 5, "Abort command limit exceeded"}, + {0x104, 5, "Reserved [0x104]"}, + {0x105, 5, "Asynchronous event request limit exceeded"}, + {0x106, 5, "Invalid firmware slot"}, + {0x107, 5, "Invalid firmware image"}, + {0x108, 5, "Invalid interrupt vector"}, + {0x109, 5, "Invalid log page"}, + {0x10a,16, "Invalid format"}, + {0x10b, 5, "Firmware activation requires conventional reset"}, + {0x10c, 5, "Invalid queue deletion"}, + {0x10d, 5, "Feature identifier not saveable"}, + {0x10e, 5, "Feature not changeable"}, + {0x10f, 5, "Feature not namespace specific"}, + {0x110, 5, "Firmware activation requires NVM subsystem reset"}, + {0x111, 5, "Firmware activation requires reset"}, + {0x112, 5, "Firmware activation requires maximum time violation"}, + {0x113, 5, "Firmware activation prohibited"}, + {0x114, 5, "Overlapping range"}, + {0x115, 5, "Namespace insufficient capacity"}, + {0x116, 5, "Namespace identifier unavailable"}, + {0x117, 5, "Reserved [0x107]"}, + {0x118, 5, "Namespace already attached"}, + {0x119, 5, "Namespace is private"}, + {0x11a, 5, "Namespace not attached"}, + {0x11b, 3, "Thin provisioning not supported"}, + {0x11c, 3, "Controller list invalid"}, + {0x11d,17, "Device self-test in progress"}, + {0x11e,18, "Boot partition write prohibited"}, + {0x11f, 5, "Invalid controller identifier"}, + {0x120, 5, "Invalid secondary controller state"}, + {0x121, 5, "Invalid number of controller resorces"}, + {0x122, 5, "Invalid resorce identifier"}, + + /* Command specific status values, Status Code Type (SCT): 1h + * for NVM (I/O) Command Set */ + {0x180, 2, "Conflicting attributes"}, + {0x181,19, "Invalid protection information"}, + {0x182,18, "Attempted write to read only range"}, + /* 0x1c0 - 0x1ff: vendor specific */ + + /* Media and Data Integrity error values, Status Code Type (SCT): 2h */ + {0x280,20, "Write fault"}, + {0x281,21, "Unrecovered read error"}, + {0x282,22, "End-to-end guard check error"}, + {0x283,23, "End-to-end application tag check error"}, + {0x284,24, "End-to-end reference tag check error"}, + {0x285,25, "Compare failure"}, + {0x286, 8, "Access denied"}, + {0x287,26, "Deallocated or unwritten logical block"}, + /* 0x2c0 - 0x2ff: vendor specific */ + + /* Leave this Sentinel value at end of this array */ + {0x3ff, 0, NULL}, +}; + +/* The sg_lib_nvme_cmd_status_arr[n].peri_dev_type field is an index + * to this array. It allows an NVMe status (error) value to be mapped + * to this SCSI tuple: status, sense_key, additional sense code (asc) and + * asc qualifier (ascq). For brevity SAM_STAT_CHECK_CONDITION is written + * as 0x2. */ +struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] = +{ + {SAM_STAT_GOOD, SPC_SK_NO_SENSE, 0, 0}, /* it's all good */ /* 0 */ + {SAM_STAT_CHECK_CONDITION, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x0},/* opcode */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x24, 0x0}, /* field in cdb */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x0, 0x0}, + {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0xb, 0x8}, + {0x2, SPC_SK_HARDWARE_ERROR, 0x44, 0x0}, /* internal error */ /* 5 */ + {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0x0, 0x0}, + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x9}, /* invalid LU */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x2}, /* access denied */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x2c, 0x0}, /* cmd sequence error */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x31, 0x3}, /* sanitize failed */ /* 10 */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x1b}, /* sanitize in progress */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x21, 0x0}, /* LBA out of range */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x0}, /* not reportable; 0x1: becoming */ + {SAM_STAT_RESERVATION_CONFLICT, 0x0, 0x0, 0x0}, + {0x2, SPC_SK_NOT_READY, 0x4, 0x4}, /* format in progress */ /* 15 */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x31, 0x1}, /* format failed */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x9}, /* self-test in progress */ + {0x2, SPC_SK_DATA_PROTECT, 0x27, 0x0}, /* write prohibited */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x10, 0x5}, /* protection info */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x3, 0x0}, /* periph dev w fault */ /* 20 */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x11, 0x0}, /* unrecoc rd */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x1}, /* PI guard */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2}, /* PI app tag */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2}, /* PI app tag */ + {0x2, SPC_SK_MISCOMPARE, 0x1d, 0x0}, /* during verify */ /* 25 */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x21, 0x6}, /* read invalid data */ + + /* Leave this Sentinel value at end of this array */ + {0xff, 0xff, 0xff, 0xff}, +}; + + +#else /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */ +struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] = +{ + + /* Leave this Sentinel value at end of this array */ + {0x3ff, 0, NULL}, +}; + +struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] = +{ + + /* Leave this Sentinel value at end of this array */ + {0xff, 0xff, 0xff, 0xff}, +}; + +#endif /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */ diff --git a/tools/sg_write_buffer/sg_pt_common.c b/tools/sg_write_buffer/sg_pt_common.c new file mode 100644 index 0000000..85bc191 --- /dev/null +++ b/tools/sg_write_buffer/sg_pt_common.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2009-2018 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_pt.h" +#include "sg_pt_nvme.h" + + +static const char * scsi_pt_version_str = "3.03 20180115"; + +static const char * nvme_scsi_vendor_str = "NVMe "; + + +const char * +scsi_pt_version() +{ + return scsi_pt_version_str; +} + +/* Given the NVMe Identify controller response and optionally the NVMe + * Identify namespace response (NULL otherwise), generate the SCSI VPD + * page 0x83 (device identification) descriptor(s) in dop. Return the + * number of bytes written which will not exceed max_do_len. Probably use + * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport + * protocol (tproto) should be -1 if not known, else SCSI value. + * N.B. Does not write total VPD page length into dop[2:3] . */ +int +sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p, + const uint8_t * nvme_id_ns_p, int pdt, + int tproto, uint8_t * dop, int max_do_len) +{ + bool have_nguid, have_eui64; + int k, n; + char b[4]; + + if ((NULL == nvme_id_ctl_p) || (NULL == dop) || (max_do_len < 56)) + return 0; + + memset(dop, 0, max_do_len); + dop[0] = 0x1f & pdt; /* (PQ=0)<<5 | (PDT=pdt); 0 or 0xd (SES) */ + dop[1] = 0x83; /* Device Identification VPD page number */ + /* Build a T10 Vendor ID based designator (desig_id=1) for controller */ + if (tproto >= 0) { + dop[4] = ((0xf & tproto) << 4) | 0x2; + dop[5] = 0xa1; /* PIV=1, ASSOC=2 (target device), desig_id=1 */ + } else { + dop[4] = 0x2; /* Prococol id=0, code_set=2 (ASCII) */ + dop[5] = 0x21; /* PIV=0, ASSOC=2 (target device), desig_id=1 */ + } + memcpy(dop + 8, nvme_scsi_vendor_str, 8); /* N.B. this is "NVMe " */ + memcpy(dop + 16, nvme_id_ctl_p + 24, 40); /* MN */ + for (k = 40; k > 0; --k) { + if (' ' == dop[15 + k]) + dop[15 + k] = '_'; /* convert trailing spaces */ + else + break; + } + if (40 == k) + --k; + n = 16 + 1 + k; + if (max_do_len < (n + 20)) + return 0; + memcpy(dop + n, nvme_id_ctl_p + 4, 20); /* SN */ + for (k = 20; k > 0; --k) { /* trim trailing spaces */ + if (' ' == dop[n + k - 1]) + dop[n + k - 1] = '\0'; + else + break; + } + n += k; + if (0 != (n % 4)) + n = ((n / 4) + 1) * 4; /* round up to next modulo 4 */ + dop[7] = n - 8; + if (NULL == nvme_id_ns_p) + return n; + + /* Look for NGUID (16 byte identifier) or EUI64 (8 byte) fields in + * NVME Identify for namespace. If found form a EUI and a SCSI string + * descriptor for non-zero NGUID or EUI64 (prefer NGUID if both). */ + have_nguid = ! sg_all_zeros(nvme_id_ns_p + 104, 16); + have_eui64 = ! sg_all_zeros(nvme_id_ns_p + 120, 8); + if ((! have_nguid) && (! have_eui64)) + return n; + if (have_nguid) { + if (max_do_len < (n + 20)) + return n; + dop[n + 0] = 0x1; /* Prococol id=0, code_set=1 (binary) */ + dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */ + dop[n + 3] = 16; + memcpy(dop + n + 4, nvme_id_ns_p + 104, 16); + n += 20; + if (max_do_len < (n + 40)) + return n; + dop[n + 0] = 0x3; /* Prococol id=0, code_set=3 (utf8) */ + dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */ + dop[n + 3] = 36; + memcpy(dop + n + 4, "eui.", 4); + for (k = 0; k < 16; ++k) { + snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[104 + k]); + memcpy(dop + n + 8 + (2 * k), b, 2); + } + return n + 40; + } else { /* have_eui64 is true, 8 byte identifier */ + if (max_do_len < (n + 12)) + return n; + dop[n + 0] = 0x1; /* Prococol id=0, code_set=1 (binary) */ + dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */ + dop[n + 3] = 8; + memcpy(dop + n + 4, nvme_id_ns_p + 120, 8); + n += 12; + if (max_do_len < (n + 24)) + return n; + dop[n + 0] = 0x3; /* Prococol id=0, code_set=3 (utf8) */ + dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */ + dop[n + 3] = 20; + memcpy(dop + n + 4, "eui.", 4); + for (k = 0; k < 8; ++k) { + snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[120 + k]); + memcpy(dop + n + 8 + (2 * k), b, 2); + } + return n + 24; + } +} diff --git a/tools/sg_write_buffer/sg_pt_linux.c b/tools/sg_write_buffer/sg_pt_linux.c new file mode 100644 index 0000000..22ea068 --- /dev/null +++ b/tools/sg_write_buffer/sg_pt_linux.c @@ -0,0 +1,964 @@ +/* + * Copyright (c) 2005-2018 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.37 20180126 */ + + +#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" + + +#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) */ + +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 + +bool sg_bsg_nvme_char_major_checked = false; +int sg_bsg_major = 0; +volatile int sg_nvme_char_major = 0; + +long sg_lin_page_size = 4096; /* default, overridden with correct value */ + + +#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). */ +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 + + +/* 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 (! 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_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_nvme_id_ctlp) { + free(ptp->free_nvme_id_ctlp); + ptp->free_nvme_id_ctlp = NULL; + ptp->nvme_id_ctlp = NULL; + } + 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; + uint32_t 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_direct = false; + 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 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); + else { + ptp->is_sg = false; + ptp->is_bsg = false; + ptp->is_nvme = false; + ptp->nvme_direct = 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_uintptr_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_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, 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_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 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_uintptr_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_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; + } +} + +/* 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->nvme_direct ? 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->nvme_direct ? 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->nvme_direct ? 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; +} + +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 >= 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 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 (! 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) { + 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 sg_do_nvme_pt(vp, -1, time_secs, verbose); + else if (sg_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; +} diff --git a/tools/sg_write_buffer/sg_pt_linux_nvme.c b/tools/sg_write_buffer/sg_pt_linux_nvme.c new file mode 100644 index 0000000..5b08f6d --- /dev/null +++ b/tools/sg_write_buffer/sg_pt_linux_nvme.c @@ -0,0 +1,1185 @@ +/* + * Copyright (c) 2017-2018 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. + * + * The code to use the NVMe Management Interface (MI) SES pass-through + * was provided by WDC in November 2017. + */ + +/* + * Copyright 2017, Western Digital Corporation + * + * Written by Berck Nash + * + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + * + * Based on the NVM-Express command line utility, which bore the following + * notice: + * + * Copyright (c) 2014-2015, Intel Corporation. + * + * Written by Keith Busch <keith.busch@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +/* sg_pt_linux_nvme version 1.04 20180115 */ + + +#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> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.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_unaligned.h" + +#define SCSI_INQUIRY_OPC 0x12 +#define SCSI_REPORT_LUNS_OPC 0xa0 +#define SCSI_TEST_UNIT_READY_OPC 0x0 +#define SCSI_REQUEST_SENSE_OPC 0x3 +#define SCSI_SEND_DIAGNOSTIC_OPC 0x1d +#define SCSI_RECEIVE_DIAGNOSTIC_OPC 0x1c +#define SCSI_MAINT_IN_OPC 0xa3 +#define SCSI_REP_SUP_OPCS_OPC 0xc +#define SCSI_REP_SUP_TMFS_OPC 0xd + +/* Additional Sense Code (ASC) */ +#define NO_ADDITIONAL_SENSE 0x0 +#define LOGICAL_UNIT_NOT_READY 0x4 +#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8 +#define UNRECOVERED_READ_ERR 0x11 +#define PARAMETER_LIST_LENGTH_ERR 0x1a +#define INVALID_OPCODE 0x20 +#define LBA_OUT_OF_RANGE 0x21 +#define INVALID_FIELD_IN_CDB 0x24 +#define INVALID_FIELD_IN_PARAM_LIST 0x26 +#define UA_RESET_ASC 0x29 +#define UA_CHANGED_ASC 0x2a +#define TARGET_CHANGED_ASC 0x3f +#define LUNS_CHANGED_ASCQ 0x0e +#define INSUFF_RES_ASC 0x55 +#define INSUFF_RES_ASCQ 0x3 +#define LOW_POWER_COND_ON_ASC 0x5e /* ASCQ=0 */ +#define POWER_ON_RESET_ASCQ 0x0 +#define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */ +#define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */ +#define CAPACITY_CHANGED_ASCQ 0x9 +#define SAVING_PARAMS_UNSUP 0x39 +#define TRANSPORT_PROBLEM 0x4b +#define THRESHOLD_EXCEEDED 0x5d +#define LOW_POWER_COND_ON 0x5e +#define MISCOMPARE_VERIFY_ASC 0x1d +#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */ +#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16 + + +static inline bool is_aligned(const void * pointer, size_t byte_count) +{ + return ((sg_uintptr_t)pointer % byte_count) == 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; +} + +#if (HAVE_NVME && (! IGNORE_NVME)) + +/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5) + * to the name of its associated char device (e.g. /dev/nvme0). If this + * occurs true is returned and the char device name is placed in 'b' (as + * long as b_len is sufficient). Otherwise false is returned. */ +bool +sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len, + char * b) +{ + uint32_t n, tlen; + const char * cp; + char buff[8]; + + if ((NULL == b) || (b_len < 5)) + return false; /* degenerate cases */ + cp = strstr(nvme_block_devname, "nvme"); + if (NULL == cp) + return false; /* expected to find "nvme" in given name */ + if (1 != sscanf(cp, "nvme%u", &n)) + return false; /* didn't find valid "nvme<number>" */ + snprintf(buff, sizeof(buff), "%u", n); + tlen = (cp - nvme_block_devname) + 4 + strlen(buff); + if ((tlen + 1) > b_len) + return false; /* b isn't long enough to fit output */ + memcpy(b, nvme_block_devname, tlen); + b[tlen] = '\0'; + return true; +} + +static void +build_sense_buffer(bool desc, uint8_t *buf, uint8_t skey, uint8_t asc, + uint8_t ascq) +{ + if (desc) { + buf[0] = 0x72; /* descriptor, current */ + buf[1] = skey; + buf[2] = asc; + buf[3] = ascq; + buf[7] = 0; + } else { + buf[0] = 0x70; /* fixed, current */ + buf[2] = skey; + buf[7] = 0xa; /* Assumes length is 18 bytes */ + buf[12] = asc; + buf[13] = ascq; + } +} + +/* Set in_bit to -1 to indicate no bit position of invalid field */ +static void +mk_sense_asc_ascq(struct sg_pt_linux_scsi * ptp, int sk, int asc, int ascq, + int vb) +{ + bool dsense = ptp->scsi_dsense; + int n; + uint8_t * sbp = (uint8_t *)ptp->io_hdr.response; + + ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + if (vb) + pr2ws("%s: max_response_len=%d too short, want 14 or more\n", + __func__, n); + return; + } else + ptp->io_hdr.response_len = dsense ? 8 : ((n < 18) ? n : 18); + memset(sbp, 0, n); + build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, sk, + asc, ascq); +} + +static void +mk_sense_from_nvme_status(struct sg_pt_linux_scsi * ptp, int vb) +{ + bool ok; + bool dsense = ptp->scsi_dsense; + int n; + uint8_t sstatus, sk, asc, ascq; + uint8_t * sbp = (uint8_t *)ptp->io_hdr.response; + + ok = sg_nvme_status2scsi(ptp->nvme_status, &sstatus, &sk, &asc, &ascq); + if (! ok) { /* can't find a mapping to a SCSI error, so ... */ + sstatus = SAM_STAT_CHECK_CONDITION; + sk = SPC_SK_ILLEGAL_REQUEST; + asc = 0xb; + ascq = 0x0; /* asc: "WARNING" purposely vague */ + } + + ptp->io_hdr.device_status = sstatus; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__, n); + return; + } else + ptp->io_hdr.response_len = (dsense ? 8 : ((n < 18) ? n : 18)); + memset(sbp, 0, n); + build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (vb > 3) + pr2ws("%s: [status, sense_key,asc,ascq]: [0x%x, 0x%x,0x%x,0x%x]\n", + __func__, sstatus, sk, asc, ascq); +} + +/* Set in_bit to -1 to indicate no bit position of invalid field */ +static void +mk_sense_invalid_fld(struct sg_pt_linux_scsi * ptp, bool in_cdb, int in_byte, + int in_bit, int vb) +{ + bool dsense = ptp->scsi_dsense; + int sl, asc, n; + uint8_t * sbp = (uint8_t *)ptp->io_hdr.response; + uint8_t sks[4]; + + ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION; + asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + if (vb) + pr2ws("%s: max_response_len=%d too short, want 14 or more\n", + __func__, n); + return; + } else + ptp->io_hdr.response_len = dsense ? 8 : ((n < 18) ? n : 18); + memset(sbp, 0, n); + build_sense_buffer(dsense, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0); + memset(sks, 0, sizeof(sks)); + sks[0] = 0x80; + if (in_cdb) + sks[0] |= 0x40; + if (in_bit >= 0) { + sks[0] |= 0x8; + sks[0] |= (0x7 & in_bit); + } + sg_put_unaligned_be16(in_byte, sks + 1); + if (dsense) { + sl = sbp[7] + 8; + sbp[7] = sl; + sbp[sl] = 0x2; + sbp[sl + 1] = 0x6; + memcpy(sbp + sl + 4, sks, 3); + } else + memcpy(sbp + 15, sks, 3); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n", + __func__, asc, in_cdb ? 'C' : 'D', in_byte, in_bit); +} + +/* Returns 0 for success. Returns SG_LIB_NVME_STATUS if there is non-zero + * NVMe status (from the completion queue) with the value placed in + * ptp->nvme_status. If Unix error from ioctl then return negated value + * (equivalent -errno from basic Unix system functions like open()). + * CDW0 from the completion queue is placed in ptp->nvme_result in the + * absence of a Unix error. If time_secs is negative it is treated as + * a timeout in milliseconds (of abs(time_secs) ). */ +static int +do_nvme_admin_cmd(struct sg_pt_linux_scsi * ptp, + struct sg_nvme_passthru_cmd *cmdp, void * dp, bool is_read, + int time_secs, int vb) +{ + const uint32_t cmd_len = sizeof(struct sg_nvme_passthru_cmd); + int res; + uint32_t n; + uint16_t sct_sc; + const uint8_t * up = ((const uint8_t *)cmdp) + SG_NVME_PT_OPCODE; + + cmdp->timeout_ms = (time_secs < 0) ? (-time_secs) : (1000 * time_secs); + ptp->os_err = 0; + if (vb > 2) { + pr2ws("NVMe command:\n"); + hex2stderr((const uint8_t *)cmdp, cmd_len, 1); + if ((vb > 3) && (! is_read) && dp) { + uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN); + + if (len > 0) { + n = len; + if ((len < 512) || (vb > 5)) + pr2ws("\nData-out buffer (%u bytes):\n", n); + else { + pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n); + n = 512; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + } + res = ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, cmdp); + if (res < 0) { /* OS error (errno negated) */ + ptp->os_err = -res; + if (vb > 1) { + pr2ws("%s: ioctl opcode=0x%x failed: %s " + "(errno=%d)\n", __func__, *up, strerror(-res), -res); + } + return res; + } + + /* Now res contains NVMe completion queue CDW3 31:17 (15 bits) */ + ptp->nvme_result = cmdp->result; + if (ptp->nvme_direct && ptp->io_hdr.response && + (ptp->io_hdr.max_response_len > 3)) { + /* build 16 byte "sense" buffer */ + uint8_t * sbp = (uint8_t *)ptp->io_hdr.response; + uint16_t st = (uint16_t)res; + + n = ptp->io_hdr.max_response_len; + n = (n < 16) ? n : 16; + memset(sbp, 0 , n); + ptp->io_hdr.response_len = n; + sg_put_unaligned_le32(cmdp->result, + sbp + SG_NVME_PT_CQ_RESULT); + if (n > 15) /* LSBit will be 0 (Phase bit) after (st << 1) */ + sg_put_unaligned_le16(st << 1, sbp + SG_NVME_PT_CQ_STATUS_P); + } + /* clear upper bits (DNR and More) leaving ((SCT << 8) | SC) */ + sct_sc = 0x3ff & res; + ptp->nvme_status = sct_sc; + if (sct_sc) { /* when non-zero, treat as command error */ + if (vb > 1) { + char b[80]; + + pr2ws("%s: ioctl opcode=0x%x failed: NVMe status: %s [0x%x]\n", + __func__, *up, + sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b), sct_sc); + } + return SG_LIB_NVME_STATUS; /* == SCSI_PT_DO_NVME_STATUS */ + } + if ((vb > 3) && is_read && dp) { + uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN); + + if (len > 0) { + n = len; + if ((len < 1024) || (vb > 5)) + pr2ws("\nData-in buffer (%u bytes):\n", n); + else { + pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n); + n = 1024; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + return 0; +} + +/* Returns 0 on success; otherwise a positive value is returned */ +static int +sntl_cache_identity(struct sg_pt_linux_scsi * ptp, int time_secs, int vb) +{ + struct sg_nvme_passthru_cmd cmd; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * up; + + up = sg_memalign(pg_sz, pg_sz, &ptp->free_nvme_id_ctlp, vb > 3); + ptp->nvme_id_ctlp = up; + if (NULL == up) { + pr2ws("%s: sg_memalign() failed to get memory\n", __func__); + return -ENOMEM; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x6; /* Identify */ + cmd.cdw10 = 0x1; /* CNS=0x1 Identify controller */ + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->nvme_id_ctlp; + cmd.data_len = pg_sz; + return do_nvme_admin_cmd(ptp, &cmd, up, true, time_secs, vb); +} + +static const char * nvme_scsi_vendor_str = "NVMe "; +static const uint16_t inq_resp_len = 36; + +static int +sntl_inq(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs, + int vb) +{ + bool evpd; + bool cp_id_ctl = false; + int res; + uint16_t n, alloc_len, pg_cd; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * nvme_id_ns = NULL; + uint8_t * free_nvme_id_ns = NULL; + uint8_t inq_dout[256]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + + if (0x2 & cdbp[1]) { /* Reject CmdDt=1 */ + mk_sense_invalid_fld(ptp, true, 1, 1, vb); + return 0; + } + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) /* should be negative errno */ + return res; + } + memset(inq_dout, 0, sizeof(inq_dout)); + alloc_len = sg_get_unaligned_be16(cdbp + 3); + evpd = !!(0x1 & cdbp[1]); + pg_cd = cdbp[2]; + if (evpd) { /* VPD page responses */ + switch (pg_cd) { + case 0: + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + n = 8; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[4] = 0x0; + inq_dout[5] = 0x80; + inq_dout[6] = 0x83; + inq_dout[n - 1] = 0xde; /* last VPD number */ + break; + case 0x80: + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + sg_put_unaligned_be16(20, inq_dout + 2); + memcpy(inq_dout + 4, ptp->nvme_id_ctlp + 4, 20); /* SN */ + n = 24; + break; + case 0x83: + if ((ptp->nvme_nsid > 0) && + (ptp->nvme_nsid < SG_NVME_BROADCAST_NSID)) { + nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns, + vb > 3); + if (nvme_id_ns) { + struct sg_nvme_passthru_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x6; /* Identify */ + cmd.nsid = ptp->nvme_nsid; + cmd.cdw10 = 0x0; /* CNS=0x0 Identify namespace */ + cmd.addr = (uint64_t)(sg_uintptr_t)nvme_id_ns; + cmd.data_len = pg_sz; + res = do_nvme_admin_cmd(ptp, &cmd, nvme_id_ns, true, + time_secs, vb > 3); + if (res) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + } + } + n = sg_make_vpd_devid_for_nvme(ptp->nvme_id_ctlp, nvme_id_ns, + 0 /* pdt */, -1 /*tproto */, + inq_dout, sizeof(inq_dout)); + if (n > 3) + sg_put_unaligned_be16(n - 4, inq_dout + 2); + if (free_nvme_id_ns) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + break; + case 0xde: + inq_dout[1] = pg_cd; + sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2); + n = 16 + 4096; + cp_id_ctl = true; + break; + default: /* Point to page_code field in cdb */ + mk_sense_invalid_fld(ptp, true, 2, 7, vb); + return 0; + } + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) { + if (cp_id_ctl) { + memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, + (n < 16 ? n : 16)); + if (n > 16) + memcpy((uint8_t *)ptp->io_hdr.din_xferp + 16, + ptp->nvme_id_ctlp, n - 16); + } else + memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, n); + } + } + } else { /* Standard INQUIRY response */ + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); pdt=0 --> SBC; 0xd --> SES */ + inq_dout[2] = 6; /* version: SPC-4 */ + inq_dout[3] = 2; /* NORMACA=0, HISUP=0, response data format: 2 */ + inq_dout[4] = 31; /* so response length is (or could be) 36 bytes */ + inq_dout[6] = 0x40; /* ENCSERV=1 */ + inq_dout[7] = 0x2; /* CMDQUE=1 */ + memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8); /* NVMe not Intel */ + memcpy(inq_dout + 16, ptp->nvme_id_ctlp + 24, 16); /* Prod <-- MN */ + memcpy(inq_dout + 32, ptp->nvme_id_ctlp + 64, 4); /* Rev <-- FR */ + if (alloc_len > 0) { + n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, n); + } + } + return 0; +} + +static int +sntl_rluns(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs, + int vb) +{ + int res; + uint16_t sel_report; + uint32_t alloc_len, k, n, num, max_nsid; + uint8_t * rl_doutp; + uint8_t * up; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + + sel_report = cdbp[2]; + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + max_nsid = sg_get_unaligned_le32(ptp->nvme_id_ctlp + 516); + switch (sel_report) { + case 0: + case 2: + num = max_nsid; + break; + case 1: + case 0x10: + case 0x12: + num = 0; + break; + case 0x11: + num = (1 == ptp->nvme_nsid) ? max_nsid : 0; + break; + default: + if (vb > 1) + pr2ws("%s: bad select_report value: 0x%x\n", __func__, + sel_report); + mk_sense_invalid_fld(ptp, true, 2, 7, vb); + return 0; + } + rl_doutp = (uint8_t *)calloc(num + 1, 8); + if (NULL == rl_doutp) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8) + sg_put_unaligned_be16(k, up); + n = num * 8; + sg_put_unaligned_be32(n, rl_doutp); + n+= 8; + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, rl_doutp, n); + } + res = 0; + free(rl_doutp); + return res; +} + +static int +sntl_tur(struct sg_pt_linux_scsi * ptp, int time_secs, int vb) +{ + int res; + uint32_t pow_state; + struct sg_nvme_passthru_cmd cmd; + + if (vb > 4) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0xa; /* Get feature */ + cmd.nsid = SG_NVME_BROADCAST_NSID; + cmd.cdw10 = 0x2; /* SEL=0 (current), Feature=2 Power Management */ + cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } else { + ptp->os_err = 0; + ptp->nvme_status = 0; + } + pow_state = (0x1f & ptp->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); +#if 0 /* pow_state bounces around too much on laptop */ + if (pow_state) + mk_sense_asc_ascq(ptp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0, + vb); +#endif + return 0; +} + +static int +sntl_req_sense(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool desc; + int res; + uint32_t pow_state, alloc_len, n; + struct sg_nvme_passthru_cmd cmd; + uint8_t rs_dout[64]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + desc = !!(0x1 & cdbp[1]); + alloc_len = cdbp[4]; + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0xa; /* Get feature */ + cmd.nsid = SG_NVME_BROADCAST_NSID; + cmd.cdw10 = 0x2; /* SEL=0 (current), Feature=2 Power Management */ + cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } else { + ptp->os_err = 0; + ptp->nvme_status = 0; + } + ptp->io_hdr.response_len = 0; + pow_state = (0x1f & ptp->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); + memset(rs_dout, 0, sizeof(rs_dout)); + if (pow_state) + build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + LOW_POWER_COND_ON_ASC, 0); + else + build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + NO_ADDITIONAL_SENSE, 0); + n = desc ? 8 : 18; + n = (n < alloc_len) ? n : alloc_len; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, rs_dout, n); + return 0; +} + +/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI + * has a special command (SES Send) to tunnel through pages to an + * enclosure. The NVMe enclosure is meant to understand the SES + * (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_senddiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool pf, self_test; + int res; + uint8_t st_cd, dpg_cd; + uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * dop; + struct sg_nvme_passthru_cmd cmd; + uint8_t * cmd_up = (uint8_t *)&cmd; + + st_cd = 0x7 & (cdbp[1] >> 5); + self_test = !! (0x4 & cdbp[1]); + pf = !! (0x10 & cdbp[1]); + if (vb > 3) + pr2ws("%s: pf=%d, self_test=%d (st_code=%d)\n", __func__, (int)pf, + (int)self_test, (int)st_cd); + if (self_test || st_cd) { + memset(cmd_up, 0, sizeof(cmd)); + cmd_up[SG_NVME_PT_OPCODE] = 0x14; /* Device self-test */ + /* just this namespace (if there is one) and controller */ + sg_put_unaligned_le32(ptp->nvme_nsid, cmd_up + SG_NVME_PT_NSID); + switch (st_cd) { + case 0: /* Here if self_test is set, do short self-test */ + case 1: /* Background short */ + case 5: /* Foreground short */ + nvme_dst = 1; + break; + case 2: /* Background extended */ + case 6: /* Foreground extended */ + nvme_dst = 2; + break; + case 4: /* Abort self-test */ + nvme_dst = 0xf; + break; + default: + pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd); + mk_sense_invalid_fld(ptp, true, 1, 7, vb); + return 0; + } + sg_put_unaligned_le32(nvme_dst, cmd_up + SG_NVME_PT_CDW10); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } + } + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + dout_len = ptp->io_hdr.dout_xfer_len; + if (pf) { + if (0 == alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: PF bit set bit param_list_len=0\n", __func__); + return 0; + } + } else { /* PF bit clear */ + if (alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: param_list_len>0 but PF clear\n", __func__); + return 0; + } else + return 0; /* nothing to do */ + if (dout_len > 0) { + if (vb) + pr2ws("%s: dout given but PF clear\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } + if (dout_len < 4) { + if (vb) + pr2ws("%s: dout length (%u bytes) too short\n", __func__, + dout_len); + return SCSI_PT_DO_BAD_PARAMS; + } + n = dout_len; + n = (n < alloc_len) ? n : alloc_len; + dop = (uint8_t *)ptp->io_hdr.dout_xferp; + if (! is_aligned(dop, pg_sz)) { /* caller best use sg_memalign(,pg_sz) */ + if (vb) + pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)ptp->io_hdr.dout_xferp); + return SCSI_PT_DO_BAD_PARAMS; + } + dpg_cd = dop[0]; + dpg_len = sg_get_unaligned_be16(dop + 2) + 4; + /* should we allow for more than one D_PG is dout ?? */ + n = (n < dpg_len) ? n : dpg_len; /* not yet ... */ + + if (vb) + pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n", + __func__, dpg_cd, dpg_len); + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x1d; /* MI send; hmmm same opcode as SEND DIAG */ + cmd.addr = (uint64_t)(sg_uintptr_t)dop; + cmd.data_len = 0x1000; /* NVMe 4k page size. Maybe determine this? */ + /* dout_len > 0x1000, is this a problem?? */ + cmd.cdw10 = 0x0804; /* NVMe Message Header */ + cmd.cdw11 = 0x9; /* nvme_mi_ses_send; (0x8 -> mi_ses_recv) */ + cmd.cdw13 = n; + res = do_nvme_admin_cmd(ptp, &cmd, dop, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } + } + return res; +} + +/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1) + * NVMe-MI has a special command (SES Receive) to read pages through a + * tunnel from an enclosure. The NVMe enclosure is meant to understand the + * SES (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_recvdiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool pcv; + int res; + uint8_t dpg_cd; + uint32_t alloc_len, n, din_len; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * dip; + struct sg_nvme_passthru_cmd cmd; + + pcv = !! (0x1 & cdbp[1]); + dpg_cd = cdbp[2]; + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + if (vb > 3) + pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__, + dpg_cd, (int)pcv, alloc_len); + din_len = ptp->io_hdr.din_xfer_len; + n = din_len; + n = (n < alloc_len) ? n : alloc_len; + dip = (uint8_t *)ptp->io_hdr.din_xferp; + if (! is_aligned(dip, pg_sz)) { /* caller best use sg_memalign(,pg_sz) */ + if (vb) + pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)ptp->io_hdr.din_xferp); + return SCSI_PT_DO_BAD_PARAMS; + } + + if (vb) + pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__, + dpg_cd); + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x1e; /* MI receive */ + cmd.addr = (uint64_t)(sg_uintptr_t)dip; + cmd.data_len = 0x1000; /* NVMe 4k page size. Maybe determine this? */ + /* din_len > 0x1000, is this a problem?? */ + cmd.cdw10 = 0x0804; /* NVMe Message Header */ + cmd.cdw11 = 0x8; /* nvme_mi_ses_receive */ + cmd.cdw12 = dpg_cd; + cmd.cdw13 = n; + res = do_nvme_admin_cmd(ptp, &cmd, dip, true, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } + ptp->io_hdr.din_resid = din_len - n; + return res; +} + +#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */ +#define F_SA_HIGH 0x100 /* as used by variable length cdbs */ +#define FF_SA (F_SA_HIGH | F_SA_LOW) +#define F_INV_OP 0x200 + +static struct opcode_info_t { + uint8_t opcode; + uint16_t sa; /* service action, 0 for none */ + uint32_t flags; /* OR-ed set of F_* flags */ + uint8_t len_mask[16]; /* len=len_mask[0], then mask for cdb[1]... */ + /* ignore cdb bytes after position 15 */ + } opcode_info_arr[] = { + {0x0, 0, 0, {6, /* TEST UNIT READY */ + 0, 0, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x3, 0, 0, {6, /* REQUEST SENSE */ + 0xe1, 0, 0, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x12, 0, 0, {6, /* INQUIRY */ + 0xe3, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x1c, 0, 0, {6, /* RECEIVE DIAGNOSTIC RESULTS */ + 0x1, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x1d, 0, 0, {6, /* SEND DIAGNOSTIC */ + 0xf7, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0xa0, 0, 0, {12, /* REPORT LUNS */ + 0xe3, 0xff, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} }, + {0xa3, 0xc, F_SA_LOW, {12, /* REPORT SUPPORTED OPERATION CODES */ + 0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, + 0} }, + {0xa3, 0xd, F_SA_LOW, {12, /* REPORT SUPPORTED TASK MAN. FUNCTIONS */ + 0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} }, + + {0xff, 0xffff, 0xffff, {0, /* Sentinel, keep as last element */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, +}; + +static int +sntl_rep_opcodes(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool rctd; + uint8_t reporting_opts, req_opcode, supp; + uint16_t req_sa, u; + uint32_t alloc_len, offset, a_len; + uint32_t pg_sz = sg_get_page_size(); + int k, len, count, bump; + const struct opcode_info_t *oip; + uint8_t *arr; + uint8_t *free_arr; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + rctd = !!(cdbp[2] & 0x80); /* report command timeout desc. */ + reporting_opts = cdbp[2] & 0x7; + req_opcode = cdbp[3]; + req_sa = sg_get_unaligned_be16(cdbp + 4); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4 || alloc_len > 0xffff) { + mk_sense_invalid_fld(ptp, true, 6, -1, vb); + return 0; + } + a_len = pg_sz - 72; + arr = sg_memalign(pg_sz, pg_sz, &free_arr, vb > 3); + if (NULL == arr) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + switch (reporting_opts) { + case 0: /* all commands */ + count = 0; + bump = rctd ? 20 : 8; + for (offset = 4, oip = opcode_info_arr; + (oip->flags != 0xffff) && (offset < a_len); ++oip) { + if (F_INV_OP & oip->flags) + continue; + ++count; + arr[offset] = oip->opcode; + sg_put_unaligned_be16(oip->sa, arr + offset + 2); + if (rctd) + arr[offset + 5] |= 0x2; + if (FF_SA & oip->flags) + arr[offset + 5] |= 0x1; + sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6); + if (rctd) + sg_put_unaligned_be16(0xa, arr + offset + 8); + offset += bump; + } + sg_put_unaligned_be32(count * bump, arr + 0); + break; + case 1: /* one command: opcode only */ + case 2: /* one command: opcode plus service action */ + case 3: /* one command: if sa==0 then opcode only else opcode+sa */ + for (oip = opcode_info_arr; oip->flags != 0xffff; ++oip) { + if ((req_opcode == oip->opcode) && (req_sa == oip->sa)) + break; + } + if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) { + supp = 1; + offset = 4; + } else { + if (1 == reporting_opts) { + if (FF_SA & oip->flags) { + mk_sense_invalid_fld(ptp, true, 2, 2, vb); + free(free_arr); + return 0; + } + req_sa = 0; + } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) { + mk_sense_invalid_fld(ptp, true, 4, -1, vb); + free(free_arr); + return 0; + } + if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode)) + supp = 3; + else if (0 == (FF_SA & oip->flags)) + supp = 1; + else if (req_sa != oip->sa) + supp = 1; + else + supp = 3; + if (3 == supp) { + u = oip->len_mask[0]; + sg_put_unaligned_be16(u, arr + 2); + arr[4] = oip->opcode; + for (k = 1; k < u; ++k) + arr[4 + k] = (k < 16) ? + oip->len_mask[k] : 0xff; + offset = 4 + u; + } else + offset = 4; + } + arr[1] = (rctd ? 0x80 : 0) | supp; + if (rctd) { + sg_put_unaligned_be16(0xa, arr + offset); + offset += 12; + } + break; + default: + mk_sense_invalid_fld(ptp, true, 2, 2, vb); + free(free_arr); + return 0; + } + offset = (offset < a_len) ? offset : a_len; + len = (offset < alloc_len) ? offset : alloc_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len; + if (len > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, arr, len); + free(free_arr); + return 0; +} + +static int +sntl_rep_tmfs(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool repd; + uint32_t alloc_len, len; + uint8_t arr[16]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + memset(arr, 0, sizeof(arr)); + repd = !!(cdbp[2] & 0x80); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4) { + mk_sense_invalid_fld(ptp, true, 6, -1, vb); + return 0; + } + arr[0] = 0xc8; /* ATS | ATSS | LURS */ + arr[1] = 0x1; /* ITNRS */ + if (repd) { + arr[3] = 0xc; + len = 16; + } else + len = 4; + + len = (len < alloc_len) ? len : alloc_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len; + if (len > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, arr, len); + 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. */ +int +sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb) +{ + bool scsi_cdb; + bool is_read = false; + int n, len; + uint16_t sa; + struct sg_pt_linux_scsi * ptp = &vp->impl; + struct sg_nvme_passthru_cmd cmd; + const uint8_t * cdbp; + void * dp = NULL; + + if (! ptp->io_hdr.request) { + if (vb) + pr2ws("No NVMe command given (set_scsi_pt_cdb())\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + if (fd >= 0) { + if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) { + if (vb) + 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 (vb) + pr2ws("%s: invalid file descriptors\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + n = ptp->io_hdr.request_len; + cdbp = (const uint8_t *)ptp->io_hdr.request; + if (vb > 3) + pr2ws("%s: opcode=0x%x, fd=%d, time_secs=%d\n", __func__, cdbp[0], + fd, time_secs); + scsi_cdb = sg_is_scsi_cdb(cdbp, n); + /* direct NVMe command (i.e. 64 bytes long) or SNTL */ + ptp->nvme_direct = ! scsi_cdb; + if (scsi_cdb) { + switch (cdbp[0]) { + case SCSI_INQUIRY_OPC: + return sntl_inq(ptp, cdbp, time_secs, vb); + case SCSI_REPORT_LUNS_OPC: + return sntl_rluns(ptp, cdbp, time_secs, vb); + case SCSI_TEST_UNIT_READY_OPC: + return sntl_tur(ptp, time_secs, vb); + case SCSI_REQUEST_SENSE_OPC: + return sntl_req_sense(ptp, cdbp, time_secs, vb); + case SCSI_SEND_DIAGNOSTIC_OPC: + return sntl_senddiag(ptp, cdbp, time_secs, vb); + case SCSI_RECEIVE_DIAGNOSTIC_OPC: + return sntl_recvdiag(ptp, cdbp, time_secs, vb); + case SCSI_MAINT_IN_OPC: + sa = 0x1f & cdbp[1]; /* service action */ + if (SCSI_REP_SUP_OPCS_OPC == sa) + return sntl_rep_opcodes(ptp, cdbp, time_secs, vb); + else if (SCSI_REP_SUP_TMFS_OPC == sa) + return sntl_rep_tmfs(ptp, cdbp, time_secs, vb); + /* fall through */ + default: + if (vb > 2) { + char b[64]; + + sg_get_command_name(cdbp, -1, sizeof(b), b); + pr2ws("%s: no translation to NVMe for SCSI %s command\n", + __func__, b); + } + mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE, + 0, vb); + return 0; + } + } + 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, (const uint8_t *)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; + dp = (void *)ptp->io_hdr.din_xferp; + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.din_xferp; + is_read = true; + } else if (ptp->io_hdr.dout_xfer_len > 0) { + cmd.data_len = ptp->io_hdr.dout_xfer_len; + dp = (void *)ptp->io_hdr.dout_xferp; + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.dout_xferp; + is_read = false; + } + return do_nvme_admin_cmd(ptp, &cmd, dp, is_read, time_secs, vb); +} + +#else /* (HAVE_NVME && (! IGNORE_NVME)) */ + +int +sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb) +{ + if (vb) + pr2ws("%s: not supported\n", __func__); + if (vp) { ; } /* suppress warning */ + if (fd) { ; } /* suppress warning */ + if (time_secs) { ; } /* suppress warning */ + return -ENOTTY; /* inappropriate ioctl error */ +} + +#endif /* (HAVE_NVME && (! IGNORE_NVME)) */ diff --git a/tools/sg_write_buffer/sg_write_buffer.c b/tools/sg_write_buffer/sg_write_buffer.c new file mode 100644 index 0000000..18d8f6f --- /dev/null +++ b/tools/sg_write_buffer/sg_write_buffer.c @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2006-2018 Luben Tuikov and 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. + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <ctype.h> +#include <string.h> +#include <getopt.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +#ifdef SG_LIB_WIN32 +#ifdef SG_LIB_WIN32_DIRECT +#include "sg_pt.h" /* needed for scsi_pt_win32_direct() */ +#endif +#endif + +/* + * This utility issues the SCSI WRITE BUFFER command to the given device. + */ + +static const char * version_str = "1.24 20180111"; /* spc5r18 */ + +#define ME "sg_write_buffer: " +#define DEF_XFER_LEN (8 * 1024 * 1024) +#define EBUFF_SZ 256 + +#define WRITE_BUFFER_CMD 0x3b +#define WRITE_BUFFER_CMDLEN 10 +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define DEF_PT_TIMEOUT 300 /* 300 seconds, 5 minutes */ + +static struct option long_options[] = { + {"bpw", required_argument, 0, 'b'}, + {"dry-run", no_argument, 0, 'd'}, + {"dry_run", no_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"id", required_argument, 0, 'i'}, + {"in", required_argument, 0, 'I'}, + {"length", required_argument, 0, 'l'}, + {"mode", required_argument, 0, 'm'}, + {"offset", required_argument, 0, 'o'}, + {"read-stdin", no_argument, 0, 'r'}, + {"read_stdin", no_argument, 0, 'r'}, + {"raw", no_argument, 0, 'r'}, + {"skip", required_argument, 0, 's'}, + {"specific", required_argument, 0, 'S'}, + {"timeout", required_argument, 0, 't' }, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + + +static void +usage() +{ + pr2serr("Usage: " + "sg_write_buffer [--bpw=CS] [--dry-run] [--help] [--id=ID] " + "[--in=FILE]\n" + " [--length=LEN] [--mode=MO] " + "[--offset=OFF]\n" + " [--read-stdin] [--skip=SKIP] " + "[--specific=MS]\n" + " [--timeout=TO] [--verbose] [--version] " + "DEVICE\n" + " where:\n" + " --bpw=CS|-b CS CS is chunk size: bytes per write " + "buffer\n" + " command (def: 0 -> as many as " + "possible)\n" + " --dry-run|-d skip WRITE BUFFER commands, do " + "everything else\n" + " --help|-h print out usage message then exit\n" + " --id=ID|-i ID buffer identifier (0 (default) to " + "255)\n" + " --in=FILE|-I FILE read from FILE ('-I -' read " + "from stdin)\n" + " --length=LEN|-l LEN length in bytes to write; may be " + "deduced from\n" + " FILE\n" + " --mode=MO|-m MO write buffer mode, MO is number or " + "acronym\n" + " (def: 0 -> 'combined header and " + "data' (obs))\n" + " --offset=OFF|-o OFF buffer offset (unit: bytes, def: 0)\n" + " --read-stdin|-r read from stdin (same as '-I -')\n" + " --skip=SKIP|-s SKIP bytes in file FILE to skip before " + "reading\n" + " --specific=MS|-S MS mode specific value; 3 bit field " + "(0 to 7)\n" + " --timeout=TO|-t TO command timeout in seconds (def: " + "300)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Performs one or more SCSI WRITE BUFFER commands. Use '-m xxx' " + "to list\navailable modes. A chunk size of 4 KB ('--bpw=4k') " + "seems to work well.\nExample: sg_write_buffer -b 4k -I xxx.lod " + "-m 7 /dev/sg3\n" + ); + +} + +#define MODE_HEADER_DATA 0 +#define MODE_VENDOR 1 +#define MODE_DATA 2 +#define MODE_DNLD_MC 4 +#define MODE_DNLD_MC_SAVE 5 +#define MODE_DNLD_MC_OFFS 6 +#define MODE_DNLD_MC_OFFS_SAVE 7 +#define MODE_ECHO_BUFFER 0x0A +#define MODE_DNLD_MC_EV_OFFS_DEFER 0x0D +#define MODE_DNLD_MC_OFFS_DEFER 0x0E +#define MODE_ACTIVATE_MC 0x0F +#define MODE_EN_EX_ECHO 0x1A +#define MODE_DIS_EX 0x1B +#define MODE_DNLD_ERR_HISTORY 0x1C + + +struct mode_s { + const char *mode_string; + int mode; + const char *comment; +}; + +static struct mode_s mode_arr[] = { + {"hd", MODE_HEADER_DATA, "combined header and data " + "(obsolete)"}, + {"vendor", MODE_VENDOR, "vendor specific"}, + {"data", MODE_DATA, "data"}, + {"dmc", MODE_DNLD_MC, "download microcode and activate"}, + {"dmc_save", MODE_DNLD_MC_SAVE, "download microcode, save and " + "activate"}, + {"dmc_offs", MODE_DNLD_MC_OFFS, "download microcode with offsets " + "and activate"}, + {"dmc_offs_save", MODE_DNLD_MC_OFFS_SAVE, "download microcode with " + "offsets, save and\n\t\t\t\tactivate"}, + {"echo", MODE_ECHO_BUFFER, "write data to echo buffer"}, + {"dmc_offs_ev_defer", MODE_DNLD_MC_EV_OFFS_DEFER, "download " + "microcode with offsets, select\n\t\t\t\tactivation event, " + "save and defer activation"}, + {"dmc_offs_defer", MODE_DNLD_MC_OFFS_DEFER, "download microcode " + "with offsets, save and\n\t\t\t\tdefer activation"}, + {"activate_mc", MODE_ACTIVATE_MC, "activate deferred microcode"}, + {"en_ex", MODE_EN_EX_ECHO, "enable expander communications " + "protocol and\n\t\t\t\techo buffer (obsolete)"}, + {"dis_ex", MODE_DIS_EX, "disable expander communications " + "protocol\n\t\t\t\t(obsolete)"}, + {"deh", MODE_DNLD_ERR_HISTORY, "download application client " + "error history "}, + {NULL, 0, NULL}, +}; + +static void +print_modes(void) +{ + const struct mode_s * mp; + + pr2serr("The modes parameter argument can be numeric (hex or decimal)\n" + "or symbolic:\n"); + for (mp = mode_arr; mp->mode_string; ++mp) { + pr2serr(" %2d (0x%02x) %-18s%s\n", mp->mode, mp->mode, + mp->mode_string, mp->comment); + } + pr2serr("\nAdditionally '--bpw=<val>,act' does a activate deferred " + "microcode after\nsuccessful dmc_offs_defer and " + "dmc_offs_ev_defer mode downloads.\n"); +} + + +int +main(int argc, char * argv[]) +{ + bool bpw_then_activate = false; + bool dry_run = false; + bool got_stdin = false; + bool wb_len_given = false; + int sg_fd, infd, res, c, len, k, n; + int bpw = 0; + int do_help = 0; + int ret = 0; + int verbose = 0; + int wb_id = 0; + int wb_len = 0; + int wb_mode = 0; + int wb_offset = 0; + int wb_skip = 0; + int wb_timeout = DEF_PT_TIMEOUT; + int wb_mspec = 0; + const char * device_name = NULL; + const char * file_name = NULL; + unsigned char * dop = NULL; + char * cp; + const struct mode_s * mp; + char ebuff[EBUFF_SZ]; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "b:dhi:I:l:m:o:rs:S:t:vV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'b': + bpw = sg_get_num(optarg); + if (bpw < 0) { + pr2serr("argument to '--bpw' should be in a positive " + "number\n"); + return SG_LIB_SYNTAX_ERROR; + } + if ((cp = strchr(optarg, ','))) { + if (0 == strncmp("act", cp + 1, 3)) + bpw_then_activate = true; + } + break; + case 'd': + dry_run = true; + break; + case 'h': + case '?': + ++do_help; + break; + case 'i': + wb_id = sg_get_num(optarg); + if ((wb_id < 0) || (wb_id > 255)) { + pr2serr("argument to '--id' should be in the range 0 to " + "255\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'I': + file_name = optarg; + break; + case 'l': + wb_len = sg_get_num(optarg); + if (wb_len < 0) { + pr2serr("bad argument to '--length'\n"); + return SG_LIB_SYNTAX_ERROR; + } + wb_len_given = true; + break; + case 'm': + if (isdigit(*optarg)) { + wb_mode = sg_get_num(optarg); + if ((wb_mode < 0) || (wb_mode > 31)) { + pr2serr("argument to '--mode' should be in the range 0 " + "to 31\n"); + return SG_LIB_SYNTAX_ERROR; + } + } else { + len = strlen(optarg); + for (mp = mode_arr; mp->mode_string; ++mp) { + if (0 == strncmp(mp->mode_string, optarg, len)) { + wb_mode = mp->mode; + break; + } + } + if (! mp->mode_string) { + print_modes(); + return SG_LIB_SYNTAX_ERROR; + } + } + break; + case 'o': + wb_offset = sg_get_num(optarg); + if (wb_offset < 0) { + pr2serr("bad argument to '--offset'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'r': /* --read-stdin and --raw (previous name) */ + file_name = "-"; + break; + case 's': + wb_skip = sg_get_num(optarg); + if (wb_skip < 0) { + pr2serr("bad argument to '--skip'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'S': + wb_mspec = sg_get_num(optarg); + if ((wb_mspec < 0) || (wb_mspec > 7)) { + pr2serr("expected argument to '--specific' to be 0 to 7\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 't': + wb_timeout = sg_get_num(optarg); + if (wb_timeout < 0) { + pr2serr("Invalid argument to '--timeout'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'v': + ++verbose; + break; + case 'V': + pr2serr(ME "version: %s\n", version_str); + return 0; + default: + pr2serr("unrecognised option code 0x%x ??\n", c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (do_help) { + if (do_help > 1) { + usage(); + pr2serr("\n"); + print_modes(); + } else + usage(); + return 0; + } + if (optind < argc) { + if (NULL == device_name) { + device_name = argv[optind]; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + pr2serr("Unexpected extra argument: %s\n", argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + + if (NULL == device_name) { + pr2serr("missing device name!\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + + if ((wb_len > 0) && (bpw > wb_len)) { + pr2serr("trim chunk size (CS) to be the same as LEN\n"); + bpw = wb_len; + } + +#ifdef SG_LIB_WIN32 +#ifdef SG_LIB_WIN32_DIRECT + if (verbose > 4) + pr2serr("Initial win32 SPT interface state: %s\n", + scsi_pt_win32_spt_state() ? "direct" : "indirect"); + scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */); +#endif +#endif + + sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose); + if (sg_fd < 0) { + pr2serr(ME "open error: %s: %s\n", device_name, + safe_strerror(-sg_fd)); + return SG_LIB_FILE_ERROR; + } + if (file_name || (wb_len > 0)) { + if (0 == wb_len) + wb_len = DEF_XFER_LEN; + if (NULL == (dop = (unsigned char *)malloc(wb_len))) { + pr2serr(ME "out of memory\n"); + ret = SG_LIB_SYNTAX_ERROR; + goto err_out; + } + memset(dop, 0xff, wb_len); + if (file_name) { + got_stdin = (0 == strcmp(file_name, "-")); + if (got_stdin) { + if (wb_skip > 0) { + pr2serr("Can't skip on stdin\n"); + ret = SG_LIB_FILE_ERROR; + goto err_out; + } + infd = STDIN_FILENO; + } else { + if ((infd = open(file_name, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + ME "could not open %s for reading", file_name); + perror(ebuff); + ret = SG_LIB_FILE_ERROR; + goto err_out; + } else if (sg_set_binary_mode(infd) < 0) + perror("sg_set_binary_mode"); + if (wb_skip > 0) { + if (lseek(infd, wb_skip, SEEK_SET) < 0) { + snprintf(ebuff, EBUFF_SZ, ME "couldn't skip to " + "required position on %s", file_name); + perror(ebuff); + close(infd); + ret = SG_LIB_FILE_ERROR; + goto err_out; + } + } + } + res = read(infd, dop, wb_len); + if (res < 0) { + snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s", + file_name); + perror(ebuff); + if (! got_stdin) + close(infd); + ret = SG_LIB_FILE_ERROR; + goto err_out; + } + if (res < wb_len) { + if (wb_len_given) { + pr2serr("tried to read %d bytes from %s, got %d bytes\n", + wb_len, file_name, res); + pr2serr("pad with 0xff bytes and continue\n"); + } else { + if (verbose) { + pr2serr("tried to read %d bytes from %s, got %d " + "bytes\n", wb_len, file_name, res); + pr2serr("will write %d bytes", res); + if ((bpw > 0) && (bpw < wb_len)) + pr2serr(", %d bytes per WRITE BUFFER command\n", + bpw); + else + pr2serr("\n"); + } + wb_len = res; + } + } + if (! got_stdin) + close(infd); + } + } + + res = 0; + if (bpw > 0) { + for (k = 0; k < wb_len; k += n) { + n = wb_len - k; + if (n > bpw) + n = bpw; + if (verbose) + pr2serr("sending write buffer, mode=0x%x, mspec=%d, id=%d, " + " offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id, + wb_offset + k, n); + if (dry_run) { + if (verbose) + pr2serr("skipping WRITE BUFFER command due to " + "--dry-run\n"); + res = 0; + } else + res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id, + wb_offset + k, dop + k, n, + wb_timeout, true, verbose); + if (res) + break; + } + if (bpw_then_activate) { + if (verbose) + pr2serr("sending Activate deferred microcode [0xf]\n"); + if (dry_run) { + if (verbose) + pr2serr("skipping WRITE BUFFER(ACTIVATE) command due to " + "--dry-run\n"); + res = 0; + } else + res = sg_ll_write_buffer_v2(sg_fd, MODE_ACTIVATE_MC, + 0 /* buffer_id */, + 0 /* buffer_offset */, 0, + NULL, 0, wb_timeout, true, + verbose); + } + } else { + if (verbose) + pr2serr("sending single write buffer, mode=0x%x, mpsec=%d, " + "id=%d, offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id, + wb_offset, wb_len); + if (dry_run) { + if (verbose) + pr2serr("skipping WRITE BUFFER(all in one) command due to " + "--dry-run\n"); + res = 0; + } else + res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id, + wb_offset, dop, wb_len, wb_timeout, + true, verbose); + } + if (0 != res) { + char b[80]; + + ret = res; + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("Write buffer failed: %s\n", b); + } + +err_out: + if (dop) + free(dop); + res = sg_cmds_close_device(sg_fd); + if (res < 0) { + pr2serr("close error: %s\n", safe_strerror(-res)); + if (0 == ret) + return SG_LIB_FILE_ERROR; + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} -- 2.15.0.531.g2ccb3012c9-goog ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU 2018-03-14 0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim @ 2018-03-14 2:10 ` Junling Zheng 2018-03-14 4:27 ` [PATCH 2/2 v2] " Jaegeuk Kim 2018-03-16 8:29 ` [PATCH 2/2] " Chao Yu 1 sibling, 1 reply; 8+ messages in thread From: Junling Zheng @ 2018-03-14 2:10 UTC (permalink / raw) To: Jaegeuk Kim, linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim Hi, Hyojun, Jaegeuk, On 2018/3/14 8:08, Jaegeuk Kim wrote: > From: Hyojun Kim <hyojun@google.com> > > sg_write_buffer sources are added for FFU. > > Signed-off-by: Hyojun Kim <hyojun@google.com> > Signed-off-by: Jaegeuk Kim <jaegeuk@google.com> > --- > tools/sg_write_buffer/Android.bp | 27 + Maybe it's better to change Android.bp into Makefile.am ? Thanks, Junling ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2 v2] tools: sg_write_buffer: add sg_write_buffer for FFU 2018-03-14 2:10 ` Junling Zheng @ 2018-03-14 4:27 ` Jaegeuk Kim 0 siblings, 0 replies; 8+ messages in thread From: Jaegeuk Kim @ 2018-03-14 4:27 UTC (permalink / raw) To: Junling Zheng; +Cc: Hyojun Kim, Jaegeuk Kim, linux-f2fs-devel sg_write_buffer sources are added for FFU. Signed-off-by: Hyojun Kim <hyojun@google.com> Signed-off-by: Jaegeuk Kim <jaegeuk@google.com> --- v2: - add Makefile.am - fix some build errors configure.ac | 1 + tools/Makefile.am | 2 + tools/sg_write_buffer/Android.bp | 27 + tools/sg_write_buffer/Makefile.am | 18 + tools/sg_write_buffer/include/freebsd_nvme_ioctl.h | 156 + tools/sg_write_buffer/include/sg_cmds.h | 21 + tools/sg_write_buffer/include/sg_cmds_basic.h | 310 ++ tools/sg_write_buffer/include/sg_cmds_extra.h | 369 +++ tools/sg_write_buffer/include/sg_cmds_mmc.h | 52 + tools/sg_write_buffer/include/sg_io_linux.h | 185 ++ tools/sg_write_buffer/include/sg_lib.h | 602 ++++ tools/sg_write_buffer/include/sg_lib_data.h | 121 + tools/sg_write_buffer/include/sg_linux_inc.h | 56 + tools/sg_write_buffer/include/sg_pr2serr.h | 30 + tools/sg_write_buffer/include/sg_pt.h | 215 ++ tools/sg_write_buffer/include/sg_pt_linux.h | 171 + tools/sg_write_buffer/include/sg_pt_nvme.h | 172 + tools/sg_write_buffer/include/sg_pt_win32.h | 473 +++ tools/sg_write_buffer/include/sg_unaligned.h | 325 ++ tools/sg_write_buffer/sg_cmds_basic.c | 663 ++++ tools/sg_write_buffer/sg_cmds_basic2.c | 1069 ++++++ tools/sg_write_buffer/sg_cmds_extra.c | 2524 ++++++++++++++ tools/sg_write_buffer/sg_cmds_mmc.c | 382 +++ tools/sg_write_buffer/sg_io_linux.c | 256 ++ tools/sg_write_buffer/sg_lib.c | 3494 ++++++++++++++++++++ tools/sg_write_buffer/sg_lib_data.c | 1688 ++++++++++ tools/sg_write_buffer/sg_pt_common.c | 141 + tools/sg_write_buffer/sg_pt_linux.c | 964 ++++++ tools/sg_write_buffer/sg_pt_linux_nvme.c | 1185 +++++++ tools/sg_write_buffer/sg_write_buffer.c | 516 +++ 30 files changed, 16188 insertions(+) create mode 100644 tools/sg_write_buffer/Android.bp create mode 100644 tools/sg_write_buffer/Makefile.am create mode 100644 tools/sg_write_buffer/include/freebsd_nvme_ioctl.h create mode 100644 tools/sg_write_buffer/include/sg_cmds.h create mode 100644 tools/sg_write_buffer/include/sg_cmds_basic.h create mode 100644 tools/sg_write_buffer/include/sg_cmds_extra.h create mode 100644 tools/sg_write_buffer/include/sg_cmds_mmc.h create mode 100644 tools/sg_write_buffer/include/sg_io_linux.h create mode 100644 tools/sg_write_buffer/include/sg_lib.h create mode 100644 tools/sg_write_buffer/include/sg_lib_data.h create mode 100644 tools/sg_write_buffer/include/sg_linux_inc.h create mode 100644 tools/sg_write_buffer/include/sg_pr2serr.h create mode 100644 tools/sg_write_buffer/include/sg_pt.h create mode 100644 tools/sg_write_buffer/include/sg_pt_linux.h create mode 100644 tools/sg_write_buffer/include/sg_pt_nvme.h create mode 100644 tools/sg_write_buffer/include/sg_pt_win32.h create mode 100644 tools/sg_write_buffer/include/sg_unaligned.h create mode 100644 tools/sg_write_buffer/sg_cmds_basic.c create mode 100644 tools/sg_write_buffer/sg_cmds_basic2.c create mode 100644 tools/sg_write_buffer/sg_cmds_extra.c create mode 100644 tools/sg_write_buffer/sg_cmds_mmc.c create mode 100644 tools/sg_write_buffer/sg_io_linux.c create mode 100644 tools/sg_write_buffer/sg_lib.c create mode 100644 tools/sg_write_buffer/sg_lib_data.c create mode 100644 tools/sg_write_buffer/sg_pt_common.c create mode 100644 tools/sg_write_buffer/sg_pt_linux.c create mode 100644 tools/sg_write_buffer/sg_pt_linux_nvme.c create mode 100644 tools/sg_write_buffer/sg_write_buffer.c diff --git a/configure.ac b/configure.ac index ea57d54..64c156e 100644 --- a/configure.ac +++ b/configure.ac @@ -196,6 +196,7 @@ AC_CONFIG_FILES([ mkfs/Makefile fsck/Makefile tools/Makefile + tools/sg_write_buffer/Makefile ]) # export library version info for mkfs/libf2fs_format_la diff --git a/tools/Makefile.am b/tools/Makefile.am index 81cf89b..25a8c6f 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -13,3 +13,5 @@ f2fscrypt_SOURCES = f2fscrypt.c sha512.c f2fscrypt_LDFLAGS = -luuid dist_man_MANS = f2fscrypt.8 endif + +SUBDIRS = sg_write_buffer diff --git a/tools/sg_write_buffer/Android.bp b/tools/sg_write_buffer/Android.bp new file mode 100644 index 0000000..5222a59 --- /dev/null +++ b/tools/sg_write_buffer/Android.bp @@ -0,0 +1,27 @@ +cc_defaults { + name: "sg3-utils-defaults", + cflags: [ + "-Wno-unused-function" + ], + local_include_dirs: [ + "include", + ], +} + +cc_binary { + name: "sg_write_buffer", + defaults: [ "sg3-utils-defaults" ], + srcs: [ + "sg_write_buffer.c", + "sg_cmds_basic.c", + "sg_cmds_basic2.c", + "sg_cmds_extra.c", + "sg_cmds_mmc.c", + "sg_io_linux.c", + "sg_lib.c", + "sg_lib_data.c", + "sg_pt_common.c", + "sg_pt_linux.c", + "sg_pt_linux_nvme.c", + ], +} diff --git a/tools/sg_write_buffer/Makefile.am b/tools/sg_write_buffer/Makefile.am new file mode 100644 index 0000000..922c328 --- /dev/null +++ b/tools/sg_write_buffer/Makefile.am @@ -0,0 +1,18 @@ +## Makefile.am + +if LINUX +AM_CPPFLAGS = -I./include +AM_CFLAGS = -Wall +sbin_PROGRAMS = sg_write_buffer +sg_write_buffer_SOURCES = sg_write_buffer.c \ + sg_cmds_basic.c \ + sg_cmds_basic2.c \ + sg_cmds_extra.c \ + sg_cmds_mmc.c \ + sg_io_linux.c \ + sg_lib.c \ + sg_lib_data.c \ + sg_pt_common.c \ + sg_pt_linux.c \ + sg_pt_linux_nvme.c +endif diff --git a/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h b/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h new file mode 100644 index 0000000..f5d2443 --- /dev/null +++ b/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h @@ -0,0 +1,156 @@ +PROPS-END +/*- + * Copyright (C) 2012-2013 Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + + +#include <sys/param.h> + +#define NVME_PASSTHROUGH_CMD _IOWR('n', 0, struct nvme_pt_command) + +#if __FreeBSD_version < 1100110 +struct nvme_command +{ + /* dword 0 */ + uint16_t opc : 8; /* opcode */ + uint16_t fuse : 2; /* fused operation */ + uint16_t rsvd1 : 6; + uint16_t cid; /* command identifier */ + + /* dword 1 */ + uint32_t nsid; /* namespace identifier */ + + /* dword 2-3 */ + uint32_t rsvd2; + uint32_t rsvd3; + + /* dword 4-5 */ + uint64_t mptr; /* metadata pointer */ + + /* dword 6-7 */ + uint64_t prp1; /* prp entry 1 */ + + /* dword 8-9 */ + uint64_t prp2; /* prp entry 2 */ + + /* dword 10-15 */ + uint32_t cdw10; /* command-specific */ + uint32_t cdw11; /* command-specific */ + uint32_t cdw12; /* command-specific */ + uint32_t cdw13; /* command-specific */ + uint32_t cdw14; /* command-specific */ + uint32_t cdw15; /* command-specific */ +} __packed; + +struct nvme_status { + + uint16_t p : 1; /* phase tag */ + uint16_t sc : 8; /* status code */ + uint16_t sct : 3; /* status code type */ + uint16_t rsvd2 : 2; + uint16_t m : 1; /* more */ + uint16_t dnr : 1; /* do not retry */ +} __packed; + +struct nvme_completion { + + /* dword 0 */ + uint32_t cdw0; /* command-specific */ + + /* dword 1 */ + uint32_t rsvd1; + + /* dword 2 */ + uint16_t sqhd; /* submission queue head pointer */ + uint16_t sqid; /* submission queue identifier */ + + /* dword 3 */ + uint16_t cid; /* command identifier */ + struct nvme_status status; +} __packed; + +struct nvme_pt_command { + + /* + * cmd is used to specify a passthrough command to a controller or + * namespace. + * + * The following fields from cmd may be specified by the caller: + * * opc (opcode) + * * nsid (namespace id) - for admin commands only + * * cdw10-cdw15 + * + * Remaining fields must be set to 0 by the caller. + */ + struct nvme_command cmd; + + /* + * cpl returns completion status for the passthrough command + * specified by cmd. + * + * The following fields will be filled out by the driver, for + * consumption by the caller: + * * cdw0 + * * status (except for phase) + * + * Remaining fields will be set to 0 by the driver. + */ + struct nvme_completion cpl; + + /* buf is the data buffer associated with this passthrough command. */ + void * buf; + + /* + * len is the length of the data buffer associated with this + * passthrough command. + */ + uint32_t len; + + /* + * is_read = 1 if the passthrough command will read data into the + * supplied buffer from the controller. + * + * is_read = 0 if the passthrough command will write data from the + * supplied buffer to the controller. + */ + uint32_t is_read; + + /* + * driver_lock is used by the driver only. It must be set to 0 + * by the caller. + */ + struct mtx * driver_lock; +}; +#else +#include <dev/nvme/nvme.h> +#endif + +#define nvme_completion_is_error(cpl) \ + ((cpl)->status.sc != 0 || (cpl)->status.sct != 0) + +#define NVME_CTRLR_PREFIX "/dev/nvme" +#define NVME_NS_PREFIX "ns" diff --git a/tools/sg_write_buffer/include/sg_cmds.h b/tools/sg_write_buffer/include/sg_cmds.h new file mode 100644 index 0000000..690f53a --- /dev/null +++ b/tools/sg_write_buffer/include/sg_cmds.h @@ -0,0 +1,21 @@ +#ifndef SG_CMDS_H +#define SG_CMDS_H + +/******************************************************************** + * This header did contain wrapper declarations for many SCSI commands + * up until sg3_utils version 1.22 . In that version, the command + * wrappers were broken into two groups, the 'basic' ones found in the + * "sg_cmds_basic.h" header and the 'extra' ones found in the + * "sg_cmds_extra.h" header. This header now simply includes those two + * headers. + * In sg3_utils version 1.26 the sg_cmds_mmc.h header was added and + * contains some MMC specific commands. + * The corresponding function definitions are found in the sg_cmds_basic.c, + * sg_cmds_extra.c and sg_cmds_mmc.c files. + ********************************************************************/ + +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#include "sg_cmds_mmc.h" + +#endif diff --git a/tools/sg_write_buffer/include/sg_cmds_basic.h b/tools/sg_write_buffer/include/sg_cmds_basic.h new file mode 100644 index 0000000..507effa --- /dev/null +++ b/tools/sg_write_buffer/include/sg_cmds_basic.h @@ -0,0 +1,310 @@ +#ifndef SG_CMDS_BASIC_H +#define SG_CMDS_BASIC_H + +/* + * Copyright (c) 2004-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. + */ + +/* + * Error, warning and verbose output is sent to the file pointed to by + * sg_warnings_strm which is declared in sg_lib.h and can be set with + * the sg_set_warnings_strm() function. If not given sg_warnings_strm + * defaults to stderr. + * If 'noisy' is false and 'verbose' is zero then following functions should + * not output anything to sg_warnings_strm. If 'noisy' is true and + * 'verbose' is zero then Unit Attention, Recovered, Medium and Hardware + * errors (sense keys) send output to sg_warnings_strm. Increasing values + * of 'verbose' send increasing amounts of (debug) output to + * sg_warnings_strm. + */ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Invokes a SCSI INQUIRY command and yields the response + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other errors */ +int sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. + * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so + * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION + * CODES command instead). Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI LOG SELECT command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Log Select not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ABORTED_COMMAND, * SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code, + int subpg_code, unsigned char * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Log Sense not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, unsigned char * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Same as sg_ll_log_sense() apart from timeout_secs and residp. See + * sg_ll_inquiry_v2() for their description */ +int sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, unsigned char * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Same as sg_ll_mode_sense10() apart from timeout_secs and residp. See + * sg_ll_inquiry_v2() for their description */ +int sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, + int pg_code, int sub_pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command (SPC-3) + * prevent==0 allows removal, prevent==1 prevents removal ... + * Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> command not supported + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose); + +/* Invokes a SCSI READ CAPACITY (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION + * -> perhaps media changed, SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success, + * SG_LIB_CAT_UNIT_ATTENTION -> media changed??, SG_LIB_CAT_INVALID_OP + * -> cdb not supported, SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Luns not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */ +int sg_ll_report_luns(int sg_fd, int select_report, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI REQUEST SENSE command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Request Sense not supported??, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI START STOP UNIT command (SBC + MMC). + * Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Start stop unit not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure + * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and + * format_layer_number(mmc) fields. They also overlap on the noflush(sbc) + * and fl(mmc) one bit field. This is the cause of the awkardly named + * pc_mod__fl_num and noflush__fl arguments to this function. */ +int sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num, + int power_cond, bool noflush__fl, bool loej, + bool start, bool noisy, int verbose); + +/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_INVALID_OP -> cdb not supported, + * SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */ +int sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group, + unsigned int lba, unsigned int count, bool noisy, + int verbose); + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other failure */ +int sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose); + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Looks for progress indicator if 'progress' non-NULL; + * if found writes value [0..65535] else write -1. + * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ABORTED_COMMAND, SG_LIB_CAT_NOT_READY -> + * device not ready, -1 -> other failure */ +int sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress, + bool noisy, int verbose); + + +struct sg_simple_inquiry_resp { + unsigned char peripheral_qualifier; + unsigned char peripheral_type; + unsigned char byte_1; /* was 'rmb' prior to version 1.39 */ + /* now rmb == !!(0x80 & byte_1) */ + unsigned char version; /* as per recent drafts: whole of byte 2 */ + unsigned char byte_3; + unsigned char byte_5; + unsigned char byte_6; + unsigned char byte_7; + char vendor[9]; /* T10 field is 8 bytes, NUL char appended */ + char product[17]; + char revision[5]; +}; + +/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response. + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other errors */ +int sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data, + bool noisy, int verbose); + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. In most cases users are + * interested in the first mode page. This function returns the (byte) + * offset of the start of the first mode page. Set mode_sense_6 to true for + * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful + * or -1 if failure. If there is a failure a message is written to err_buff + * if it is non-NULL and err_buff_len > 0. */ +int sg_mode_page_offset(const unsigned char * resp, int resp_len, + bool mode_sense_6, char * err_buff, int err_buff_len); + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. This functions returns the + * length (in bytes) of those three components. Note that the return value + * can exceed resp_len in which case the MODE SENSE command should be + * re-issued with a larger response buffer. If bd_lenp is non-NULL and if + * successful the block descriptor length (in bytes) is written to *bd_lenp. + * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10) + * responses. Returns -1 if there is an error (e.g. response too short). */ +int sg_msense_calc_length(const unsigned char * resp, int resp_len, + bool mode_sense_6, int * bd_lenp); + +/* Fetches current, changeable, default and/or saveable modes pages as + * indicated by pcontrol_arr for given pg_code and sub_pg_code. If + * mode6==0 then use MODE SENSE (10) else use MODE SENSE (6). If + * flexible set and mode data length seems wrong then try and + * fix (compensating hack for bad device or driver). pcontrol_arr + * should have 4 elements for output of current, changeable, default + * and saved values respectively. Each element should be NULL or + * at least mx_mpage_len bytes long. + * Return of 0 -> overall success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_MALFORMED -> bad response, -1 -> other failure. + * If success_mask pointer is not NULL then first zeros it. Then set bits + * 0, 1, 2 and/or 3 if the current, changeable, default and saved values + * respectively have been fetched. If error on current page + * then stops and returns that error; otherwise continues if an error is + * detected but returns the first error encountered. */ +int sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, + int sub_pg_code, bool dbd, bool flexible, + int mx_mpage_len, int * success_mask, + void * pcontrol_arr[], int * reported_lenp, + int verbose); + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. Implementation calls scsi_pt_open_device(). */ +int sg_cmds_open_device(const char * device_name, bool read_only, int verbose); + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. Implementation calls scsi_pt_open_flags(). */ +int sg_cmds_open_flags(const char * device_name, int flags, int verbose); + +/* Returns 0 if successful. If error in Unix returns negated errno. + Implementation calls scsi_pt_close_device(). */ +int sg_cmds_close_device(int device_fd); + +const char * sg_cmds_version(); + +#define SG_NO_DATA_IN 0 + +struct sg_pt_base; + +/* This is a helper function used by sg_cmds_* implementations after the + * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid + * sense data is found it is decoded and output to sg_warnings_strm (def: + * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for + * sense data (may not be fatal), -1 for failed, 0, or a positive number. If + * 'mx_di_len > 0' then asks pass-through for resid and returns + * (mx_di_len - resid); otherwise returns 0. So for data-in it should return + * the actual number of bytes received. For data-out (to device) or no data + * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category + * output via 'o_sense_cat' pointer (if not NULL). Note that several sense + * categories also have data in bytes received; -2 is still returned. */ +int sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin, + int pt_res, int mx_di_len, + const unsigned char * sense_b, bool noisy, + int verbose, int * o_sense_cat); + +/* NVMe devices use a different command set. This function will return true + * if the device associated with 'pvtp' is a NVME device, else it will + * return false (e.g. for SCSI devices). */ +bool sg_cmds_is_nvme(const struct sg_pt_base * ptvp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_cmds_extra.h b/tools/sg_write_buffer/include/sg_cmds_extra.h new file mode 100644 index 0000000..fbc2377 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_cmds_extra.h @@ -0,0 +1,369 @@ +#ifndef SG_CMDS_EXTRA_H +#define SG_CMDS_EXTRA_H + +/* + * Copyright (c) 2004-2018 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. + */ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Note: all functions that have an 'int timeout_secs' argument will use + * that value if it is > 0. Otherwise they will set an internal default + * which is currently 60 seconds. This timeout is typically applied in the + * SCSI stack above the initiator. If it goes off then the SCSI command is + * aborted and there can be other unwelcome side effects. Note that some + * commands (e.g. FORMAT UNIT and the Third Party copy commands) can take + * a lot longer than the default timeout. */ + + +/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is + * selected by the cdb_len argument that can take values of 12, 16 or 32 + * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9 + * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from + * the control byte, the rest is copied into an internal cdb which is then + * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15 + * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the + * timeout is set to 60 seconds. For data in or out transfers set dinp or + * doutp, and dlen to the number of bytes to transfer. If dlen is zero then + * no data transfer is assumed. If sense buffer obtained then it is written + * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is + * obtained then written to ata_return_dp, else ata_return_dp[0] is set to + * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers. + * Returns SCSI status value (>= 0) or -1 if other error. Users are + * expected to check the sense buffer themselves. If available the data in + * resid is written to residp. Note in SAT-2 and later, fixed format sense + * data may be placed in *sensep in which case sensep[0]==0x70, prior to + * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72). + */ +int sg_ll_ata_pt(int sg_fd, const unsigned char * cdbp, int cdb_len, + int timeout_secs, void * dinp, void * doutp, int dlen, + unsigned char * sensep, int max_sense_len, + unsigned char * ata_return_dp, int max_ata_return_len, + int * residp, int verbose); + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Format unit not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Note that sg_ll_format_unit2() and + * sg_ll_format_unit_v2() are the same, both add the ffmt argument. */ +int sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose); +int sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose); +int sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI GET LBA STATUS(16) or GET LBA STATUS(32) command (SBC). + * Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> GET LBA STATUS(16 or 32) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure. + * sg_ll_get_lba_status() calls the 16 byte variant with rt=0 . */ +int sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp, + int alloc_len, bool noisy, int verbose); +int sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose); +int sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len, + uint32_t element_id, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose); + +/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0 + * when successful, SG_LIB_CAT_INVALID_OP if command not supported, + * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0 + * when successful, SG_LIB_CAT_INVALID_OP if command not supported, + * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope, + unsigned int rq_type, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ BLOCK LIMITS not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */ +int sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI READ BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * resp, int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, + int dl_format, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ LONG(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose); + +/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ LONG(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose); + +/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Read media serial number not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI REASSIGN BLOCKS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */ +int sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, + void * paramp, int param_len, bool noisy, + int verbose); + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Receive diagnostic results not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Same as sg_ll_receive_diag() but with added timeout_secs and residp + * arguments. Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was + * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report identifying information not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); +int sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len, + bool extended, bool noisy, int verbose); + +/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose); + +/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Referrals not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg, + void * resp, int mx_resp_len, bool noisy, + int verbose); + +/* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can + * take a long time, if so set long_duration flag in which case the timeout + * is set to 7200 seconds; if the value of long_duration is > 7200 then that + * value is taken as the timeout value in seconds. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Send diagnostic not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit, + bool devofl_bit, bool unitofl_bit, int long_duration, + void * paramp, int param_len, bool noisy, int verbose); + +/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was + * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set identifying information not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI UNMAP (SBC-3) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose); +/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field + * (sbc3r22). Otherwise same as sg_ll_unmap() . */ +int sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose); + +/* Invokes a SCSI VERIFY (10) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Verify(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info, + * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */ +int sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytechk, + unsigned int lba, int veri_len, void * data_out, + int data_out_len, unsigned int * infop, bool noisy, + int verbose); + +/* Invokes a SCSI VERIFY (16) command (SBC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Verify(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info, + * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */ +int sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytechk, + uint64_t llba, int veri_len, int group_num, + void * data_out, int data_out_len, uint64_t * infop, + bool noisy, int verbose); + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * paramp, int param_len, bool noisy, int verbose); + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Adds mode specific field (spc4r32) and timeout + * to command abort to override default of 60 seconds. If timeout_secs is + * 0 or less then the default timeout is used instead. */ +int +sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id, + uint32_t buffer_offset, void * paramp, + uint32_t param_len, int timeout_secs, bool noisy, + int verbose); + +/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> WRITE LONG(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + unsigned int lba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose); + +/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> WRITE LONG(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + uint64_t llba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose); + +/* Invokes a SPC-3 SCSI RECEIVE COPY RESULTS command. In SPC-4 this function + * supports all service action variants of the THIRD-PARTY COPY IN opcode. + * SG_LIB_CAT_INVALID_OP -> Receive copy results not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI EXTENDED COPY(LID1) command. For EXTENDED COPY(LID4) + * including POPULATE TOKEN and WRITE USING TOKEN use + * sg_ll_3party_copy_out(). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Extended copy not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose); + +/* Handles various service actions associated with opcode 0x83 which is + * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID4), + * POPULATE TOKEN and WRITE USING TOKEN commands. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> opcode 0x83 not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, + int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC). + * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_* + * positive values or -1 -> other errors. Note that CONDITION MET status + * is returned when immed=true and num_blocks can fit in device's cache, + * somewaht strangely, GOOD status (return 0) is returned if num_blocks + * cannot fit in device's cache. If do_seek10==true then does a SEEK(10) + * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10) + * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then + * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */ +int sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed, + uint64_t lba, uint32_t num_blocks, int group_num, + int timeout_secs, bool noisy, int verbose); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_cmds_mmc.h b/tools/sg_write_buffer/include/sg_cmds_mmc.h new file mode 100644 index 0000000..3988b1d --- /dev/null +++ b/tools/sg_write_buffer/include/sg_cmds_mmc.h @@ -0,0 +1,52 @@ +#ifndef SG_CMDS_MMC_H +#define SG_CMDS_MMC_H + +/* + * Copyright (c) 2008-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. + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Invokes a SCSI GET CONFIGURATION command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_get_config(int sg_fd, int rt, int starting, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba, + int max_num_desc, int type, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI SET CD SPEED command (MMC). + * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed, + int drv_write_speed, bool noisy, int verbose); + +/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len, + bool noisy, int verbose); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_io_linux.h b/tools/sg_write_buffer/include/sg_io_linux.h new file mode 100644 index 0000000..bd44bd6 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_io_linux.h @@ -0,0 +1,185 @@ +#ifndef SG_IO_LINUX_H +#define SG_IO_LINUX_H + +/* + * Copyright (c) 2004-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. + */ + +/* + * Version 1.05 [20171009] + */ + +/* + * This header file contains linux specific information related to the SCSI + * command pass through in the SCSI generic (sg) driver and the linux + * block layer. + */ + +#include "sg_lib.h" +#include "sg_linux_inc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* The following are 'host_status' codes */ +#ifndef DID_OK +#define DID_OK 0x00 +#endif +#ifndef DID_NO_CONNECT +#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */ +#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */ +#define DID_TIME_OUT 0x03 /* Timed out for some other reason */ +#define DID_BAD_TARGET 0x04 /* Bad target (id?) */ +#define DID_ABORT 0x05 /* Told to abort for some other reason */ +#define DID_PARITY 0x06 /* Parity error (on SCSI bus) */ +#define DID_ERROR 0x07 /* Internal error */ +#define DID_RESET 0x08 /* Reset by somebody */ +#define DID_BAD_INTR 0x09 /* Received an unexpected interrupt */ +#define DID_PASSTHROUGH 0x0a /* Force command past mid-level */ +#define DID_SOFT_ERROR 0x0b /* The low-level driver wants a retry */ +#endif +#ifndef DID_IMM_RETRY +#define DID_IMM_RETRY 0x0c /* Retry without decrementing retry count */ +#endif +#ifndef DID_REQUEUE +#define DID_REQUEUE 0x0d /* Requeue command (no immediate retry) also + * without decrementing the retry count */ +#endif +#ifndef DID_TRANSPORT_DISRUPTED +#define DID_TRANSPORT_DISRUPTED 0xe +#endif +#ifndef DID_TRANSPORT_FAILFAST +#define DID_TRANSPORT_FAILFAST 0xf +#endif +#ifndef DID_TARGET_FAILURE +#define DID_TARGET_FAILURE 0x10 +#endif +#ifndef DID_NEXUS_FAILURE +#define DID_NEXUS_FAILURE 0x11 +#endif + +/* These defines are to isolate applications from kernel define changes */ +#define SG_LIB_DID_OK DID_OK +#define SG_LIB_DID_NO_CONNECT DID_NO_CONNECT +#define SG_LIB_DID_BUS_BUSY DID_BUS_BUSY +#define SG_LIB_DID_TIME_OUT DID_TIME_OUT +#define SG_LIB_DID_BAD_TARGET DID_BAD_TARGET +#define SG_LIB_DID_ABORT DID_ABORT +#define SG_LIB_DID_PARITY DID_PARITY +#define SG_LIB_DID_ERROR DID_ERROR +#define SG_LIB_DID_RESET DID_RESET +#define SG_LIB_DID_BAD_INTR DID_BAD_INTR +#define SG_LIB_DID_PASSTHROUGH DID_PASSTHROUGH +#define SG_LIB_DID_SOFT_ERROR DID_SOFT_ERROR +#define SG_LIB_DID_IMM_RETRY DID_IMM_RETRY +#define SG_LIB_DID_REQUEUE DID_REQUEUE +#define SG_LIB_TRANSPORT_DISRUPTED DID_TRANSPORT_DISRUPTED +#define SG_LIB_DID_TRANSPORT_FAILFAST DID_TRANSPORT_FAILFAST +#define SG_LIB_DID_TARGET_FAILURE DID_TARGET_FAILURE +#define SG_LIB_DID_NEXUS_FAILURE DID_NEXUS_FAILURE + +/* The following are 'driver_status' codes */ +#ifndef DRIVER_OK +#define DRIVER_OK 0x00 +#endif +#ifndef DRIVER_BUSY +#define DRIVER_BUSY 0x01 +#define DRIVER_SOFT 0x02 +#define DRIVER_MEDIA 0x03 +#define DRIVER_ERROR 0x04 +#define DRIVER_INVALID 0x05 +#define DRIVER_TIMEOUT 0x06 +#define DRIVER_HARD 0x07 +#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */ + +/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept + * to stop compilation breakages. + * Following "suggests" are "or-ed" with one of previous 8 entries */ +#define SUGGEST_RETRY 0x10 +#define SUGGEST_ABORT 0x20 +#define SUGGEST_REMAP 0x30 +#define SUGGEST_DIE 0x40 +#define SUGGEST_SENSE 0x80 +#define SUGGEST_IS_OK 0xff +#endif + +#ifndef DRIVER_MASK +#define DRIVER_MASK 0x0f +#endif +#ifndef SUGGEST_MASK +#define SUGGEST_MASK 0xf0 +#endif + +/* These defines are to isolate applications from kernel define changes */ +#define SG_LIB_DRIVER_OK DRIVER_OK +#define SG_LIB_DRIVER_BUSY DRIVER_BUSY +#define SG_LIB_DRIVER_SOFT DRIVER_SOFT +#define SG_LIB_DRIVER_MEDIA DRIVER_MEDIA +#define SG_LIB_DRIVER_ERROR DRIVER_ERROR +#define SG_LIB_DRIVER_INVALID DRIVER_INVALID +#define SG_LIB_DRIVER_TIMEOUT DRIVER_TIMEOUT +#define SG_LIB_DRIVER_HARD DRIVER_HARD +#define SG_LIB_DRIVER_SENSE DRIVER_SENSE + + +/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept + * to stop compilation breakages. */ +#define SG_LIB_SUGGEST_RETRY SUGGEST_RETRY +#define SG_LIB_SUGGEST_ABORT SUGGEST_ABORT +#define SG_LIB_SUGGEST_REMAP SUGGEST_REMAP +#define SG_LIB_SUGGEST_DIE SUGGEST_DIE +#define SG_LIB_SUGGEST_SENSE SUGGEST_SENSE +#define SG_LIB_SUGGEST_IS_OK SUGGEST_IS_OK +#define SG_LIB_DRIVER_MASK DRIVER_MASK +#define SG_LIB_SUGGEST_MASK SUGGEST_MASK + +void sg_print_masked_status(int masked_status); +void sg_print_host_status(int host_status); +void sg_print_driver_status(int driver_status); + +/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings + else it prints errors/warnings (prefixed by 'leadin') to + 'sg_warnings_fd' and returns 0. raw_sinfo indicates whether the + raw sense buffer (in ASCII hex) should be printed. */ +int sg_chk_n_print(const char * leadin, int masked_status, int host_status, + int driver_status, const unsigned char * sense_buffer, + int sb_len, bool raw_sinfo); + +/* The following function declaration is for the sg version 3 driver. */ +struct sg_io_hdr; +/* sg_chk_n_print3() returns 1 quietly if there are no errors/warnings; + else it prints errors/warnings (prefixed by 'leadin') to + 'sg_warnings_fd' and returns 0. */ +int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp, + bool raw_sinfo); + +/* Calls sg_scsi_normalize_sense() after obtaining the sense buffer and + its length from the struct sg_io_hdr pointer. If these cannot be + obtained, false is returned. */ +bool sg_normalize_sense(const struct sg_io_hdr * hp, + struct sg_scsi_sense_hdr * sshp); + +int sg_err_category(int masked_status, int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len); + +int sg_err_category_new(int scsi_status, int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len); + +/* The following function declaration is for the sg version 3 driver. */ +int sg_err_category3(struct sg_io_hdr * hp); + + +/* Note about SCSI status codes found in older versions of Linux. + Linux has traditionally used a 1 bit right shifted and masked + version of SCSI standard status codes. Now CHECK_CONDITION + and friends (in <scsi/scsi.h>) are deprecated. */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_lib.h b/tools/sg_write_buffer/include/sg_lib.h new file mode 100644 index 0000000..bc93d49 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_lib.h @@ -0,0 +1,602 @@ +#ifndef SG_LIB_H +#define SG_LIB_H + +/* + * Copyright (c) 2004-2018 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. + */ + +/* + * + * On 5th October 2004 a FreeBSD license was added to this file. + * The intention is to keep this file and the related sg_lib.c file + * as open source and encourage their unencumbered use. + * + * Current version number is in the sg_lib.c file and can be accessed + * with the sg_lib_version() function. + */ + + +/* + * This header file contains defines and function declarations that may + * be useful to applications that communicate with devices that use a + * SCSI command set. These command sets have names like SPC-4, SBC-3, + * SSC-3, SES-2 and draft standards defining them can be found at + * http://www.t10.org . Virtually all devices in the Linux SCSI subsystem + * utilize SCSI command sets. Many devices in other Linux device subsystems + * utilize SCSI command sets either natively or via emulation (e.g. a + * parallel ATA disk in a USB enclosure). + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* SCSI Peripheral Device Types (PDT) [5 bit field] */ +#define PDT_DISK 0x0 /* direct access block device (disk) */ +#define PDT_TAPE 0x1 /* sequential access device (magnetic tape) */ +#define PDT_PRINTER 0x2 /* printer device (see SSC-1) */ +#define PDT_PROCESSOR 0x3 /* processor device (e.g. SAFTE device) */ +#define PDT_WO 0x4 /* write once device (some optical disks) */ +#define PDT_MMC 0x5 /* CD/DVD/BD (multi-media) */ +#define PDT_SCANNER 0x6 /* obsolete */ +#define PDT_OPTICAL 0x7 /* optical memory device (some optical disks) */ +#define PDT_MCHANGER 0x8 /* media changer device (e.g. tape robot) */ +#define PDT_COMMS 0x9 /* communications device (obsolete) */ +#define PDT_SAC 0xc /* storage array controller device */ +#define PDT_SES 0xd /* SCSI Enclosure Services (SES) device */ +#define PDT_RBC 0xe /* Reduced Block Commands (simplified PDT_DISK) */ +#define PDT_OCRW 0xf /* optical card read/write device */ +#define PDT_BCC 0x10 /* bridge controller commands */ +#define PDT_OSD 0x11 /* Object Storage Device (OSD) */ +#define PDT_ADC 0x12 /* Automation/drive commands (ADC) */ +#define PDT_SMD 0x13 /* Security Manager Device (SMD) */ +#define PDT_ZBC 0x14 /* Zoned Block Commands (ZBC) */ +#define PDT_WLUN 0x1e /* Well known logical unit (WLUN) */ +#define PDT_UNKNOWN 0x1f /* Unknown or no device type */ + +#ifndef SAM_STAT_GOOD +/* The SCSI status codes as found in SAM-4 at www.t10.org */ +#define SAM_STAT_GOOD 0x0 +#define SAM_STAT_CHECK_CONDITION 0x2 +#define SAM_STAT_CONDITION_MET 0x4 +#define SAM_STAT_BUSY 0x8 +#define SAM_STAT_INTERMEDIATE 0x10 /* obsolete in SAM-4 */ +#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14 /* obsolete in SAM-4 */ +#define SAM_STAT_RESERVATION_CONFLICT 0x18 +#define SAM_STAT_COMMAND_TERMINATED 0x22 /* obsolete in SAM-3 */ +#define SAM_STAT_TASK_SET_FULL 0x28 +#define SAM_STAT_ACA_ACTIVE 0x30 +#define SAM_STAT_TASK_ABORTED 0x40 +#endif + +/* The SCSI sense key codes as found in SPC-4 at www.t10.org */ +#define SPC_SK_NO_SENSE 0x0 +#define SPC_SK_RECOVERED_ERROR 0x1 +#define SPC_SK_NOT_READY 0x2 +#define SPC_SK_MEDIUM_ERROR 0x3 +#define SPC_SK_HARDWARE_ERROR 0x4 +#define SPC_SK_ILLEGAL_REQUEST 0x5 +#define SPC_SK_UNIT_ATTENTION 0x6 +#define SPC_SK_DATA_PROTECT 0x7 +#define SPC_SK_BLANK_CHECK 0x8 +#define SPC_SK_VENDOR_SPECIFIC 0x9 +#define SPC_SK_COPY_ABORTED 0xa +#define SPC_SK_ABORTED_COMMAND 0xb +#define SPC_SK_RESERVED 0xc +#define SPC_SK_VOLUME_OVERFLOW 0xd +#define SPC_SK_MISCOMPARE 0xe +#define SPC_SK_COMPLETED 0xf + +/* Transport protocol identifiers or just Protocol identifiers */ +#define TPROTO_FCP 0 +#define TPROTO_SPI 1 +#define TPROTO_SSA 2 +#define TPROTO_1394 3 +#define TPROTO_SRP 4 /* SCSI over RDMA */ +#define TPROTO_ISCSI 5 +#define TPROTO_SAS 6 +#define TPROTO_ADT 7 +#define TPROTO_ATA 8 +#define TPROTO_UAS 9 /* USB attached SCSI */ +#define TPROTO_SOP 0xa /* SCSI over PCIe */ +#define TPROTO_PCIE 0xb /* includes NVMe */ +#define TPROTO_NONE 0xf + +/* SCSI Feature Sets (sfs) */ +#define SCSI_FS_SPC_DISCOVERY_2016 0x1 +#define SCSI_FS_SBC_BASE_2010 0x102 +#define SCSI_FS_SBC_BASE_2016 0x101 +#define SCSI_FS_SBC_BASIC_PROV_2016 0x103 +#define SCSI_FS_SBC_DRIVE_MAINT_2016 0x104 + +/* Often SCSI responses use the highest integer that can fit in a field + * to indicate "unbounded" or limit does not apply. Sometimes represented + * in output as "-1" for brevity */ +#define SG_LIB_UNBOUNDED_16BIT 0xffff +#define SG_LIB_UNBOUNDED_32BIT 0xffffffffU +#define SG_LIB_UNBOUNDED_64BIT 0xffffffffffffffffULL + +#if (__STDC_VERSION__ >= 199901L) /* C99 or later */ + typedef uintptr_t sg_uintptr_t; +#else + typedef unsigned long sg_uintptr_t; +#endif + + +/* The format of the version string is like this: "2.26 20170906" */ +const char * sg_lib_version(); + +/* Returns length of SCSI command given the opcode (first byte). + * Yields the wrong answer for variable length commands (opcode=0x7f) + * and potentially some vendor specific commands. */ +int sg_get_command_size(unsigned char cdb_byte0); + +/* Command name given pointer to the cdb. Certain command names + * depend on peripheral type (give 0 or -1 if unknown). Places command + * name into buff and will write no more than buff_len bytes. */ +void sg_get_command_name(const unsigned char * cdbp, int peri_type, + int buff_len, char * buff); + +/* Command name given only the first byte (byte 0) of a cdb and + * peripheral type (give 0 or -1 if unknown). */ +void sg_get_opcode_name(unsigned char cdb_byte0, int peri_type, int buff_len, + char * buff); + +/* Command name given opcode (byte 0), service action and peripheral type. + * If no service action give 0, if unknown peripheral type give 0 or -1 . */ +void sg_get_opcode_sa_name(unsigned char cdb_byte0, int service_action, + int peri_type, int buff_len, char * buff); + +/* Fetch scsi status string. */ +void sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff); + +/* This is a slightly stretched SCSI sense "descriptor" format header. + * The addition is to allow the 0x70 and 0x71 response codes. The idea + * is to place the salient data of both "fixed" and "descriptor" sense + * format into one structure to ease application processing. + * The original sense buffer should be kept around for those cases + * in which more information is required (e.g. the LBA of a MEDIUM ERROR). */ +struct sg_scsi_sense_hdr { + unsigned char response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */ + unsigned char sense_key; + unsigned char asc; + unsigned char ascq; + unsigned char byte4; + unsigned char byte5; + unsigned char byte6; + unsigned char additional_length; +}; + +/* Maps the salient data from a sense buffer which is in either fixed or + * descriptor format into a structure mimicking a descriptor format + * header (i.e. the first 8 bytes of sense descriptor format). + * If zero response code returns false. Otherwise returns true and if 'sshp' + * is non-NULL then zero all fields and then set the appropriate fields in + * that structure. sshp::additional_length is always 0 for response + * codes 0x70 and 0x71 (fixed format). */ +bool sg_scsi_normalize_sense(const unsigned char * sensep, int sense_len, + struct sg_scsi_sense_hdr * sshp); + +/* Attempt to find the first SCSI sense data descriptor that matches the + * given 'desc_type'. If found return pointer to start of sense data + * descriptor; otherwise (including fixed format sense data) returns NULL. */ +const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep, + int sense_len, int desc_type); + +/* Get sense key from sense buffer. If successful returns a sense key value + * between 0 and 15. If sense buffer cannot be decode, returns -1 . */ +int sg_get_sense_key(const unsigned char * sensep, int sense_len); + +/* Yield string associated with sense_key value. Returns 'buff'. */ +char * sg_get_sense_key_str(int sense_key, int buff_len, char * buff); + +/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */ +char * sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff); + +/* Returns true if valid bit set, false if valid bit clear. Irrespective the + * information field is written out via 'info_outp' (except when it is + * NULL). Handles both fixed and descriptor sense formats. */ +bool sg_get_sense_info_fld(const unsigned char * sensep, int sb_len, + uint64_t * info_outp); + +/* Returns true if fixed format or command specific information descriptor + * is found in the descriptor sense; else false. If available the command + * specific information field (4 byte integer in fixed format, 8 byte + * integer in descriptor format) is written out via 'cmd_spec_outp'. + * Handles both fixed and descriptor sense formats. */ +bool sg_get_sense_cmd_spec_fld(const unsigned char * sensep, int sb_len, + uint64_t * cmd_spec_outp); + +/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set. + * In descriptor format if the stream commands descriptor not found + * then returns false. Writes true or false corresponding to these bits to + * the last three arguments if they are non-NULL. */ +bool sg_get_sense_filemark_eom_ili(const unsigned char * sensep, int sb_len, + bool * filemark_p, bool * eom_p, + bool * ili_p); + +/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also + * returns true if progress indication sense data descriptor found. Places + * progress field from sense data where progress_outp points. If progress + * field is not available returns false. Handles both fixed and descriptor + * sense formats. N.B. App should multiply by 100 and divide by 65536 + * to get percentage completion from given value. */ +bool sg_get_sense_progress_fld(const unsigned char * sensep, int sb_len, + int * progress_outp); + +/* Closely related to sg_print_sense(). Puts decoded sense data in 'buff'. + * Usually multiline with multiple '\n' including one trailing. If + * 'raw_sinfo' set appends sense buffer in hex. 'leadin' is string prepended + * to each line written to 'buff', NULL treated as "". Returns the number of + * bytes written to 'buff' excluding the trailing '\0'. + * N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the first + * line output. Also this function returned type void. */ +int sg_get_sense_str(const char * leadin, const unsigned char * sense_buffer, + int sb_len, bool raw_sinfo, int buff_len, char * buff); + +/* Decode descriptor format sense descriptors (assumes sense buffer is + * in descriptor format). 'leadin' is string prepended to each line written + * to 'b', NULL treated as "". Returns the number of bytes written to 'b' + * excluding the trailing '\0'. */ +int sg_get_sense_descriptors_str(const char * leadin, + const unsigned char * sense_buffer, + int sb_len, int blen, char * b); + +/* Decodes a designation descriptor (e.g. as found in the Device + * Identification VPD page (0x83)) into string 'b' whose maximum length is + * blen. 'leadin' is string prepended to each line written to 'b', NULL + * treated as "". Returns the number of bytes written to 'b' excluding the + * trailing '\0'. */ +int sg_get_designation_descriptor_str(const char * leadin, + const unsigned char * ddp, int dd_len, + bool print_assoc, bool do_long, + int blen, char * b); + +/* Yield string associated with peripheral device type (pdt). Returns + * 'buff'. If 'pdt' out of range yields "bad pdt" string. */ +char * sg_get_pdt_str(int pdt, int buff_len, char * buff); + +/* Some lesser used PDTs share a lot in common with a more used PDT. + * Examples are PDT_ADC decaying to PDT_TAPE and PDT_ZBC to PDT_DISK. + * If such a lesser used 'pdt' is given to this function, then it will + * return the more used PDT (i.e. "decays to"); otherwise 'pdt' is returned. + * Valid for 'pdt' 0 to 31, for other values returns 0. */ +int sg_lib_pdt_decay(int pdt); + +/* Yield string associated with transport protocol identifier (tpi). Returns + * 'buff'. If 'tpi' out of range yields "bad tpi" string. */ +char * sg_get_trans_proto_str(int tpi, int buff_len, char * buff); + +/* Decode TransportID pointed to by 'bp' of length 'bplen'. Place decoded + * string output in 'buff' which is also the return value. Each new line + * is prefixed by 'leadin'. If leadin NULL treat as "". */ +char * sg_decode_transportid_str(const char * leadin, unsigned char * bp, + int bplen, bool only_one, int buff_len, + char * buff); + +/* Returns a designator's type string given 'val' (0 to 15 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_type_str(int val); + +/* Returns a designator's code_set string given 'val' (0 to 15 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_code_set_str(int val); + +/* Returns a designator's association string given 'val' (0 to 3 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_assoc_str(int val); + +/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31) + * returns pointer to string (same as 'buff') associated with 'sfs_code'. + * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match + * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL + * then where it points is set to true if a match is found else it is set to + * false. If 'buff' is not NULL then in the case of a match a descriptive + * string is written to 'buff' while if there is not a not then a string + * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC + * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL. + * Example: + * char b[64]; + * ... + * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0)); + */ +const char * sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, + char * buff, bool * foundp, int verbose); + +/* This is a heuristic that takes into account the command bytes and length + * to decide whether the presented unstructured sequence of bytes could be + * a SCSI command. If so it returns true otherwise false. Vendor specific + * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed + * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The + * only SCSI commands considered above 16 bytes of length are the Variable + * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e). + * Both have an inbuilt length field which can be cross checked with clen. + * No NVMe commands (64 bytes long plus some extra added by some OSes) have + * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS + * structures that are sent across the wire. The 'FIS register' structure is + * used to move a command from a SATA host to device, but the ATA 'command' + * is not the first byte. So it is harder to say what will happen if a + * FIS structure is presented as a SCSI command, hopfully there is a low + * probability this function will yield true in that case. */ +bool sg_is_scsi_cdb(const uint8_t * cdbp, int clen); + +/* Yield string associated with NVMe command status value in sct_sc. It + * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25 + * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC). + * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found + * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated. + * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/ +char * sg_get_nvme_cmd_status_str(uint16_t sct_sc, int buff_len, char * buff); + +/* Attempts to map NVMe status value ((SCT << 8) | SC) n sct_sc to a SCSI + * status, sense_key, asc and ascq tuple. If successful returns true and + * writes to non-NULL pointer arguments; otherwise returns false. */ +bool sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p, + uint8_t * asc_p, uint8_t * ascq_p); + +extern FILE * sg_warnings_strm; + +void sg_set_warnings_strm(FILE * warnings_strm); + +/* The following "print" functions send ACSII to 'sg_warnings_strm' file + * descriptor (default value is stderr). 'leadin' is string prepended to + * each line printed out, NULL treated as "". */ +void sg_print_command(const unsigned char * command); +void sg_print_scsi_status(int scsi_status); + +/* 'leadin' is string prepended to each line printed out, NULL treated as + * "". N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the + * first line printed. */ +void sg_print_sense(const char * leadin, const unsigned char * sense_buffer, + int sb_len, bool raw_info); + +/* Following examines exit_status and outputs a clear error message to + * warnings_strm (usually stderr) if one is known and returns true. + * Otherwise it doesn't print anything and returns false. Note that if + * exit_status==0 then returns true but prints nothing and if + * exit_status<0 ("some error occurred") false is returned. If leadin is + * non-NULL is will be printed before error message. */ +bool sg_if_can2stderr(const char * leadin, int exit_status); + +/* Utilities can use these exit status values for syntax errors and + * file (device node) problems (e.g. not found or permissions). */ +#define SG_LIB_SYNTAX_ERROR 1 /* command line syntax problem */ +#define SG_LIB_FILE_ERROR 15 /* device or other file problem */ + +/* The sg_err_category_sense() function returns one of the following. + * These may be used as exit status values (from a process). Notice that + * some of the lower values correspond to SCSI sense key values. */ +#define SG_LIB_CAT_CLEAN 0 /* No errors or other information */ +/* Value 1 left unused for utilities to use SG_LIB_SYNTAX_ERROR */ +#define SG_LIB_CAT_NOT_READY 2 /* sense key, unit stopped? */ + /* [sk,asc,ascq: 0x2,*,*] */ +#define SG_LIB_CAT_MEDIUM_HARD 3 /* medium or hardware error, blank check */ + /* [sk,asc,ascq: 0x3/0x4/0x8,*,*] */ +#define SG_LIB_CAT_ILLEGAL_REQ 5 /* Illegal request (other than invalid */ + /* opcode): [sk,asc,ascq: 0x5,*,*] */ +#define SG_LIB_CAT_UNIT_ATTENTION 6 /* sense key, device state changed */ + /* [sk,asc,ascq: 0x6,*,*] */ + /* was SG_LIB_CAT_MEDIA_CHANGED earlier [sk,asc,ascq: 0x6,0x28,*] */ +#define SG_LIB_CAT_DATA_PROTECT 7 /* sense key, media write protected? */ + /* [sk,asc,ascq: 0x7,*,*] */ +#define SG_LIB_CAT_INVALID_OP 9 /* (Illegal request,) Invalid opcode: */ + /* [sk,asc,ascq: 0x5,0x20,0x0] */ +#define SG_LIB_CAT_COPY_ABORTED 10 /* sense key, some data transferred */ + /* [sk,asc,ascq: 0xa,*,*] */ +#define SG_LIB_CAT_ABORTED_COMMAND 11 /* interpreted from sense buffer */ + /* [sk,asc,ascq: 0xb,! 0x10,*] */ +#define SG_LIB_CAT_MISCOMPARE 14 /* sense key, probably verify */ + /* [sk,asc,ascq: 0xe,*,*] */ +#define SG_LIB_CAT_NO_SENSE 20 /* sense data with key of "no sense" */ + /* [sk,asc,ascq: 0x0,*,*] */ +#define SG_LIB_CAT_RECOVERED 21 /* Successful command after recovered err */ + /* [sk,asc,ascq: 0x1,*,*] */ +#define SG_LIB_CAT_RES_CONFLICT SAM_STAT_RESERVATION_CONFLICT + /* 24: this is a SCSI status, not sense. */ + /* It indicates reservation by another */ + /* machine blocks this command */ +#define SG_LIB_CAT_CONDITION_MET 25 /* SCSI status, not sense key. */ + /* Only from PRE-FETCH (SBC-4) */ +#define SG_LIB_CAT_BUSY 26 /* SCSI status, not sense. Invites retry */ +#define SG_LIB_CAT_TS_FULL 27 /* SCSI status, not sense. Wait then retry */ +#define SG_LIB_CAT_ACA_ACTIVE 28 /* SCSI status; ACA seldom used */ +#define SG_LIB_CAT_TASK_ABORTED 29 /* SCSI status, this command aborted by? */ +#define SG_LIB_CAT_PROTECTION 40 /* subset of aborted command (for PI, DIF) */ + /* [sk,asc,ascq: 0xb,0x10,*] */ +#define SG_LIB_NVME_STATUS 48 /* NVMe Status Field (SF) other than 0 */ +#define SG_LIB_WILD_RESID 49 /* Residual value for data-in transfer of a */ + /* SCSI command is nonsensical */ +#define SG_LIB_OS_BASE_ERR 50 /* in Linux: values found in: */ + /* include/uapi/asm-generic/errno-base.h */ + /* Example: ENOMEM reported as 62 (=50+12) */ +#define SG_LIB_CAT_MALFORMED 97 /* Response to SCSI command malformed */ +#define SG_LIB_CAT_SENSE 98 /* Something else is in the sense buffer */ +#define SG_LIB_CAT_OTHER 99 /* Some other error/warning has occurred */ + /* (e.g. a transport or driver error) */ + +/* Returns a SG_LIB_CAT_* value. If cannot decode sense_buffer or a less + * common sense key then return SG_LIB_CAT_SENSE .*/ +int sg_err_category_sense(const unsigned char * sense_buffer, int sb_len); + +/* Here are some additional sense data categories that are not returned + * by sg_err_category_sense() but are returned by some related functions. */ +#define SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO 17 /* Illegal request (other than */ + /* invalid opcode) plus 'info' field: */ + /* [sk,asc,ascq: 0x5,*,*] */ +#define SG_LIB_CAT_MEDIUM_HARD_WITH_INFO 18 /* medium or hardware error */ + /* sense key plus 'info' field: */ + /* [sk,asc,ascq: 0x3/0x4,*,*] */ +#define SG_LIB_CAT_PROTECTION_WITH_INFO 41 /* aborted command sense key, */ + /* protection plus 'info' field: */ + /* [sk,asc,ascq: 0xb,0x10,*] */ +#define SG_LIB_CAT_TIMEOUT 33 + +/* Yield string associated with sense category. Returns 'buff' (or pointer + * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then + * yield "Sense category: <sense_cat>" string. */ +const char * sg_get_category_sense_str(int sense_cat, int buff_len, + char * buff, int verbose); + + +/* Iterates to next designation descriptor in the device identification + * VPD page. The 'initial_desig_desc' should point to start of first + * descriptor with 'page_len' being the number of valid bytes in that + * and following descriptors. To start, 'off' should point to a negative + * value, thereafter it should point to the value yielded by the previous + * call. If 0 returned then 'initial_desig_desc + *off' should be a valid + * descriptor; returns -1 if normal end condition and -2 for an abnormal + * termination. Matches association, designator_type and/or code_set when + * any of those values are greater than or equal to zero. */ +int sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len, + int * off, int m_assoc, int m_desig_type, + int m_code_set); + + +/* <<< General purpose (i.e. not SCSI specific) utility functions >>> */ + +/* Always returns valid string even if errnum is wild (or library problem). + * If errnum is negative, flip its sign. */ +char * safe_strerror(int errnum); + + +/* Print (to stdout) 'str' of bytes in hex, 16 bytes per line optionally + * followed at the right hand side of the line with an ASCII interpretation. + * Each line is prefixed with an address, starting at 0 for str[0]..str[15]. + * All output numbers are in hex. 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 16 ASCII-hex bytes + * = 0 in addition, the bytes are listed in ASCII to the right + * < 0 only the ASCII-hex bytes are listed (i.e. without address) +*/ +void dStrHex(const char * str, int len, int no_ascii); + +/* Print (to sg_warnings_strm (stderr)) 'str' of bytes in hex, 16 bytes per + * line optionally followed at right by its ASCII interpretation. Same + * logic as dStrHex() with different output stream (i.e. stderr). */ +void dStrHexErr(const char * str, int len, int no_ascii); + +/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space + * separated) to 'b' not to exceed 'b_len' characters. Each line + * starts with 'leadin' (NULL for no leadin) and there are 16 bytes + * per line with an extra space between the 8th and 9th bytes. 'format' + * is 0 for repeat in printable ASCII ('.' for non printable chars) to + * right of each line; 1 don't (so just output ASCII hex). Returns + * number of bytes written to 'b' excluding the trailing '\0'. */ +int dStrHexStr(const char * str, int len, const char * leadin, int format, + int cb_len, char * cbp); + +/* The following 3 functions are equivalent to dStrHex(), dStrHexErr() and + * dStrHexStr() respectively. The difference is the type of the first of + * argument: uint8_t instead of char. The name of the argument is changed + * to b_str to stress it is a pointer to the start of a binary string. */ +void hex2stdout(const uint8_t * b_str, int len, int no_ascii); +void hex2stderr(const uint8_t * b_str, int len, int no_ascii); +int hex2str(const uint8_t * b_str, int len, const char * leadin, int format, + int cb_len, char * cbp); + +/* Returns true when executed on big endian machine; else returns false. + * Useful for displaying ATA identify words (which need swapping on a + * big endian machine). */ +bool sg_is_big_endian(); + +/* Returns true if byte sequence starting at bp with a length of b_len is + * all zeros (for sg_all_zeros()) or all 0xff_s (for sg_all_ffs()); + * otherwise returns false. If bp is NULL ir b_len <= 0 returns false. */ +bool sg_all_zeros(const uint8_t * bp, int b_len); +bool sg_all_ffs(const uint8_t * bp, int b_len); + +/* Extract character sequence from ATA words as in the model string + * in a IDENTIFY DEVICE response. Returns number of characters + * written to 'ochars' before 0 character is found or 'num' words + * are processed. */ +int sg_ata_get_chars(const uint16_t * word_arr, int start_word, + int num_words, bool is_big_endian, char * ochars); + +/* Print (to stdout) 16 bit 'words' in hex, 8 words per line optionally + * followed at the right hand side of the line with an ASCII interpretation + * (pairs of ASCII characters in big endian order (upper first)). + * Each line is prefixed with an address, starting at 0. + * All output numbers are in hex. 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 8 ASCII-hex words + * = 0 in addition, the words are listed in ASCII pairs to the right + * = -1 only the ASCII-hex words are listed (i.e. without address) + * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin" + * < -2 same as -1 + * If 'swapb' is true then bytes in each word swapped. Needs to be set + * for ATA IDENTIFY DEVICE response on big-endian machines. +*/ +void dWordHex(const uint16_t * words, int num, int no_ascii, bool swapb); + +/* If the number in 'buf' can not be decoded or the multiplier is unknown + * then -1 is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H') + * suffix. Otherwise a decimal multiplier suffix may be given. Recognised + * multipliers: c C *1; w W *2; b B *512; k K KiB *1,024; + * KB *1,000; m M MiB *1,048,576; MB *1,000,000; g G GiB *1,073,741,824; + * GB *1,000,000,000 and <n>x<m> which multiplies <n> by <m> . Ignore leading + * spaces and tabs; accept comma, hyphen, space, tab and hash as terminator. + */ +int sg_get_num(const char * buf); + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int sg_get_num_nomult(const char * buf); + +/* If the number in 'buf' can not be decoded or the multiplier is unknown + * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H') + * suffix. Otherwise a decimal multiplier suffix may be given. In addition + * to supporting the multipliers of sg_get_num(), this function supports: + * t T TiB *(2**40); TB *(10**12); p P PiB *(2**50); PB *(10**15) . + * Ignore leading spaces and tabs; accept comma, hyphen, space, tab and hash + * as terminator. */ +int64_t sg_get_llnum(const char * buf); + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int64_t sg_get_llnum_nomult(const char * buf); + +/* Returns pointer to heap (or NULL) that is aligned to a align_to byte + * boundary. Sends back *buff_to_free pointer in third argument that may be + * different from the return value. If it is different then the *buff_to_free + * pointer should be freed (rather than the returned value) when the heap is + * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all + * returned heap to zeros. If num_bytes is 0 then set to page size. */ +uint8_t * sg_memalign(uint32_t num_bytes, uint32_t align_to, + uint8_t ** buff_to_free, bool vb); + +/* Returns OS page size in bytes. If uncertain returns 4096. */ +uint32_t sg_get_page_size(void); + +/* If os_err_num is within bounds then the returned value is 'os_err_num + + * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0 + * is returned. */ +int sg_convert_errno(int os_err_num); + + +/* <<< Architectural support functions [is there a better place?] >>> */ + +/* Non Unix OSes distinguish between text and binary files. + * Set text mode on fd. Does nothing in Unix. Returns negative number on + * failure. */ +int sg_set_text_mode(int fd); + +/* Set binary mode on fd. Does nothing in Unix. Returns negative number on + * failure. */ +int sg_set_binary_mode(int fd); + +#ifdef __cplusplus +} +#endif + +#endif /* SG_LIB_H */ diff --git a/tools/sg_write_buffer/include/sg_lib_data.h b/tools/sg_write_buffer/include/sg_lib_data.h new file mode 100644 index 0000000..09cd53c --- /dev/null +++ b/tools/sg_write_buffer/include/sg_lib_data.h @@ -0,0 +1,121 @@ +#ifndef SG_LIB_DATA_H +#define SG_LIB_DATA_H + +/* + * Copyright (c) 2007-2018 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. + */ + +/* + * This header file contains some structure declarations and array name + * declarations which are defined in the sg_lib_data.c . + * Typically this header does not need to be exposed to users of the + * sg_lib interface declared in sg_libs.h . + */ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Operation codes with associated service actions that change or qualify + * the command name */ +#define SG_EXTENDED_COPY 0x83 /* since spc4r34 became next entry */ +#define SG_3PARTY_COPY_OUT 0x83 /* new in spc4r34: Third party copy out */ +#define SG_RECEIVE_COPY 0x84 /* since spc4r34 became next entry */ +#define SG_3PARTY_COPY_IN 0x84 /* new in spc4r34: Third party copy in */ +#define SG_MAINTENANCE_IN 0xa3 +#define SG_MAINTENANCE_OUT 0xa4 +#define SG_PERSISTENT_RESERVE_IN 0x5e +#define SG_PERSISTENT_RESERVE_OUT 0x5f +#define SG_READ_ATTRIBUTE 0x8c +#define SG_READ_BUFFER 0x3c /* now READ BUFFER(10) */ +#define SG_READ_BUFFER_16 0x9b +#define SG_READ_POSITION 0x34 /* SSC command with service actions */ +#define SG_SANITIZE 0x48 +#define SG_SERVICE_ACTION_BIDI 0x9d +#define SG_SERVICE_ACTION_IN_12 0xab +#define SG_SERVICE_ACTION_IN_16 0x9e +#define SG_SERVICE_ACTION_OUT_12 0xa9 +#define SG_SERVICE_ACTION_OUT_16 0x9f +#define SG_VARIABLE_LENGTH_CMD 0x7f +#define SG_WRITE_BUFFER 0x3b +#define SG_ZONING_OUT 0x94 +#define SG_ZONING_IN 0x95 + + + +struct sg_lib_simple_value_name_t { + int value; + const char * name; +}; + +struct sg_lib_value_name_t { + int value; + int peri_dev_type; /* 0 -> SPC and/or PDT_DISK, >0 -> PDT */ + const char * name; +}; + +struct sg_lib_asc_ascq_t { + unsigned char asc; /* additional sense code */ + unsigned char ascq; /* additional sense code qualifier */ + const char * text; +}; + +struct sg_lib_asc_ascq_range_t { + unsigned char asc; /* additional sense code (ASC) */ + unsigned char ascq_min; /* ASCQ minimum in range */ + unsigned char ascq_max; /* ASCQ maximum in range */ + const char * text; +}; + +/* First use: SCSI status, sense_key, asc, ascq tuple */ +struct sg_lib_4tuple_u8 { + uint8_t t1; + uint8_t t2; + uint8_t t3; + uint8_t t4; +}; + + +extern const char * sg_lib_version_str; + +extern struct sg_lib_value_name_t sg_lib_normal_opcodes[]; +extern struct sg_lib_value_name_t sg_lib_read_buff_arr[]; +extern struct sg_lib_value_name_t sg_lib_write_buff_arr[]; +extern struct sg_lib_value_name_t sg_lib_maint_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_maint_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_pr_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_pr_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_in12_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_out12_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_in16_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_out16_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_bidi_arr[]; +extern struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_variable_length_arr[]; +extern struct sg_lib_value_name_t sg_lib_zoning_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_zoning_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_read_attr_arr[]; +extern struct sg_lib_value_name_t sg_lib_read_pos_arr[]; +extern struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[]; +extern struct sg_lib_asc_ascq_t sg_lib_asc_ascq[]; +extern struct sg_lib_value_name_t sg_lib_scsi_feature_sets[]; +extern const char * sg_lib_sense_key_desc[]; +extern const char * sg_lib_pdt_strs[]; +extern const char * sg_lib_transport_proto_strs[]; +extern int sg_lib_pdt_decay_arr[]; + +extern struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[]; +extern struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_linux_inc.h b/tools/sg_write_buffer/include/sg_linux_inc.h new file mode 100644 index 0000000..b587c6c --- /dev/null +++ b/tools/sg_write_buffer/include/sg_linux_inc.h @@ -0,0 +1,56 @@ +#ifndef SG_LINUX_INC_H +#define SG_LINUX_INC_H + +#ifdef SG_KERNEL_INCLUDES + #define __user + typedef unsigned char u8; + #include "/usr/src/linux/include/scsi/sg.h" + #include "/usr/src/linux/include/scsi/scsi.h" +#else + #ifdef SG_TRICK_GNU_INCLUDES + #include <linux/../scsi/sg.h> + #include <linux/../scsi/scsi.h> + #else + #include <scsi/sg.h> + #include <scsi/scsi.h> + #endif +#endif + +#ifdef BLKGETSIZE64 + #ifndef u64 + #include <stdint.h> /* C99 header for exact integer types */ + typedef uint64_t u64; /* problems with BLKGETSIZE64 ioctl in lk 2.4 */ + #endif +#endif + +/* + Getting the correct include files for the sg interface can be an ordeal. + In a perfect world, one would just write: + #include <scsi/sg.h> + #include <scsi/scsi.h> + This would include the files found in the /usr/include/scsi directory. + Those files are maintained with the GNU library which may or may not + agree with the kernel and version of sg driver that is running. Any + many cases this will not matter. However in some it might, for example + glibc 2.1's include files match the sg driver found in the lk 2.2 + series. Hence if glibc 2.1 is used with lk 2.4 then the additional + sg v3 interface will not be visible. + If this is a problem then defining SG_KERNEL_INCLUDES will access the + kernel supplied header files (assuming they are in the normal place). + The GNU library maintainers and various kernel people don't like + this approach (but it does work). + The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and + was used) prior to glibc 2.2 . Prior to that version /usr/include/linux + was a symbolic link to /usr/src/linux/include/linux . + + There are other approaches if this include "mixup" causes pain. These + would involve include files being copied or symbolic links being + introduced. + + Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES + nor SG_TRICK_GNU_INCLUDES is defined. + + dpg 20010415, 20030522 +*/ + +#endif diff --git a/tools/sg_write_buffer/include/sg_pr2serr.h b/tools/sg_write_buffer/include/sg_pr2serr.h new file mode 100644 index 0000000..4419087 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pr2serr.h @@ -0,0 +1,30 @@ +#ifndef SG_PR2SERR_H +#define SG_PR2SERR_H + +/* + * Copyright (c) 2004-2018 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. + */ + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(__GNUC__) || defined(__clang__) +int pr2serr(const char * fmt, ...) + __attribute__ ((format (printf, 1, 2))); +#else +int pr2serr(const char * fmt, ...); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_pt.h b/tools/sg_write_buffer/include/sg_pt.h new file mode 100644 index 0000000..b01215c --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pt.h @@ -0,0 +1,215 @@ +#ifndef SG_PT_H +#define SG_PT_H + +/* + * Copyright (c) 2005-2018 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. + */ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* This declaration hides the fact that each implementation has its own + * structure "derived" (using a C++ term) from this one. It compiles + * because 'struct sg_pt_base' is only referenced (by pointer: 'objp') + * in this interface. An instance of this structure represents the + * context of one SCSI command. */ +struct sg_pt_base; + + +/* The format of the version string is like this: "2.01 20090201". + * The leading digit will be incremented if this interface changes + * in a way that may impact backward compatibility. */ +const char * scsi_pt_version(); + + +/* 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); + +/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed + * together. Returns valid file descriptor( >= 0 ) if successful, otherwise + * returns -1 or a negated errno. + * In Win32 O_EXCL translated to equivalent. */ +int scsi_pt_open_flags(const char * device_name, int flags, int verbose); + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int scsi_pt_close_device(int device_fd); + +/* 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); + + +/* Creates an object that can be used to issue one or more SCSI commands + * (or task management functions). Returns NULL if problem. + * Once this object has been created it should be destroyed with + * 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 + * associate a device file descriptor (handle) with the object and a + * verbose argument that causes error messages 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. */ +struct sg_pt_base * + construct_scsi_pt_obj_with_fd(int dev_fd, int verbose); + +/* 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 * objp, int dev_fd, int verbose); + +/* 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 * objp); + +/* Clear state information held in *objp . This allows this object to be + * used to issue more than one SCSI command. The dev_fd is remembered. + * 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) */ +void set_scsi_pt_cdb(struct sg_pt_base * objp, const unsigned char * cdb, + int cdb_len); +/* Set the sense buffer and the maximum length that it can handle */ +void set_scsi_pt_sense(struct sg_pt_base * objp, unsigned char * 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 */ + unsigned char * 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 unsigned char * 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 */ +void set_pt_metadata_xfer(struct sg_pt_base * objp, unsigned char * 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); +void set_scsi_pt_task_management(struct sg_pt_base * objp, int tmf_code); +void set_scsi_pt_task_attr(struct sg_pt_base * objp, int attribute, + int priority); + +/* Following is a guard which is defined when set_scsi_pt_flags() is + * present. Older versions of this library may not have this function. */ +#define SCSI_PT_FLAGS_FUNCTION 1 +/* If neither QUEUE_AT_HEAD nor QUEUE_AT_TAIL are given, or both + * are given, use the pass-through default. */ +#define SCSI_PT_FLAGS_QUEUE_AT_TAIL 0x10 +#define SCSI_PT_FLAGS_QUEUE_AT_HEAD 0x20 +/* Set (potentially OS dependent) flags for pass-through mechanism. + * Apart from contradictions, flags can be OR-ed together. */ +void set_scsi_pt_flags(struct sg_pt_base * objp, int flags); + +#define SCSI_PT_DO_START_OK 0 +#define SCSI_PT_DO_BAD_PARAMS 1 +#define SCSI_PT_DO_TIMEOUT 2 +#define SCSI_PT_DO_NVME_STATUS 48 /* == SG_LIB_NVME_STATUS */ +/* If OS error prior to or during command submission then returns negated + * error value (e.g. Unix '-errno'). This includes interrupted system calls + * (e.g. by a signal) in which case -EINTR would be returned. Note that + * system call errors also can be fetched with get_scsi_pt_os_err(). + * Return 0 if okay (i.e. at the very least: command sent). Positive + * return values are errors (see SCSI_PT_DO_* defines). If a file descriptor + * has already been provided by construct_scsi_pt_obj_with_fd() then the + * given 'fd' can be -1 or the same value as given to the constructor. */ +int do_scsi_pt(struct sg_pt_base * objp, int fd, int timeout_secs, + int verbose); + +#define SCSI_PT_RESULT_GOOD 0 +#define SCSI_PT_RESULT_STATUS 1 /* other than GOOD and CHECK CONDITION */ +#define SCSI_PT_RESULT_SENSE 2 +#define SCSI_PT_RESULT_TRANSPORT_ERR 3 +#define SCSI_PT_RESULT_OS_ERR 4 +/* highest numbered applicable category returned */ +int get_scsi_pt_result_category(const struct sg_pt_base * objp); + +/* If not available return 0 which implies there is no residual + * value. If supported the number of bytes actually sent back by + * the device is 'dxfer_ilen - get_scsi_pt_len()' bytes. */ +int get_scsi_pt_resid(const struct sg_pt_base * objp); + +/* Returns SCSI status value (from device that received the command). If an + * NVMe command was issued directly (i.e. through do_scsi_pt() then return + * NVMe status (i.e. ((SCT << 8) | SC)) */ +int get_scsi_pt_status_response(const struct sg_pt_base * objp); + +/* Returns SCSI status value or, if NVMe command given to do_scsi_pt(), + * then returns NVMe result (i.e. DWord(0) from completion queue). If + * 'objp' is NULL then returns 0xffffffff. */ +uint32_t get_pt_result(const struct sg_pt_base * objp); + +/* Actual sense length returned. If sense data is present but + actual sense length is not known, return 'max_sense_len' */ +int get_scsi_pt_sense_len(const struct sg_pt_base * objp); + +/* If not available return 0 (for success). */ +int get_scsi_pt_os_err(const struct sg_pt_base * objp); +char * get_scsi_pt_os_err_str(const struct sg_pt_base * objp, int max_b_len, + char * b); + +/* If not available return 0 (for success) */ +int get_scsi_pt_transport_err(const struct sg_pt_base * objp); +void set_scsi_pt_transport_err(struct sg_pt_base * objp, int err); +char * get_scsi_pt_transport_err_str(const struct sg_pt_base * objp, + int max_b_len, char * b); + +/* If not available return -1 */ +int get_scsi_pt_duration_ms(const struct sg_pt_base * objp); + +/* Return true if device associated with 'objp' uses NVMe command set. To + * be useful (in modifying the type of command sent (SCSI or NVMe) then + * construct_scsi_pt_obj_with_fd() should be used followed by an invocation + * of this function. */ +bool pt_device_is_nvme(const struct sg_pt_base * objp); + +/* If a NVMe block device (which includes the NSID) handle is associated + * with 'objp', 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 * objp); + + +/* Should be invoked once per objp after other processing is complete in + * order to clean up resources. For ever successful construct_scsi_pt_obj() + * call there should be one destruct_scsi_pt_obj(). If the + * construct_scsi_pt_obj_with_fd() function was used to create this object + * then the dev_fd provided to that constructor is not altered by this + * destructor. So the user should still close dev_fd (perhaps with + * scsi_pt_close_device() ). */ +void destruct_scsi_pt_obj(struct sg_pt_base * objp); + +#ifdef SG_LIB_WIN32 +#define SG_LIB_WIN32_DIRECT 1 + +/* Request SPT direct interface when state_direct is 1, state_direct set + * to 0 for the SPT indirect interface. Default setting selected by build + * (i.e. library compile time) and is usually indirect. */ +void scsi_pt_win32_direct(int state_direct); + +/* Returns current SPT interface state, 1 for direct, 0 for indirect */ +int scsi_pt_win32_spt_state(void); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_pt_linux.h b/tools/sg_write_buffer/include/sg_pt_linux.h new file mode 100644 index 0000000..5e22fd7 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pt_linux.h @@ -0,0 +1,171 @@ +#ifndef SG_PT_LINUX_H +#define SG_PT_LINUX_H + +/* + * Copyright (c) 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. + */ + +#include <stdint.h> +#include <stdbool.h> + +#include <linux/types.h> + +#include "sg_pt_nvme.h" + +/* This header is for internal use by the sg3_utils library (libsgutils) + * and is Linux specific. Best not to include it directly in code that + * is meant to be OS independent. */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_LINUX_BSG_H + +#define BSG_PROTOCOL_SCSI 0 + +#define BSG_SUB_PROTOCOL_SCSI_CMD 0 +#define BSG_SUB_PROTOCOL_SCSI_TMF 1 +#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT 2 + +/* + * For flag constants below: + * sg.h sg_io_hdr also has bits defined for it's flags member. These + * two flag values (0x10 and 0x20) have the same meaning in sg.h . For + * bsg the BSG_FLAG_Q_AT_HEAD flag is ignored since it is the default. + */ +#define BSG_FLAG_Q_AT_TAIL 0x10 /* default is Q_AT_HEAD */ +#define BSG_FLAG_Q_AT_HEAD 0x20 + +struct sg_io_v4 { + __s32 guard; /* [i] 'Q' to differentiate from v3 */ + __u32 protocol; /* [i] 0 -> SCSI , .... */ + __u32 subprotocol; /* [i] 0 -> SCSI command, 1 -> SCSI task + management function, .... */ + + __u32 request_len; /* [i] in bytes */ + __u64 request; /* [i], [*i] {SCSI: cdb} */ + __u64 request_tag; /* [i] {SCSI: task tag (only if flagged)} */ + __u32 request_attr; /* [i] {SCSI: task attribute} */ + __u32 request_priority; /* [i] {SCSI: task priority} */ + __u32 request_extra; /* [i] {spare, for padding} */ + __u32 max_response_len; /* [i] in bytes */ + __u64 response; /* [i], [*o] {SCSI: (auto)sense data} */ + + /* "dout_": data out (to device); "din_": data in (from device) */ + __u32 dout_iovec_count; /* [i] 0 -> "flat" dout transfer else + dout_xfer points to array of iovec */ + __u32 dout_xfer_len; /* [i] bytes to be transferred to device */ + __u32 din_iovec_count; /* [i] 0 -> "flat" din transfer */ + __u32 din_xfer_len; /* [i] bytes to be transferred from device */ + __u64 dout_xferp; /* [i], [*i] */ + __u64 din_xferp; /* [i], [*o] */ + + __u32 timeout; /* [i] units: millisecond */ + __u32 flags; /* [i] bit mask */ + __u64 usr_ptr; /* [i->o] unused internally */ + __u32 spare_in; /* [i] */ + + __u32 driver_status; /* [o] 0 -> ok */ + __u32 transport_status; /* [o] 0 -> ok */ + __u32 device_status; /* [o] {SCSI: command completion status} */ + __u32 retry_delay; /* [o] {SCSI: status auxiliary information} */ + __u32 info; /* [o] additional information */ + __u32 duration; /* [o] time to complete, in milliseconds */ + __u32 response_len; /* [o] bytes of response actually written */ + __s32 din_resid; /* [o] din_xfer_len - actual_din_xfer_len */ + __s32 dout_resid; /* [o] dout_xfer_len - actual_dout_xfer_len */ + __u64 generated_tag; /* [o] {SCSI: transport generated task tag} */ + __u32 spare_out; /* [o] */ + + __u32 padding; +}; + +#else + +#include <linux/bsg.h> + +#endif + + +struct sg_pt_linux_scsi { + struct sg_io_v4 io_hdr; /* use v4 header as it is more general */ + /* Leave io_hdr in first place of this structure */ + bool is_sg; + bool is_bsg; + bool is_nvme; /* OS device type, if false ignore nvme_direct */ + bool nvme_direct; /* false: our SNTL; true: received NVMe command */ + bool mdxfer_out; /* direction of metadata xfer, true->data-out */ + bool scsi_dsense; /* SCSI descriptor sense active when true */ + int dev_fd; /* -1 if not given (yet) */ + int in_err; + int os_err; + 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; /* DW0 from completion queue */ + uint32_t nvme_status; /* SCT|SC: DW3 27:17 from completion queue, + * note: the DNR+More bit are not there. + * The whole 16 byte completion q entry is + * sent back as sense data */ + uint32_t mdxfer_len; + void * mdxferp; + uint8_t * nvme_id_ctlp; /* cached response to controller IDENTIFY */ + uint8_t * free_nvme_id_ctlp; + unsigned char tmf_request[4]; +}; + +struct sg_pt_base { + struct sg_pt_linux_scsi impl; +}; + + +#ifndef sg_nvme_admin_cmd +#define sg_nvme_admin_cmd sg_nvme_passthru_cmd +#endif + +/* Linux NVMe related ioctls */ +#ifndef NVME_IOCTL_ID +#define NVME_IOCTL_ID _IO('N', 0x40) +#endif +#ifndef NVME_IOCTL_ADMIN_CMD +#define NVME_IOCTL_ADMIN_CMD _IOWR('N', 0x41, struct sg_nvme_admin_cmd) +#endif +#ifndef NVME_IOCTL_SUBMIT_IO +#define NVME_IOCTL_SUBMIT_IO _IOW('N', 0x42, struct sg_nvme_user_io) +#endif +#ifndef NVME_IOCTL_IO_CMD +#define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct sg_nvme_passthru_cmd) +#endif +#ifndef NVME_IOCTL_RESET +#define NVME_IOCTL_RESET _IO('N', 0x44) +#endif +#ifndef NVME_IOCTL_SUBSYS_RESET +#define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45) +#endif + +extern bool sg_bsg_nvme_char_major_checked; +extern int sg_bsg_major; +extern volatile int sg_nvme_char_major; +extern long sg_lin_page_size; + +void sg_find_bsg_nvme_char_major(int verbose); +int sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb); + +/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5) + * to the name of its associated char device (e.g. /dev/nvme0). If this + * occurs true is returned and the char device name is placed in 'b' (as + * long as b_len is sufficient). Otherwise false is returned. */ +bool sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len, + char * b); + + +#ifdef __cplusplus +} +#endif + +#endif /* end of SG_PT_LINUX_H */ diff --git a/tools/sg_write_buffer/include/sg_pt_nvme.h b/tools/sg_write_buffer/include/sg_pt_nvme.h new file mode 100644 index 0000000..3df98b4 --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pt_nvme.h @@ -0,0 +1,172 @@ +#ifndef SG_PT_NVME_H +#define SG_PT_NVME_H + +/* + * Copyright (c) 2017-2018 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. + */ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* structures copied and slightly modified from <linux/nvme_ioctl.h> which + * is Copyright (c) 2011-2014, Intel Corporation. */ + + +/* Note that the command input structure is in (packed) "cpu" format. That + * means, for example, if the CPU is little endian (most are) then so is the + * structure. However what comes out in the data-in buffer (e.g. for the + * Admin Identify command response) is almost all little endian following ATA + * (but no SCSI and IP which are big endian) and Intel's preference. There + * are exceptions, for example the EUI-64 identifiers in the Admin Identify + * response are big endian. + * + * Code online (e.g. nvme-cli at github.com) seems to like packed strcutures, + * the author prefers byte offset plus a range of unaligned integer builders + * such as those in sg_unaligned.h . + */ + +#ifdef __GNUC__ +#ifndef __clang__ + struct __attribute__((__packed__)) sg_nvme_user_io +#else + struct sg_nvme_user_io +#endif +#else +struct sg_nvme_user_io +#endif +{ + uint8_t opcode; + uint8_t flags; + uint16_t control; + uint16_t nblocks; + uint16_t rsvd; + uint64_t metadata; + uint64_t addr; + uint64_t slba; + uint32_t dsmgmt; + uint32_t reftag; + uint16_t apptag; + uint16_t appmask; +} +#ifdef SG_LIB_FREEBSD +__packed; +#else +; +#endif + +/* Using byte offsets and unaligned be/le copies safer than packed + * structures. These are for sg_nvme_user_io . */ +#define SG_NVME_IO_OPCODE 0 +#define SG_NVME_IO_FLAGS 1 +#define SG_NVME_IO_CONTROL 2 +#define SG_NVME_IO_NBLOCKS 4 +#define SG_NVME_IO_RSVD 6 +#define SG_NVME_IO_METADATA 8 +#define SG_NVME_IO_ADDR 16 +#define SG_NVME_IO_SLBA 24 +#define SG_NVME_IO_DSMGMT 32 +#define SG_NVME_IO_REFTAG 36 +#define SG_NVME_IO_APPTAG 40 +#define SG_NVME_IO_APPMASK 42 + +#ifdef __GNUC__ +#ifndef __clang__ + struct __attribute__((__packed__)) sg_nvme_passthru_cmd +#else + struct sg_nvme_passthru_cmd +#endif +#else +struct sg_nvme_passthru_cmd +#endif +{ + uint8_t opcode; + uint8_t flags; + uint16_t rsvd1; + uint32_t nsid; + uint32_t cdw2; + uint32_t cdw3; + uint64_t metadata; + uint64_t addr; + uint32_t metadata_len; + uint32_t data_len; + uint32_t cdw10; + uint32_t cdw11; + uint32_t cdw12; + uint32_t cdw13; + uint32_t cdw14; + uint32_t cdw15; +#ifdef SG_LIB_LINUX + uint32_t timeout_ms; + uint32_t result; /* out: DWord(0) from completion queue */ +#endif +} +#ifdef SG_LIB_FREEBSD +__packed; +#else +; +#endif + + +/* Using byte offsets and unaligned be/le copies safer than packed + * structures. These are for sg_nvme_passthru_cmd . */ +#define SG_NVME_PT_OPCODE 0 /* length: 1 byte */ +#define SG_NVME_PT_FLAGS 1 /* length: 1 byte */ +#define SG_NVME_PT_RSVD1 2 /* length: 2 bytes */ +#define SG_NVME_PT_NSID 4 /* length: 4 bytes */ +#define SG_NVME_PT_CDW2 8 /* length: 4 bytes */ +#define SG_NVME_PT_CDW3 12 /* length: 4 bytes */ +#define SG_NVME_PT_METADATA 16 /* length: 8 bytes */ +#define SG_NVME_PT_ADDR 24 /* length: 8 bytes */ +#define SG_NVME_PT_METADATA_LEN 32 /* length: 4 bytes */ +#define SG_NVME_PT_DATA_LEN 36 /* length: 4 bytes */ +#define SG_NVME_PT_CDW10 40 /* length: 4 bytes */ +#define SG_NVME_PT_CDW11 44 /* length: 4 bytes */ +#define SG_NVME_PT_CDW12 48 /* length: 4 bytes */ +#define SG_NVME_PT_CDW13 52 /* length: 4 bytes */ +#define SG_NVME_PT_CDW14 56 /* length: 4 bytes */ +#define SG_NVME_PT_CDW15 60 /* length: 4 bytes */ + +#ifdef SG_LIB_LINUX +/* General references state that "all NVMe commands are 64 bytes long". If + * so then the following are add-ons by Linux, go to the OS and not the + * the NVMe device. */ +#define SG_NVME_PT_TIMEOUT_MS 64 /* length: 4 bytes */ +#define SG_NVME_PT_RESULT 68 /* length: 4 bytes */ +#endif + +/* Byte offset of Result and Status (plus phase bit) in CQ */ +#define SG_NVME_PT_CQ_RESULT 0 /* CDW0, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW0 0 /* CDW0, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW1 4 /* CDW1, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW2 8 /* CDW2, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW3 12 /* CDW3, length: 4 bytes */ +#define SG_NVME_PT_CQ_STATUS_P 14 /* CDW3 31:16, length: 2 bytes */ + + +/* Valid namespace IDs (nsid_s) range from 1 to 0xfffffffe, leaving: */ +#define SG_NVME_BROADCAST_NSID 0xffffffff /* all namespaces */ +#define SG_NVME_CTL_NSID 0x0 /* the "controller's" namespace */ + +/* Given the NVMe Identify Controller response and optionally the NVMe + * Identify Namespace response (NULL otherwise), generate the SCSI VPD + * page 0x83 (device identification) descriptor(s) in dop. Return the + * number of bytes written which will not exceed max_do_len. Probably use + * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport + * protocol (tproto) should be -1 if not known, else SCSI value. + * N.B. Does not write total VPD page length into dop[2:3] . */ +int sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p, + const uint8_t * nvme_id_ns_p, int pdt, + int tproto, uint8_t * dop, int max_do_len); + +#ifdef __cplusplus +} +#endif + +#endif /* SG_PT_NVME_H */ diff --git a/tools/sg_write_buffer/include/sg_pt_win32.h b/tools/sg_write_buffer/include/sg_pt_win32.h new file mode 100644 index 0000000..b49437f --- /dev/null +++ b/tools/sg_write_buffer/include/sg_pt_win32.h @@ -0,0 +1,473 @@ +#ifndef SG_PT_WIN32_H +#define SG_PT_WIN32_H +/* + * The information in this file was obtained from scsi-wnt.h by + * Richard Stemmer, rs@epost.de . He in turn gives credit to + * Jay A. Key (for scsipt.c). + * The plscsi program (by Pat LaVarre <p.lavarre@ieee.org>) has + * also been used as a reference. + * Much of the information in this header can also be obtained + * from msdn.microsoft.com . + * Updated for cygwin version 1.7.17 changes 20121026 + */ + +/* WIN32_LEAN_AND_MEAN may be required to prevent inclusion of <winioctl.h> */ +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCSI_MAX_SENSE_LEN 64 +#define SCSI_MAX_CDB_LEN 16 +#define SCSI_MAX_INDIRECT_DATA 16384 + +typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + ULONG_PTR DataBufferOffset; /* was ULONG; problem in 64 bit */ + ULONG SenseInfoOffset; + UCHAR Cdb[SCSI_MAX_CDB_LEN]; +} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH; + + +typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + PVOID DataBuffer; + ULONG SenseInfoOffset; + UCHAR Cdb[SCSI_MAX_CDB_LEN]; +} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT; + + +typedef struct { + SCSI_PASS_THROUGH spt; + /* plscsi shows a follow on 16 bytes allowing 32 byte cdb */ + ULONG Filler; + UCHAR ucSenseBuf[SCSI_MAX_SENSE_LEN]; + UCHAR ucDataBuf[SCSI_MAX_INDIRECT_DATA]; +} SCSI_PASS_THROUGH_WITH_BUFFERS, *PSCSI_PASS_THROUGH_WITH_BUFFERS; + + +typedef struct { + SCSI_PASS_THROUGH_DIRECT spt; + ULONG Filler; + UCHAR ucSenseBuf[SCSI_MAX_SENSE_LEN]; +} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; + + + +typedef struct { + UCHAR NumberOfLogicalUnits; + UCHAR InitiatorBusId; + ULONG InquiryDataOffset; +} SCSI_BUS_DATA, *PSCSI_BUS_DATA; + + +typedef struct { + UCHAR NumberOfBusses; + SCSI_BUS_DATA BusData[1]; +} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO; + + +typedef struct { + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + BOOLEAN DeviceClaimed; + ULONG InquiryDataLength; + ULONG NextInquiryDataOffset; + UCHAR InquiryData[1]; +} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA; + + +typedef struct { + ULONG Length; + UCHAR PortNumber; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; +} SCSI_ADDRESS, *PSCSI_ADDRESS; + +/* + * Standard IOCTL define + */ +#ifndef CTL_CODE +#define CTL_CODE(DevType, Function, Method, Access) \ + (((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) +#endif + +/* + * file access values + */ +#ifndef FILE_ANY_ACCESS +#define FILE_ANY_ACCESS 0 +#endif +#ifndef FILE_READ_ACCESS +#define FILE_READ_ACCESS 0x0001 +#endif +#ifndef FILE_WRITE_ACCESS +#define FILE_WRITE_ACCESS 0x0002 +#endif + +// IOCTL_STORAGE_QUERY_PROPERTY + +#define FILE_DEVICE_MASS_STORAGE 0x0000002d +#define IOCTL_STORAGE_BASE FILE_DEVICE_MASS_STORAGE +#define FILE_ANY_ACCESS 0 + +// #define METHOD_BUFFERED 0 + +#define IOCTL_STORAGE_QUERY_PROPERTY \ + CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) + + +#ifndef _DEVIOCTL_ +typedef enum _STORAGE_BUS_TYPE { + BusTypeUnknown = 0x00, + BusTypeScsi = 0x01, + BusTypeAtapi = 0x02, + BusTypeAta = 0x03, + BusType1394 = 0x04, + BusTypeSsa = 0x05, + BusTypeFibre = 0x06, + BusTypeUsb = 0x07, + BusTypeRAID = 0x08, + BusTypeiScsi = 0x09, + BusTypeSas = 0x0A, + BusTypeSata = 0x0B, + BusTypeSd = 0x0C, + BusTypeMmc = 0x0D, + BusTypeVirtual = 0xE, + BusTypeFileBackedVirtual = 0xF, + BusTypeSpaces = 0x10, + BusTypeNvme = 0x11, + BusTypeSCM = 0x12, + BusTypeUfs = 0x13, + BusTypeMax = 0x14, + BusTypeMaxReserved = 0x7F +} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE; + +typedef enum _STORAGE_PROTOCOL_TYPE { + ProtocolTypeUnknown = 0, + ProtocolTypeScsi, + ProtocolTypeAta, + ProtocolTypeNvme, + ProtocolTypeSd +} STORAGE_PROTOCOL_TYPE; + +typedef enum _STORAGE_PROTOCOL_NVME_DATA_TYPE { + NVMeDataTypeUnknown = 0, + NVMeDataTypeIdentify, + NVMeDataTypeLogPage, + NVMeDataTypeFeature +} STORAGE_PROTOCOL_NVME_DATA_TYPE; + +typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA { + STORAGE_PROTOCOL_TYPE ProtocolType; + ULONG DataType; + ULONG ProtocolDataRequestValue; + ULONG ProtocolDataRequestSubValue; + ULONG ProtocolDataOffset; + ULONG ProtocolDataLength; + ULONG FixedProtocolReturnData; + ULONG Reserved[3]; +} STORAGE_PROTOCOL_SPECIFIC_DATA; + + +typedef struct _STORAGE_DEVICE_DESCRIPTOR { + ULONG Version; + ULONG Size; + UCHAR DeviceType; + UCHAR DeviceTypeModifier; + BOOLEAN RemovableMedia; + BOOLEAN CommandQueueing; + ULONG VendorIdOffset; /* 0 if not available */ + ULONG ProductIdOffset; /* 0 if not available */ + ULONG ProductRevisionOffset;/* 0 if not available */ + ULONG SerialNumberOffset; /* -1 if not available ?? */ + STORAGE_BUS_TYPE BusType; + ULONG RawPropertiesLength; + UCHAR RawDeviceProperties[1]; +} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR; + +#define STORAGE_PROTOCOL_STRUCTURE_VERSION 0x1 + +#define IOCTL_STORAGE_PROTOCOL_COMMAND \ + CTL_CODE(IOCTL_STORAGE_BASE, 0x04F0, METHOD_BUFFERED, \ + FILE_READ_ACCESS | FILE_WRITE_ACCESS) + +typedef struct _STORAGE_PROTOCOL_COMMAND { + DWORD Version; /* STORAGE_PROTOCOL_STRUCTURE_VERSION */ + DWORD Length; + STORAGE_PROTOCOL_TYPE ProtocolType; + DWORD Flags; + DWORD ReturnStatus; + DWORD ErrorCode; + DWORD CommandLength; + DWORD ErrorInfoLength; + DWORD DataToDeviceTransferLength; + DWORD DataFromDeviceTransferLength; + DWORD TimeOutValue; + DWORD ErrorInfoOffset; + DWORD DataToDeviceBufferOffset; + DWORD DataFromDeviceBufferOffset; + DWORD CommandSpecific; + DWORD Reserved0; + DWORD FixedProtocolReturnData; + DWORD Reserved1[3]; + BYTE Command[1]; /* has CommandLength elements */ +} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND; + +#endif /* _DEVIOCTL_ */ + +typedef struct _STORAGE_DEVICE_UNIQUE_IDENTIFIER { + ULONG Version; + ULONG Size; + ULONG StorageDeviceIdOffset; + ULONG StorageDeviceOffset; + ULONG DriveLayoutSignatureOffset; +} STORAGE_DEVICE_UNIQUE_IDENTIFIER, *PSTORAGE_DEVICE_UNIQUE_IDENTIFIER; + +// Use CompareStorageDuids(PSTORAGE_DEVICE_UNIQUE_IDENTIFIER duid1, duid2) +// to test for equality + +#ifndef _DEVIOCTL_ +typedef enum _STORAGE_QUERY_TYPE { + PropertyStandardQuery = 0, + PropertyExistsQuery, + PropertyMaskQuery, + PropertyQueryMaxDefined +} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE; + +typedef enum _STORAGE_PROPERTY_ID { + StorageDeviceProperty = 0, + StorageAdapterProperty, + StorageDeviceIdProperty, + StorageDeviceUniqueIdProperty, + StorageDeviceWriteCacheProperty, + StorageMiniportProperty, + StorageAccessAlignmentProperty, + /* Identify controller goes to adapter; Identify namespace to device */ + StorageAdapterProtocolSpecificProperty = 49, + StorageDeviceProtocolSpecificProperty = 50 +} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID; + +typedef struct _STORAGE_PROPERTY_QUERY { + STORAGE_PROPERTY_ID PropertyId; + STORAGE_QUERY_TYPE QueryType; + UCHAR AdditionalParameters[1]; +} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY; + +typedef struct _STORAGE_PROTOCOL_DATA_DESCRIPTOR { + DWORD Version; + DWORD Size; + STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecificData; +} STORAGE_PROTOCOL_DATA_DESCRIPTOR, *PSTORAGE_PROTOCOL_DATA_DESCRIPTOR; + +// Command completion status +// The "Phase Tag" field and "Status Field" are separated in spec. We define +// them in the same data structure to ease the memory access from software. +// +typedef union { + struct { + USHORT P : 1; // Phase Tag (P) + + USHORT SC : 8; // Status Code (SC) + USHORT SCT : 3; // Status Code Type (SCT) + USHORT Reserved : 2; + USHORT M : 1; // More (M) + USHORT DNR : 1; // Do Not Retry (DNR) + } DUMMYSTRUCTNAME; + USHORT AsUshort; +} NVME_COMMAND_STATUS, *PNVME_COMMAND_STATUS; + +// Information of log: NVME_LOG_PAGE_ERROR_INFO. Size: 64 bytes +// +typedef struct { + ULONGLONG ErrorCount; + USHORT SQID; // Submission Queue ID + USHORT CMDID; // Command ID + NVME_COMMAND_STATUS Status; // Status Field: This field indicates the + // Status Field for the command that + // completed. The Status Field is located in + // bits 15:01, bit 00 corresponds to the Phase + // Tag posted for the command. + struct { + USHORT Byte : 8; // Byte in command that contained error + USHORT Bit : 3; // Bit in command that contained error + USHORT Reserved : 5; + } ParameterErrorLocation; + + ULONGLONG Lba; // LBA: This field indicates the first LBA + // that experienced the error condition, if + // applicable. + ULONG NameSpace; // Namespace: This field indicates the nsid + // that the error is associated with, if + // applicable. + UCHAR VendorInfoAvailable; // Vendor Specific Information Available + UCHAR Reserved0[3]; + ULONGLONG CommandSpecificInfo; // This field contains command specific + // information. If used, the command + // definition specifies the information + // returned. + UCHAR Reserved1[24]; +} NVME_ERROR_INFO_LOG, *PNVME_ERROR_INFO_LOG; + +typedef struct { + + ULONG DW0; + ULONG Reserved; + + union { + struct { + USHORT SQHD; // SQ Head Pointer (SQHD) + USHORT SQID; // SQ Identifier (SQID) + } DUMMYSTRUCTNAME; + + ULONG AsUlong; + } DW2; + + union { + struct { + USHORT CID; // Command Identifier (CID) + NVME_COMMAND_STATUS Status; + } DUMMYSTRUCTNAME; + + ULONG AsUlong; + } DW3; + +} NVME_COMPLETION_ENTRY, *PNVME_COMPLETION_ENTRY; + + +// Bit-mask values for STORAGE_PROTOCOL_COMMAND - "Flags" field. +// +// Flag indicates the request targeting to adapter instead of device. +#define STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST 0x80000000 + +// +// Status values for STORAGE_PROTOCOL_COMMAND - "ReturnStatus" field. +// +#define STORAGE_PROTOCOL_STATUS_PENDING 0x0 +#define STORAGE_PROTOCOL_STATUS_SUCCESS 0x1 +#define STORAGE_PROTOCOL_STATUS_ERROR 0x2 +#define STORAGE_PROTOCOL_STATUS_INVALID_REQUEST 0x3 +#define STORAGE_PROTOCOL_STATUS_NO_DEVICE 0x4 +#define STORAGE_PROTOCOL_STATUS_BUSY 0x5 +#define STORAGE_PROTOCOL_STATUS_DATA_OVERRUN 0x6 +#define STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES 0x7 + +#define STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED 0xFF + +// Command Length for Storage Protocols. +// +// NVMe commands are always 64 bytes. +#define STORAGE_PROTOCOL_COMMAND_LENGTH_NVME 0x40 + +// Command Specific Information for Storage Protocols - CommandSpecific field +// +#define STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND 0x01 +#define STORAGE_PROTOCOL_SPECIFIC_NVME_NVM_COMMAND 0x02 + +#endif /* _DEVIOCTL_ */ + + +// NVME_PASS_THROUGH + +#ifndef STB_IO_CONTROL +typedef struct _SRB_IO_CONTROL { + ULONG HeaderLength; + UCHAR Signature[8]; + ULONG Timeout; + ULONG ControlCode; + ULONG ReturnCode; + ULONG Length; +} SRB_IO_CONTROL, *PSRB_IO_CONTROL; +#endif + +#ifndef NVME_PASS_THROUGH_SRB_IO_CODE + +#define NVME_SIG_STR "NvmeMini" +#define NVME_STORPORT_DRIVER 0xe000 + +#define NVME_PASS_THROUGH_SRB_IO_CODE \ + CTL_CODE(NVME_STORPORT_DRIVER, 0x0800, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#pragma pack(1) + +/* Following is pre-Win10; used with DeviceIoControl(IOCTL_SCSI_MINIPORT), + * in Win10 need DeviceIoControl(IOCTL_STORAGE_PROTOCOL_COMMAND) for pure + * pass-through. Win10 also has "Protocol specific queries" for things like + * Identify and Get feature. */ +typedef struct _NVME_PASS_THROUGH_IOCTL +{ + SRB_IO_CONTROL SrbIoCtrl; + ULONG VendorSpecific[6]; + ULONG NVMeCmd[16]; /* Command DW[0...15] */ + ULONG CplEntry[4]; /* Completion DW[0...3] */ + ULONG Direction; /* 0=None, 1=Out, 2=In, 3=I/O */ + ULONG QueueId; /* 0=AdminQ */ + ULONG DataBufferLen; /* sizeof(DataBuffer) if Data In */ + ULONG MetaDataLen; + ULONG ReturnBufferLen; /* offsetof(DataBuffer), plus + * sizeof(DataBuffer) if Data Out */ + UCHAR DataBuffer[1]; +} NVME_PASS_THROUGH_IOCTL; +#pragma pack() + +#endif // NVME_PASS_THROUGH_SRB_IO_CODE + + +/* + * method codes + */ +#define METHOD_BUFFERED 0 +#define METHOD_IN_DIRECT 1 +#define METHOD_OUT_DIRECT 2 +#define METHOD_NEITHER 3 + + +#define IOCTL_SCSI_BASE 0x00000004 + +/* + * constants for DataIn member of SCSI_PASS_THROUGH* structures + */ +#define SCSI_IOCTL_DATA_OUT 0 +#define SCSI_IOCTL_DATA_IN 1 +#define SCSI_IOCTL_DATA_UNSPECIFIED 2 + +#define IOCTL_SCSI_PASS_THROUGH CTL_CODE(IOCTL_SCSI_BASE, 0x0401, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_MINIPORT CTL_CODE(IOCTL_SCSI_BASE, 0x0402, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_GET_INQUIRY_DATA CTL_CODE(IOCTL_SCSI_BASE, 0x0403, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_GET_CAPABILITIES CTL_CODE(IOCTL_SCSI_BASE, 0x0404, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE(IOCTL_SCSI_BASE, 0x0405, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_GET_ADDRESS CTL_CODE(IOCTL_SCSI_BASE, 0x0406, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/sg_write_buffer/include/sg_unaligned.h b/tools/sg_write_buffer/include/sg_unaligned.h new file mode 100644 index 0000000..3b2c70a --- /dev/null +++ b/tools/sg_write_buffer/include/sg_unaligned.h @@ -0,0 +1,325 @@ +#ifndef SG_UNALIGNED_H +#define SG_UNALIGNED_H + +/* + * Copyright (c) 2014-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. + */ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Borrowed from the Linux kernel, via mhvtl */ + +/* In the first section below, functions that copy unsigned integers in a + * computer's native format, to and from an unaligned big endian sequence of + * bytes. Big endian byte format "on the wire" is the default used by SCSI + * standards (www.t10.org). Big endian is also the network byte order. */ + +static inline uint16_t __get_unaligned_be16(const uint8_t *p) +{ + return p[0] << 8 | p[1]; +} + +static inline uint32_t __get_unaligned_be32(const uint8_t *p) +{ + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +} + +/* Assume 48 bit value placed in uint64_t */ +static inline uint64_t __get_unaligned_be48(const uint8_t *p) +{ + return (uint64_t)__get_unaligned_be16(p) << 32 | + __get_unaligned_be32(p + 2); +} + +static inline uint64_t __get_unaligned_be64(const uint8_t *p) +{ + return (uint64_t)__get_unaligned_be32(p) << 32 | + __get_unaligned_be32(p + 4); +} + +static inline void __put_unaligned_be16(uint16_t val, uint8_t *p) +{ + *p++ = val >> 8; + *p++ = val; +} + +static inline void __put_unaligned_be32(uint32_t val, uint8_t *p) +{ + __put_unaligned_be16(val >> 16, p); + __put_unaligned_be16(val, p + 2); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline void __put_unaligned_be48(uint64_t val, uint8_t *p) +{ + __put_unaligned_be16(val >> 32, p); + __put_unaligned_be32(val, p + 2); +} + +static inline void __put_unaligned_be64(uint64_t val, uint8_t *p) +{ + __put_unaligned_be32(val >> 32, p); + __put_unaligned_be32(val, p + 4); +} + +static inline uint16_t sg_get_unaligned_be16(const void *p) +{ + return __get_unaligned_be16((const uint8_t *)p); +} + +static inline uint32_t sg_get_unaligned_be24(const void *p) +{ + return ((const uint8_t *)p)[0] << 16 | ((const uint8_t *)p)[1] << 8 | + ((const uint8_t *)p)[2]; +} + +static inline uint32_t sg_get_unaligned_be32(const void *p) +{ + return __get_unaligned_be32((const uint8_t *)p); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline uint64_t sg_get_unaligned_be48(const void *p) +{ + return __get_unaligned_be48((const uint8_t *)p); +} + +static inline uint64_t sg_get_unaligned_be64(const void *p) +{ + return __get_unaligned_be64((const uint8_t *)p); +} + +/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than + * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is + * an 8 byte unsigned integer. */ +static inline uint64_t sg_get_unaligned_be(int num_bytes, const void *p) +{ + if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t))) + return 0; + else { + const uint8_t * xp = (const uint8_t *)p; + uint64_t res = *xp; + + for (++xp; num_bytes > 1; ++xp, --num_bytes) + res = (res << 8) | *xp; + return res; + } +} + +static inline void sg_put_unaligned_be16(uint16_t val, void *p) +{ + __put_unaligned_be16(val, (uint8_t *)p); +} + +static inline void sg_put_unaligned_be24(uint32_t val, void *p) +{ + ((uint8_t *)p)[0] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[2] = val & 0xff; +} + +static inline void sg_put_unaligned_be32(uint32_t val, void *p) +{ + __put_unaligned_be32(val, (uint8_t *)p); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline void sg_put_unaligned_be48(uint64_t val, void *p) +{ + __put_unaligned_be48(val, (uint8_t *)p); +} + +static inline void sg_put_unaligned_be64(uint64_t val, void *p) +{ + __put_unaligned_be64(val, (uint8_t *)p); +} + +/* Since cdb and parameter blocks are often memset to zero before these + * unaligned function partially fill them, then check for a val of zero + * and ignore if it is with these variants. */ +static inline void sg_nz_put_unaligned_be16(uint16_t val, void *p) +{ + if (val) + __put_unaligned_be16(val, (uint8_t *)p); +} + +static inline void sg_nz_put_unaligned_be24(uint32_t val, void *p) +{ + if (val) { + ((uint8_t *)p)[0] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[2] = val & 0xff; + } +} + +static inline void sg_nz_put_unaligned_be32(uint32_t val, void *p) +{ + if (val) + __put_unaligned_be32(val, (uint8_t *)p); +} + +static inline void sg_nz_put_unaligned_be64(uint64_t val, void *p) +{ + if (val) + __put_unaligned_be64(val, (uint8_t *)p); +} + + +/* Below are the little endian equivalents of the big endian functions + * above. Little endian is used by ATA, PCI and NVMe. + */ + +static inline uint16_t __get_unaligned_le16(const uint8_t *p) +{ + return p[1] << 8 | p[0]; +} + +static inline uint32_t __get_unaligned_le32(const uint8_t *p) +{ + return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0]; +} + +static inline uint64_t __get_unaligned_le64(const uint8_t *p) +{ + return (uint64_t)__get_unaligned_le32(p + 4) << 32 | + __get_unaligned_le32(p); +} + +static inline void __put_unaligned_le16(uint16_t val, uint8_t *p) +{ + *p++ = val; + *p++ = val >> 8; +} + +static inline void __put_unaligned_le32(uint32_t val, uint8_t *p) +{ + __put_unaligned_le16(val >> 16, p + 2); + __put_unaligned_le16(val, p); +} + +static inline void __put_unaligned_le64(uint64_t val, uint8_t *p) +{ + __put_unaligned_le32(val >> 32, p + 4); + __put_unaligned_le32(val, p); +} + +static inline uint16_t sg_get_unaligned_le16(const void *p) +{ + return __get_unaligned_le16((const uint8_t *)p); +} + +static inline uint32_t sg_get_unaligned_le24(const void *p) +{ + return (uint32_t)__get_unaligned_le16((const uint8_t *)p) | + ((const uint8_t *)p)[2] << 16; +} + +static inline uint32_t sg_get_unaligned_le32(const void *p) +{ + return __get_unaligned_le32((const uint8_t *)p); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline uint64_t sg_get_unaligned_le48(const void *p) +{ + return (uint64_t)__get_unaligned_le16((const uint8_t *)p + 4) << 32 | + __get_unaligned_le32((const uint8_t *)p); +} + +static inline uint64_t sg_get_unaligned_le64(const void *p) +{ + return __get_unaligned_le64((const uint8_t *)p); +} + +/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than + * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is + * an 8 byte unsigned integer. */ +static inline uint64_t sg_get_unaligned_le(int num_bytes, const void *p) +{ + if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t))) + return 0; + else { + const uint8_t * xp = (const uint8_t *)p + (num_bytes - 1); + uint64_t res = *xp; + + for (--xp; num_bytes > 1; --xp, --num_bytes) + res = (res << 8) | *xp; + return res; + } +} + +static inline void sg_put_unaligned_le16(uint16_t val, void *p) +{ + __put_unaligned_le16(val, (uint8_t *)p); +} + +static inline void sg_put_unaligned_le24(uint32_t val, void *p) +{ + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; +} + +static inline void sg_put_unaligned_le32(uint32_t val, void *p) +{ + __put_unaligned_le32(val, (uint8_t *)p); +} + +/* Assume 48 bit value placed in uint64_t */ +static inline void sg_put_unaligned_le48(uint64_t val, void *p) +{ + ((uint8_t *)p)[5] = (val >> 40) & 0xff; + ((uint8_t *)p)[4] = (val >> 32) & 0xff; + ((uint8_t *)p)[3] = (val >> 24) & 0xff; + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; +} + +static inline void sg_put_unaligned_le64(uint64_t val, void *p) +{ + __put_unaligned_le64(val, (uint8_t *)p); +} + +/* Since cdb and parameter blocks are often memset to zero before these + * unaligned function partially fill them, then check for a val of zero + * and ignore if it is with these variants. */ +static inline void sg_nz_put_unaligned_le16(uint16_t val, void *p) +{ + if (val) + __put_unaligned_le16(val, (uint8_t *)p); +} + +static inline void sg_nz_put_unaligned_le24(uint32_t val, void *p) +{ + if (val) { + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; + } +} + +static inline void sg_nz_put_unaligned_le32(uint32_t val, void *p) +{ + if (val) + __put_unaligned_le32(val, (uint8_t *)p); +} + +static inline void sg_nz_put_unaligned_le64(uint64_t val, void *p) +{ + if (val) + __put_unaligned_le64(val, (uint8_t *)p); +} + +#ifdef __cplusplus +} +#endif + +#endif /* SG_UNALIGNED_H */ diff --git a/tools/sg_write_buffer/sg_cmds_basic.c b/tools/sg_write_buffer/sg_cmds_basic.c new file mode 100644 index 0000000..35a4991 --- /dev/null +++ b/tools/sg_write_buffer/sg_cmds_basic.c @@ -0,0 +1,663 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +/* + * CONTENTS + * Some SCSI commands are executed in many contexts and hence began + * to appear in several sg3_utils utilities. This files centralizes + * some of the low level command execution code. In most cases the + * interpretation of the command response is left to the each + * utility. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_pt.h" +#include "sg_unaligned.h" + +/* Needs to be after config.h */ +#ifdef SG_LIB_LINUX +#include <errno.h> +#endif + + +static const char * const version_str = "1.83 20180204"; + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define EBUFF_SZ 256 + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define INQUIRY_CMD 0x12 +#define INQUIRY_CMDLEN 6 +#define REQUEST_SENSE_CMD 0x3 +#define REQUEST_SENSE_CMDLEN 6 +#define REPORT_LUNS_CMD 0xa0 +#define REPORT_LUNS_CMDLEN 12 +#define TUR_CMD 0x0 +#define TUR_CMDLEN 6 + +#define SAFE_STD_INQ_RESP_LEN 36 /* other lengths lock up some devices */ + + +const char * +sg_cmds_version() +{ + return version_str; +} + +#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; +} + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. */ +int +sg_cmds_open_device(const char * device_name, bool read_only, int verbose) +{ + /* 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; + + return scsi_pt_open_device(device_name, read_only, verbose); +} + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. */ +int +sg_cmds_open_flags(const char * device_name, int flags, int verbose) +{ + return scsi_pt_open_flags(device_name, flags, verbose); +} + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int +sg_cmds_close_device(int device_fd) +{ + return scsi_pt_close_device(device_fd); +} + +static const char * const pass_through_s = "pass-through"; + +static int +sg_cmds_process_helper(const char * leadin, int mx_di_len, int resid, + const unsigned char * sbp, int slen, bool noisy, + int verbose, int * o_sense_cat) +{ + int scat, got; + bool n = false; + bool check_data_in = false; + char b[512]; + + scat = sg_err_category_sense(sbp, slen); + switch (scat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_ABORTED_COMMAND: + case SG_LIB_CAT_COPY_ABORTED: + case SG_LIB_CAT_DATA_PROTECT: + case SG_LIB_CAT_PROTECTION: + case SG_LIB_CAT_NO_SENSE: + case SG_LIB_CAT_MISCOMPARE: + n = false; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_MEDIUM_HARD: + check_data_in = true; +#if defined(__GNUC__) +#if (__GNUC__ >= 7) + __attribute__((fallthrough)); + /* FALL THROUGH */ +#endif +#endif + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_SENSE: + default: + n = noisy; + break; + } + if (verbose || n) { + if (leadin && (strlen(leadin) > 0)) + pr2ws("%s:\n", leadin); + sg_get_sense_str(NULL, sbp, slen, (verbose > 1), + sizeof(b), b); + pr2ws("%s", b); + if ((mx_di_len > 0) && (resid > 0)) { + got = mx_di_len - resid; + if ((verbose > 2) || check_data_in || (got > 0)) + pr2ws(" %s requested %d bytes (data-in) but got %d " + "bytes\n", pass_through_s, mx_di_len, got); + } + } + if (o_sense_cat) + *o_sense_cat = scat; + return -2; +} + +/* This is a helper function used by sg_cmds_* implementations after the + * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid + * sense data is found it is decoded and output to sg_warnings_strm (def: + * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for + * "sense" category (may not be fatal), -1 for failed, 0, or a positive + * number. If 'mx_di_len > 0' then asks pass-through for resid and returns + * (mx_di_len - resid); otherwise returns 0. So for data-in it should return + * the actual number of bytes received. For data-out (to device) or no data + * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category + * output via 'o_sense_cat' pointer (if not NULL). Note that several sense + * categories also have data in bytes received; -2 is still returned. */ +int +sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin, + int pt_res, int mx_di_len, const unsigned char * sbp, + bool noisy, int verbose, int * o_sense_cat) +{ + int got, cat, duration, slen, resid, resp_code, sstat; + bool transport_sense; + char b[1024]; + + if (NULL == leadin) + leadin = ""; + if (pt_res < 0) { +#ifdef SG_LIB_LINUX + if (verbose) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); + if ((-ENXIO == pt_res) && o_sense_cat) { + if (verbose > 2) + pr2ws("map ENXIO to SG_LIB_CAT_NOT_READY\n"); + *o_sense_cat = SG_LIB_CAT_NOT_READY; + return -2; + } else if (noisy && (0 == verbose)) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); +#else + if (noisy || verbose) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); +#endif + return -1; + } else if (SCSI_PT_DO_BAD_PARAMS == pt_res) { + pr2ws("%s: bad %s setup\n", leadin, pass_through_s); + return -1; + } else if (SCSI_PT_DO_TIMEOUT == pt_res) { + pr2ws("%s: %s timeout\n", leadin, pass_through_s); + return -1; + } + if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + pr2ws(" duration=%d ms\n", duration); + resid = (mx_di_len > 0) ? get_scsi_pt_resid(ptvp) : 0; + slen = get_scsi_pt_sense_len(ptvp); + switch ((cat = get_scsi_pt_result_category(ptvp))) { + case SCSI_PT_RESULT_GOOD: + if (sbp && (slen > 7)) { + resp_code = sbp[0] & 0x7f; + /* SBC referrals can have status=GOOD and sense_key=COMPLETED */ + if (resp_code >= 0x70) { + if (resp_code < 0x72) { + if (SPC_SK_NO_SENSE != (0xf & sbp[2])) + sg_err_category_sense(sbp, slen); + } else if (resp_code < 0x74) { + if (SPC_SK_NO_SENSE != (0xf & sbp[1])) + sg_err_category_sense(sbp, slen); + } + } + } + if (mx_di_len > 0) { + got = mx_di_len - resid; + if ((verbose > 1) && (resid != 0)) + pr2ws(" %s: %s requested %d bytes (data-in) but got %d " + "bytes\n", leadin, pass_through_s, mx_di_len, got); + if (got >= 0) + return got; + else { + if (verbose) + pr2ws(" %s: %s can't get negative bytes, say it got " + "none\n", leadin, pass_through_s); + return 0; + } + } else + return 0; + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + sstat = get_scsi_pt_status_response(ptvp); + if (o_sense_cat) { + switch (sstat) { + case SAM_STAT_RESERVATION_CONFLICT: + *o_sense_cat = SG_LIB_CAT_RES_CONFLICT; + return -2; + case SAM_STAT_CONDITION_MET: + *o_sense_cat = SG_LIB_CAT_CONDITION_MET; + return -2; + case SAM_STAT_BUSY: + *o_sense_cat = SG_LIB_CAT_BUSY; + return -2; + case SAM_STAT_TASK_SET_FULL: + *o_sense_cat = SG_LIB_CAT_TS_FULL; + return -2; + case SAM_STAT_ACA_ACTIVE: + *o_sense_cat = SG_LIB_CAT_ACA_ACTIVE; + return -2; + case SAM_STAT_TASK_ABORTED: + *o_sense_cat = SG_LIB_CAT_TASK_ABORTED; + return -2; + default: + break; + } + } + if (verbose || noisy) { + sg_get_scsi_status_str(sstat, sizeof(b), b); + pr2ws("%s: scsi status: %s\n", leadin, b); + } + return -1; + case SCSI_PT_RESULT_SENSE: + return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp, slen, + noisy, verbose, o_sense_cat); + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose || noisy) { + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); + pr2ws("%s: transport: %s\n", leadin, b); + } +#ifdef SG_LIB_LINUX + transport_sense = (slen > 0); +#else + transport_sense = ((SAM_STAT_CHECK_CONDITION == + get_scsi_pt_status_response(ptvp)) && (slen > 0)); +#endif + if (transport_sense) + return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp, + slen, noisy, verbose, o_sense_cat); + else + return -1; + case SCSI_PT_RESULT_OS_ERR: + if (verbose || noisy) { + get_scsi_pt_os_err_str(ptvp, sizeof(b), b); + pr2ws("%s: os: %s\n", leadin, b); + } + return -1; + default: + pr2ws("%s: unknown %s result category (%d)\n", leadin, pass_through_s, + cat); + return -1; + } +} + +bool +sg_cmds_is_nvme(const struct sg_pt_base * ptvp) +{ + return pt_device_is_nvme(ptvp); +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +static const char * const inquiry_s = "inquiry"; + +static int +sg_ll_inquiry_com(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + int res, ret, k, sense_cat, resid; + unsigned char inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + unsigned char * up; + struct sg_pt_base * ptvp; + + if (cmddt) + inq_cdb[1] |= 0x2; + if (evpd) + inq_cdb[1] |= 0x1; + inq_cdb[2] = (unsigned char)pg_op; + /* 16 bit allocation length (was 8, increased in spc3r09, 200209) */ + sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3); + if (verbose) { + pr2ws(" %s cdb: ", inquiry_s); + for (k = 0; k < INQUIRY_CMDLEN; ++k) + pr2ws("%02x ", inq_cdb[k]); + pr2ws("\n"); + } + if (resp && (mx_resp_len > 0)) { + up = (unsigned char *)resp; + up[0] = 0x7f; /* defensive prefill */ + if (mx_resp_len > 4) + up[4] = 0; + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) { + pr2ws("%s: out of memory\n", __func__); + if (residp) + *residp = 0; + return -1; + } + set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, inquiry_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else if (ret < 4) { + if (verbose) + pr2ws("%s: got too few bytes (%d)\n", __func__, ret); + ret = SG_LIB_CAT_MALFORMED; + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s resid (%d) should never exceed requested " + "len=%d\n", inquiry_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer, based on resid */ + memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +} + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. + * The CMDDT field is obsolete in the INQUIRY cdb. */ +int +sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + return sg_ll_inquiry_com(sg_fd, cmddt, evpd, pg_op, resp, mx_resp_len, + 0 /* timeout_sec */, NULL, noisy, verbose); +} + +/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response. + * Returns 0 when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data, + bool noisy, int verbose) +{ + int ret; + unsigned char inq_resp[SAFE_STD_INQ_RESP_LEN]; + + if (inq_data) { + memset(inq_data, 0, sizeof(* inq_data)); + inq_data->peripheral_qualifier = 0x3; + inq_data->peripheral_type = 0x1f; + } + ret = sg_ll_inquiry_com(sg_fd, false, false, 0, inq_resp, + sizeof(inq_resp), 0, NULL, noisy, verbose); + + if (inq_data && (0 == ret)) { + inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7; + inq_data->peripheral_type = inq_resp[0] & 0x1f; + inq_data->byte_1 = inq_resp[1]; + inq_data->version = inq_resp[2]; + inq_data->byte_3 = inq_resp[3]; + inq_data->byte_5 = inq_resp[5]; + inq_data->byte_6 = inq_resp[6]; + inq_data->byte_7 = inq_resp[7]; + memcpy(inq_data->vendor, inq_resp + 8, 8); + memcpy(inq_data->product, inq_resp + 16, 16); + memcpy(inq_data->revision, inq_resp + 32, 4); + } + return ret; +} + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. + * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so + * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION + * CODES command instead). Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + return sg_ll_inquiry_com(sg_fd, false, evpd, pg_op, resp, mx_resp_len, + timeout_secs, residp, noisy, verbose); +} + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Looks for progress indicator if 'progress' non-NULL; + * if found writes value [0..65535] else write -1. + * Returns 0 when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress, + bool noisy, int verbose) +{ + static const char * const tur_s = "test unit ready"; + int res, ret, k, sense_cat; + unsigned char tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (verbose) { + pr2ws(" %s cdb: ", tur_s); + for (k = 0; k < TUR_CMDLEN; ++k) + pr2ws("%02x ", tur_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(tur_s)))) + return -1; + set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_packet_id(ptvp, pack_id); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, tur_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + if (progress) { + int slen = get_scsi_pt_sense_len(ptvp); + + if (! sg_get_sense_progress_fld(sense_b, slen, progress)) + *progress = -1; + } + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Returns 0 when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose) +{ + return sg_ll_test_unit_ready_progress(sg_fd, pack_id, NULL, noisy, + verbose); +} + +/* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various + * SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const rq_s = "request sense"; + int k, ret, res, sense_cat; + unsigned char rs_cdb[REQUEST_SENSE_CMDLEN] = + {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (desc) + rs_cdb[1] |= 0x1; + if (mx_resp_len > 0xff) { + pr2ws("mx_resp_len cannot exceed 255\n"); + return -1; + } + rs_cdb[4] = mx_resp_len & 0xff; + if (verbose) { + pr2ws(" %s cmd: ", rq_s); + for (k = 0; k < REQUEST_SENSE_CMDLEN; ++k) + pr2ws("%02x ", rs_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(rq_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, rq_s, res, mx_resp_len, sense_b, noisy, + verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((mx_resp_len >= 8) && (ret < 8)) { + if (verbose) + pr2ws(" %s: got %d bytes in response, too short\n", rq_s, + ret); + ret = -1; + } else + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const report_luns_s = "report luns"; + int k, ret, res, sense_cat; + unsigned char rl_cdb[REPORT_LUNS_CMDLEN] = + {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + rl_cdb[2] = select_report & 0xff; + sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", report_luns_s); + for (k = 0; k < REPORT_LUNS_CMDLEN; ++k) + pr2ws("%02x ", rl_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(report_luns_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, report_luns_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/tools/sg_write_buffer/sg_cmds_basic2.c b/tools/sg_write_buffer/sg_cmds_basic2.c new file mode 100644 index 0000000..18b6cd7 --- /dev/null +++ b/tools/sg_write_buffer/sg_cmds_basic2.c @@ -0,0 +1,1069 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +/* + * CONTENTS + * Some SCSI commands are executed in many contexts and hence began + * to appear in several sg3_utils utilities. This files centralizes + * some of the low level command execution code. In most cases the + * interpretation of the command response is left to the each + * utility. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_pt.h" +#include "sg_unaligned.h" + + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define EBUFF_SZ 256 + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define SYNCHRONIZE_CACHE_CMD 0x35 +#define SYNCHRONIZE_CACHE_CMDLEN 10 +#define SERVICE_ACTION_IN_16_CMD 0x9e +#define SERVICE_ACTION_IN_16_CMDLEN 16 +#define READ_CAPACITY_16_SA 0x10 +#define READ_CAPACITY_10_CMD 0x25 +#define READ_CAPACITY_10_CMDLEN 10 +#define MODE_SENSE6_CMD 0x1a +#define MODE_SENSE6_CMDLEN 6 +#define MODE_SENSE10_CMD 0x5a +#define MODE_SENSE10_CMDLEN 10 +#define MODE_SELECT6_CMD 0x15 +#define MODE_SELECT6_CMDLEN 6 +#define MODE_SELECT10_CMD 0x55 +#define MODE_SELECT10_CMDLEN 10 +#define LOG_SENSE_CMD 0x4d +#define LOG_SENSE_CMDLEN 10 +#define LOG_SELECT_CMD 0x4c +#define LOG_SELECT_CMDLEN 10 +#define START_STOP_CMD 0x1b +#define START_STOP_CMDLEN 6 +#define PREVENT_ALLOW_CMD 0x1e +#define PREVENT_ALLOW_CMDLEN 6 + +#define MODE6_RESP_HDR_LEN 4 +#define MODE10_RESP_HDR_LEN 8 +#define MODE_RESP_ARB_LEN 1024 + +#define INQUIRY_RESP_INITIAL_LEN 36 + + +#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; +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group, + unsigned int lba, unsigned int count, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "synchronize cache(10)"; + int res, ret, k, sense_cat; + unsigned char sc_cdb[SYNCHRONIZE_CACHE_CMDLEN] = + {SYNCHRONIZE_CACHE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (sync_nv) + sc_cdb[1] |= 4; + if (immed) + sc_cdb[1] |= 2; + sg_put_unaligned_be32((uint32_t)lba, sc_cdb + 2); + sc_cdb[6] = group & 0x1f; + if (count > 0xffff) { + pr2ws("count too big\n"); + return -1; + } + sg_put_unaligned_be16((int16_t)count, sc_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SYNCHRONIZE_CACHE_CMDLEN; ++k) + pr2ws("%02x ", sc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, sc_cdb, sizeof(sc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read capacity(16)"; + int k, ret, res, sense_cat; + unsigned char rc_cdb[SERVICE_ACTION_IN_16_CMDLEN] = + {SERVICE_ACTION_IN_16_CMD, READ_CAPACITY_16_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (pmi) { /* lbs only valid when pmi set */ + rc_cdb[14] |= 1; + sg_put_unaligned_be64(llba, rc_cdb + 2); + } + /* Allocation length, no guidance in SBC-2 rev 15b */ + sg_put_unaligned_be32((uint32_t)mx_resp_len, rc_cdb + 10); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", rc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ CAPACITY (10) command. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read capacity(10)"; + int k, ret, res, sense_cat; + unsigned char rc_cdb[READ_CAPACITY_10_CMDLEN] = + {READ_CAPACITY_10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (pmi) { /* lbs only valid when pmi set */ + rc_cdb[8] |= 1; + sg_put_unaligned_be32((uint32_t)lba, rc_cdb + 2); + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_CAPACITY_10_CMDLEN; ++k) + pr2ws("%02x ", rc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, int sub_pg_code, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode sense(6)"; + int res, ret, k, sense_cat, resid; + unsigned char modes_cdb[MODE_SENSE6_CMDLEN] = + {MODE_SENSE6_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (unsigned char)(dbd ? 0x8 : 0); + modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff); + modes_cdb[4] = (unsigned char)(mx_resp_len & 0xff); + if (mx_resp_len > 0xff) { + pr2ws("mx_resp_len too big\n"); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SENSE6_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +} + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + return sg_ll_mode_sense10_v2(sg_fd, llbaa, dbd, pc, pg_code, sub_pg_code, + resp, mx_resp_len, 0, NULL, noisy, verbose); +} + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + int timeout_secs, int * residp, bool noisy, int verbose) +{ + int res, ret, k, sense_cat, resid; + static const char * const cdb_name_s = "mode sense(10)"; + struct sg_pt_base * ptvp; + unsigned char modes_cdb[MODE_SENSE10_CMDLEN] = + {MODE_SENSE10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + + modes_cdb[1] = (unsigned char)((dbd ? 0x8 : 0) | (llbaa ? 0x10 : 0)); + modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff); + sg_put_unaligned_be16((int16_t)mx_resp_len, modes_cdb + 7); + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + goto gen_err; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SENSE10_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + goto gen_err; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +gen_err: + if (residp) + *residp = 0; + return -1; +} + +/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode select(6)"; + int res, ret, k, sense_cat; + unsigned char modes_cdb[MODE_SELECT6_CMDLEN] = + {MODE_SELECT6_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0)); + modes_cdb[4] = (unsigned char)(param_len & 0xff); + if (param_len > 0xff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SELECT6_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode select(10)"; + int res, ret, k, sense_cat; + unsigned char modes_cdb[MODE_SELECT10_CMDLEN] = + {MODE_SELECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0)); + sg_put_unaligned_be16((int16_t)param_len, modes_cdb + 7); + if (param_len > 0xffff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SELECT10_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. In most cases users are + * interested in the first mode page. This function returns the (byte) + * offset of the start of the first mode page. Set mode_sense_6 to true for + * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful + * or -1 if failure. If there is a failure a message is written to err_buff + * if it is non-NULL and err_buff_len > 0. */ +int +sg_mode_page_offset(const unsigned char * resp, int resp_len, + bool mode_sense_6, char * err_buff, int err_buff_len) +{ + int bd_len, calc_len, offset; + bool err_buff_ok = ((err_buff_len > 0) && err_buff); + + if ((NULL == resp) || (resp_len < 4)) + goto too_short; + if (mode_sense_6) { + calc_len = resp[0] + 1; + bd_len = resp[3]; + offset = bd_len + MODE6_RESP_HDR_LEN; + } else { /* Mode sense(10) */ + if (resp_len < 8) + goto too_short; + calc_len = sg_get_unaligned_be16(resp) + 2; + bd_len = sg_get_unaligned_be16(resp + 6); + /* LongLBA doesn't change this calculation */ + offset = bd_len + MODE10_RESP_HDR_LEN; + } + if ((offset + 2) > calc_len) { + if (err_buff_ok) + snprintf(err_buff, err_buff_len, "calculated response " + "length too small, offset=%d calc_len=%d bd_len=%d\n", + offset, calc_len, bd_len); + offset = -1; + } + return offset; +too_short: + if (err_buff_ok) + snprintf(err_buff, err_buff_len, "given MS(%d) response length (%d) " + "too short\n", (mode_sense_6 ? 6 : 10), resp_len); + return -1; +} + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. This functions returns the + * length (in bytes) of those three components. Note that the return value + * can exceed resp_len in which case the MODE SENSE command should be + * re-issued with a larger response buffer. If bd_lenp is non-NULL and if + * successful the block descriptor length (in bytes) is written to *bd_lenp. + * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10) + * responses. Returns -1 if there is an error (e.g. response too short). */ +int +sg_msense_calc_length(const unsigned char * resp, int resp_len, + bool mode_sense_6, int * bd_lenp) +{ + int calc_len; + + if (NULL == resp) + goto an_err; + if (mode_sense_6) { + if (resp_len < 4) + goto an_err; + calc_len = resp[0] + 1; + } else { + if (resp_len < 8) + goto an_err; + calc_len = sg_get_unaligned_be16(resp + 0) + 2; + } + if (bd_lenp) + *bd_lenp = mode_sense_6 ? resp[3] : sg_get_unaligned_be16(resp + 6); + return calc_len; +an_err: + if (bd_lenp) + *bd_lenp = 0; + return -1; +} + +/* Fetches current, changeable, default and/or saveable modes pages as + * indicated by pcontrol_arr for given pg_code and sub_pg_code. If + * mode6==false then use MODE SENSE (10) else use MODE SENSE (6). If + * flexible set and mode data length seems wrong then try and + * fix (compensating hack for bad device or driver). pcontrol_arr + * should have 4 elements for output of current, changeable, default + * and saved values respectively. Each element should be NULL or + * at least mx_mpage_len bytes long. + * Return of 0 -> overall success, various SG_LIB_CAT_* positive values or + * -1 -> other errors. + * If success_mask pointer is not NULL then first zeros it. Then set bits + * 0, 1, 2 and/or 3 if the current, changeable, default and saved values + * respectively have been fetched. If error on current page + * then stops and returns that error; otherwise continues if an error is + * detected but returns the first error encountered. */ +int +sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, int sub_pg_code, + bool dbd, bool flexible, int mx_mpage_len, + int * success_mask, void * pcontrol_arr[], + int * reported_lenp, int verbose) +{ + bool resp_mode6; + int k, n, res, offset, calc_len, xfer_len; + int resid = 0; + const int msense10_hlen = MODE10_RESP_HDR_LEN; + unsigned char buff[MODE_RESP_ARB_LEN]; + char ebuff[EBUFF_SZ]; + int first_err = 0; + + if (success_mask) + *success_mask = 0; + if (reported_lenp) + *reported_lenp = 0; + if (mx_mpage_len < 4) + return 0; + memset(ebuff, 0, sizeof(ebuff)); + /* first try to find length of current page response */ + memset(buff, 0, msense10_hlen); + if (mode6) /* want first 8 bytes just in case */ + res = sg_ll_mode_sense6(sg_fd, dbd, 0 /* pc */, pg_code, + sub_pg_code, buff, msense10_hlen, true, + verbose); + else /* MODE SENSE(10) obviously */ + res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd, + 0 /* pc */, pg_code, sub_pg_code, buff, + msense10_hlen, 0, &resid, true, verbose); + if (0 != res) + return res; + n = buff[0]; + if (reported_lenp) { + int m; + + m = sg_msense_calc_length(buff, msense10_hlen, mode6, NULL) - resid; + if (m < 0) /* Grrr, this should not happen */ + m = 0; + *reported_lenp = m; + } + resp_mode6 = mode6; + if (flexible) { + if (mode6 && (n < 3)) { + resp_mode6 = false; + if (verbose) + pr2ws(">>> msense(6) but resp[0]=%d so try msense(10) " + "response processing\n", n); + } + if ((! mode6) && (n > 5)) { + if ((n > 11) && (0 == (n % 2)) && (0 == buff[4]) && + (0 == buff[5]) && (0 == buff[6])) { + buff[1] = n; + buff[0] = 0; + if (verbose) + pr2ws(">>> msense(10) but resp[0]=%d and not msense(6) " + "response so fix length\n", n); + } else + resp_mode6 = true; + } + } + if (verbose && (resp_mode6 != mode6)) + pr2ws(">>> msense(%d) but resp[0]=%d so switch response " + "processing\n", (mode6 ? 6 : 10), buff[0]); + calc_len = sg_msense_calc_length(buff, msense10_hlen, resp_mode6, NULL); + if (calc_len > MODE_RESP_ARB_LEN) + calc_len = MODE_RESP_ARB_LEN; + offset = sg_mode_page_offset(buff, calc_len, resp_mode6, ebuff, EBUFF_SZ); + if (offset < 0) { + if (('\0' != ebuff[0]) && (verbose > 0)) + pr2ws("%s: %s\n", __func__, ebuff); + return SG_LIB_CAT_MALFORMED; + } + xfer_len = calc_len - offset; + if (xfer_len > mx_mpage_len) + xfer_len = mx_mpage_len; + + for (k = 0; k < 4; ++k) { + if (NULL == pcontrol_arr[k]) + continue; + memset(pcontrol_arr[k], 0, mx_mpage_len); + resid = 0; + if (mode6) + res = sg_ll_mode_sense6(sg_fd, dbd, k /* pc */, + pg_code, sub_pg_code, buff, + calc_len, true, verbose); + else + res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd, + k /* pc */, pg_code, sub_pg_code, + buff, calc_len, 0, &resid, true, + verbose); + if (res || resid) { + if (0 == first_err) { + if (res) + first_err = res; + else { + first_err = -49; /* unexpected resid != 0 */ + if (verbose) + pr2ws("%s: unexpected resid=%d, page=0x%x, " + "pcontrol=%d\n", __func__, resid, pg_code, k); + } + } + if (0 == k) + break; /* if problem on current page, it won't improve */ + else + continue; + } + if (xfer_len > 0) + memcpy(pcontrol_arr[k], buff + offset, xfer_len); + if (success_mask) + *success_mask |= (1 << k); + } + return first_err; +} + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. */ +int +sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, unsigned char * resp, + int mx_resp_len, bool noisy, int verbose) +{ + return sg_ll_log_sense_v2(sg_fd, ppc, sp, pc, pg_code, subpg_code, + paramp, resp, mx_resp_len, 0, NULL, noisy, + verbose); +} + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, unsigned char * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "log sense"; + int res, ret, k, sense_cat, resid; + unsigned char logs_cdb[LOG_SENSE_CMDLEN] = + {LOG_SENSE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + goto gen_err; + } + logs_cdb[1] = (unsigned char)((ppc ? 2 : 0) | (sp ? 1 : 0)); + logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + logs_cdb[3] = (unsigned char)(subpg_code & 0xff); + sg_put_unaligned_be16((int16_t)paramp, logs_cdb + 5); + sg_put_unaligned_be16((int16_t)mx_resp_len, logs_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < LOG_SENSE_CMDLEN; ++k) + pr2ws("%02x ", logs_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + goto gen_err; + set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((mx_resp_len > 3) && (ret < 4)) { + /* resid indicates LOG SENSE response length bad, so zero it */ + resp[2] = 0; + resp[3] = 0; + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +gen_err: + if (residp) + *residp = 0; + return -1; +} + +/* Invokes a SCSI LOG SELECT command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code, + int subpg_code, unsigned char * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "log select"; + int res, ret, k, sense_cat; + unsigned char logs_cdb[LOG_SELECT_CMDLEN] = + {LOG_SELECT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (param_len > 0xffff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + logs_cdb[1] = (unsigned char)((pcr ? 2 : 0) | (sp ? 1 : 0)); + logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + logs_cdb[3] = (unsigned char)(subpg_code & 0xff); + sg_put_unaligned_be16((int16_t)param_len, logs_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < LOG_SELECT_CMDLEN; ++k) + pr2ws("%02x ", logs_cdb[k]); + pr2ws("\n"); + } + if ((verbose > 1) && (param_len > 0)) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr(paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI START STOP UNIT command (SBC + MMC). + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and + * format_layer_number(mmc) fields. They also overlap on the noflush(sbc) + * and fl(mmc) one bit field. This is the cause of the awkardly named + * pc_mod__fl_num and noflush__fl arguments to this function. + * */ +int +sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num, + int power_cond, bool noflush__fl, bool loej, bool start, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "start stop unit"; + int k, res, ret, sense_cat; + struct sg_pt_base * ptvp; + unsigned char ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + + if (immed) + ssuBlk[1] = 0x1; + ssuBlk[3] = pc_mod__fl_num & 0xf; /* bits 2 and 3 are reserved in MMC */ + ssuBlk[4] = ((power_cond & 0xf) << 4); + if (noflush__fl) + ssuBlk[4] |= 0x4; + if (loej) + ssuBlk[4] |= 0x2; + if (start) + ssuBlk[4] |= 0x1; + if (verbose) { + pr2ws(" %s command:", cdb_name_s); + for (k = 0; k < (int)sizeof(ssuBlk); ++k) + pr2ws(" %02x", ssuBlk[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, START_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command + * [was in SPC-3 but displaced from SPC-4 into SBC-3, MMC-5, SSC-3] + * prevent==0 allows removal, prevent==1 prevents removal ... + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "prevent allow medium removal"; + int k, res, ret, sense_cat; + unsigned char p_cdb[PREVENT_ALLOW_CMDLEN] = + {PREVENT_ALLOW_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((prevent < 0) || (prevent > 3)) { + pr2ws("prevent argument should be 0, 1, 2 or 3\n"); + return -1; + } + p_cdb[4] |= (prevent & 0x3); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PREVENT_ALLOW_CMDLEN; ++k) + pr2ws("%02x ", p_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, p_cdb, sizeof(p_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/tools/sg_write_buffer/sg_cmds_extra.c b/tools/sg_write_buffer/sg_cmds_extra.c new file mode 100644 index 0000000..bebc859 --- /dev/null +++ b/tools/sg_write_buffer/sg_cmds_extra.c @@ -0,0 +1,2524 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#include "sg_pt.h" +#include "sg_unaligned.h" + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define SERVICE_ACTION_IN_16_CMD 0x9e +#define SERVICE_ACTION_IN_16_CMDLEN 16 +#define SERVICE_ACTION_OUT_16_CMD 0x9f +#define SERVICE_ACTION_OUT_16_CMDLEN 16 +#define MAINTENANCE_IN_CMD 0xa3 +#define MAINTENANCE_IN_CMDLEN 12 +#define MAINTENANCE_OUT_CMD 0xa4 +#define MAINTENANCE_OUT_CMDLEN 12 + +#define ATA_PT_12_CMD 0xa1 +#define ATA_PT_12_CMDLEN 12 +#define ATA_PT_16_CMD 0x85 +#define ATA_PT_16_CMDLEN 16 +#define ATA_PT_32_SA 0x1ff0 +#define ATA_PT_32_CMDLEN 32 +#define FORMAT_UNIT_CMD 0x4 +#define FORMAT_UNIT_CMDLEN 6 +#define PERSISTENT_RESERVE_IN_CMD 0x5e +#define PERSISTENT_RESERVE_IN_CMDLEN 10 +#define PERSISTENT_RESERVE_OUT_CMD 0x5f +#define PERSISTENT_RESERVE_OUT_CMDLEN 10 +#define READ_BLOCK_LIMITS_CMD 0x5 +#define READ_BLOCK_LIMITS_CMDLEN 6 +#define READ_BUFFER_CMD 0x3c +#define READ_BUFFER_CMDLEN 10 +#define READ_DEFECT10_CMD 0x37 +#define READ_DEFECT10_CMDLEN 10 +#define REASSIGN_BLKS_CMD 0x7 +#define REASSIGN_BLKS_CMDLEN 6 +#define RECEIVE_DIAGNOSTICS_CMD 0x1c +#define RECEIVE_DIAGNOSTICS_CMDLEN 6 +#define THIRD_PARTY_COPY_OUT_CMD 0x83 /* was EXTENDED_COPY_CMD */ +#define THIRD_PARTY_COPY_OUT_CMDLEN 16 +#define THIRD_PARTY_COPY_IN_CMD 0x84 /* was RECEIVE_COPY_RESULTS_CMD */ +#define THIRD_PARTY_COPY_IN_CMDLEN 16 +#define SEND_DIAGNOSTIC_CMD 0x1d +#define SEND_DIAGNOSTIC_CMDLEN 6 +#define SERVICE_ACTION_IN_12_CMD 0xab +#define SERVICE_ACTION_IN_12_CMDLEN 12 +#define READ_LONG10_CMD 0x3e +#define READ_LONG10_CMDLEN 10 +#define UNMAP_CMD 0x42 +#define UNMAP_CMDLEN 10 +#define VERIFY10_CMD 0x2f +#define VERIFY10_CMDLEN 10 +#define VERIFY16_CMD 0x8f +#define VERIFY16_CMDLEN 16 +#define WRITE_LONG10_CMD 0x3f +#define WRITE_LONG10_CMDLEN 10 +#define WRITE_BUFFER_CMD 0x3b +#define WRITE_BUFFER_CMDLEN 10 +#define PRE_FETCH10_CMD 0x34 +#define PRE_FETCH10_CMDLEN 10 +#define PRE_FETCH16_CMD 0x90 +#define PRE_FETCH16_CMDLEN 16 +#define SEEK10_CMD 0x2b +#define SEEK10_CMDLEN 10 + +#define GET_LBA_STATUS16_SA 0x12 +#define GET_LBA_STATUS32_SA 0x12 +#define READ_LONG_16_SA 0x11 +#define READ_MEDIA_SERIAL_NUM_SA 0x1 +#define REPORT_IDENTIFYING_INFORMATION_SA 0x5 +#define REPORT_TGT_PRT_GRP_SA 0xa +#define SET_IDENTIFYING_INFORMATION_SA 0x6 +#define SET_TGT_PRT_GRP_SA 0xa +#define WRITE_LONG_16_SA 0x11 +#define REPORT_REFERRALS_SA 0x13 +#define EXTENDED_COPY_LID1_SA 0x0 + +#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; +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + + +/* Invokes a SCSI GET LBA STATUS(16) command (SBC). Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt, + void * resp, int alloc_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Get LBA status(16)"; + int k, res, sense_cat, ret; + unsigned char getLbaStatCmd[SERVICE_ACTION_IN_16_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(getLbaStatCmd, 0, sizeof(getLbaStatCmd)); + getLbaStatCmd[0] = SERVICE_ACTION_IN_16_CMD; + getLbaStatCmd[1] = GET_LBA_STATUS16_SA; + + sg_put_unaligned_be64(start_llba, getLbaStatCmd + 2); + sg_put_unaligned_be32((uint32_t)alloc_len, getLbaStatCmd + 10); + getLbaStatCmd[14] = rt; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", getLbaStatCmd[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, getLbaStatCmd, sizeof(getLbaStatCmd)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, alloc_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp, + int alloc_len, bool noisy, int verbose) +{ + return sg_ll_get_lba_status16(sg_fd, start_llba, /* rt = */ 0x0, resp, + alloc_len, noisy, verbose); +} + +#define GLS32_CMD_LEN 32 + +int +sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len, + uint32_t element_id, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Get LBA status(32)"; + int k, res, sense_cat, ret; + unsigned char gls32_cmd[GLS32_CMD_LEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(gls32_cmd, 0, sizeof(gls32_cmd)); + gls32_cmd[0] = SG_VARIABLE_LENGTH_CMD; + gls32_cmd[7] = GLS32_CMD_LEN - 8; + sg_put_unaligned_be16((uint16_t)GET_LBA_STATUS32_SA, gls32_cmd + 8); + gls32_cmd[10] = rt; + sg_put_unaligned_be64(start_llba, gls32_cmd + 12); + sg_put_unaligned_be32(scan_len, gls32_cmd + 20); + sg_put_unaligned_be32(element_id, gls32_cmd + 24); + sg_put_unaligned_be32((uint32_t)alloc_len, gls32_cmd + 28); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GLS32_CMD_LEN; ++k) + pr2ws("%02x ", gls32_cmd[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, gls32_cmd, sizeof(gls32_cmd)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, alloc_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + return sg_ll_report_tgt_prt_grp2(sg_fd, resp, mx_resp_len, false, noisy, + verbose); +} + +/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len, + bool extended, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Report target port groups"; + int k, res, ret, sense_cat; + unsigned char rtpg_cdb[MAINTENANCE_IN_CMDLEN] = + {MAINTENANCE_IN_CMD, REPORT_TGT_PRT_GRP_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (extended) + rtpg_cdb[1] |= 0x20; + sg_put_unaligned_be32((uint32_t)mx_resp_len, rtpg_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k) + pr2ws("%02x ", rtpg_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rtpg_cdb, sizeof(rtpg_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Set target port groups"; + int k, res, ret, sense_cat; + unsigned char stpg_cdb[MAINTENANCE_OUT_CMDLEN] = + {MAINTENANCE_OUT_CMD, SET_TGT_PRT_GRP_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)param_len, stpg_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k) + pr2ws("%02x ", stpg_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, stpg_cdb, sizeof(stpg_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg, + void * resp, int mx_resp_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Report referrals"; + int k, res, ret, sense_cat; + unsigned char repRef_cdb[SERVICE_ACTION_IN_16_CMDLEN] = + {SERVICE_ACTION_IN_16_CMD, REPORT_REFERRALS_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be64(start_llba, repRef_cdb + 2); + sg_put_unaligned_be32((uint32_t)mx_resp_len, repRef_cdb + 10); + if (one_seg) + repRef_cdb[14] = 0x1; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", repRef_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, repRef_cdb, sizeof(repRef_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can + * take a long time, if so set long_duration flag in which case the timeout + * is set to 7200 seconds; if the value of long_duration is > 7200 then that + * value is taken as the timeout value in seconds. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit, + bool devofl_bit, bool unitofl_bit, int long_duration, + void * paramp, int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Send diagnostic"; + int k, res, ret, sense_cat, tmout; + unsigned char senddiag_cdb[SEND_DIAGNOSTIC_CMDLEN] = + {SEND_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + senddiag_cdb[1] = (unsigned char)(st_code << 5); + if (pf_bit) + senddiag_cdb[1] |= 0x10; + if (st_bit) + senddiag_cdb[1] |= 0x4; + if (devofl_bit) + senddiag_cdb[1] |= 0x2; + if (unitofl_bit) + senddiag_cdb[1] |= 0x1; + sg_put_unaligned_be16((uint16_t)param_len, senddiag_cdb + 3); + if (long_duration > LONG_PT_TIMEOUT) + tmout = long_duration; + else + tmout = long_duration ? LONG_PT_TIMEOUT : DEF_PT_TIMEOUT; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SEND_DIAGNOSTIC_CMDLEN; ++k) + pr2ws("%02x ", senddiag_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + if (paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + pr2ws(" %s timeout: %d seconds\n", cdb_name_s, tmout); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + return sg_ll_receive_diag_v2(sg_fd, pcv, pg_code, resp, mx_resp_len, 0, + NULL, noisy, verbose); +} + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + int resid = 0; + int k, res, ret, sense_cat; + static const char * const cdb_name_s = "Receive diagnostic results"; + struct sg_pt_base * ptvp; + unsigned char rcvdiag_cdb[RECEIVE_DIAGNOSTICS_CMDLEN] = + {RECEIVE_DIAGNOSTICS_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + + if (pcv) + rcvdiag_cdb[1] = 0x1; + rcvdiag_cdb[2] = (unsigned char)(pg_code); + sg_put_unaligned_be16((uint16_t)mx_resp_len, rcvdiag_cdb + 3); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < RECEIVE_DIAGNOSTICS_CMDLEN; ++k) + pr2ws("%02x ", rcvdiag_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) { + if (residp) + *residp = 0; + return -1; + } + set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, int dl_format, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Read defect(10)"; + int res, k, ret, sense_cat; + unsigned char rdef_cdb[READ_DEFECT10_CMDLEN] = + {READ_DEFECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + rdef_cdb[2] = (dl_format & 0x7); + if (req_plist) + rdef_cdb[2] |= 0x10; + if (req_glist) + rdef_cdb[2] |= 0x8; + sg_put_unaligned_be16((uint16_t)mx_resp_len, rdef_cdb + 7); + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_DEFECT10_CMDLEN; ++k) + pr2ws("%02x ", rdef_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rdef_cdb, sizeof(rdef_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Read media serial number"; + int k, res, ret, sense_cat; + unsigned char rmsn_cdb[SERVICE_ACTION_IN_12_CMDLEN] = + {SERVICE_ACTION_IN_12_CMD, READ_MEDIA_SERIAL_NUM_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)mx_resp_len, rmsn_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_12_CMDLEN; ++k) + pr2ws("%02x ", rmsn_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rmsn_cdb, sizeof(rmsn_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was + * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Report identifying information"; + int k, res, ret, sense_cat; + unsigned char rii_cdb[MAINTENANCE_IN_CMDLEN] = {MAINTENANCE_IN_CMD, + REPORT_IDENTIFYING_INFORMATION_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)max_resp_len, rii_cdb + 6); + rii_cdb[10] |= (itype << 1) & 0xfe; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k) + pr2ws("%02x ", rii_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rii_cdb, sizeof(rii_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, max_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, max_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was + * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Set identifying information"; + int k, res, ret, sense_cat; + unsigned char sii_cdb[MAINTENANCE_OUT_CMDLEN] = {MAINTENANCE_OUT_CMD, + SET_IDENTIFYING_INFORMATION_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)param_len, sii_cdb + 6); + sii_cdb[10] |= (itype << 1) & 0xfe; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k) + pr2ws("%02x ", sii_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, sii_cdb, sizeof(sii_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst, + dlist_format, 0, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int ffmt, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst, + dlist_format, ffmt, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a FORMAT UNIT (SBC-4) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * FFMT field added in sbc4r10 [20160121] */ +int +sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Format unit"; + int k, res, ret, sense_cat, tmout; + unsigned char fu_cdb[FORMAT_UNIT_CMDLEN] = + {FORMAT_UNIT_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (fmtpinfo) + fu_cdb[1] |= (fmtpinfo << 6); + if (longlist) + fu_cdb[1] |= 0x20; + if (fmtdata) + fu_cdb[1] |= 0x10; + if (cmplst) + fu_cdb[1] |= 0x8; + if (dlist_format) + fu_cdb[1] |= (dlist_format & 0x7); + if (ffmt) + fu_cdb[4] |= (ffmt & 0x3); + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < 6; ++k) + pr2ws("%02x ", fu_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + if (param_len > 0) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + pr2ws(" %s timeout: %d seconds\n", cdb_name_s, tmout); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, fu_cdb, sizeof(fu_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REASSIGN BLOCKS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, void * paramp, + int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Reassign blocks"; + int res, k, ret, sense_cat; + unsigned char reass_cdb[REASSIGN_BLKS_CMDLEN] = + {REASSIGN_BLKS_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (longlba) + reass_cdb[1] = 0x2; + if (longlist) + reass_cdb[1] |= 0x1; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < REASSIGN_BLKS_CMDLEN; ++k) + pr2ws("%02x ", reass_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, reass_cdb, sizeof(reass_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0 + * when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Persistent reservation in"; + int res, k, ret, sense_cat; + unsigned char prin_cdb[PERSISTENT_RESERVE_IN_CMDLEN] = + {PERSISTENT_RESERVE_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (rq_servact > 0) + prin_cdb[1] = (unsigned char)(rq_servact & 0x1f); + sg_put_unaligned_be16((uint16_t)mx_resp_len, prin_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PERSISTENT_RESERVE_IN_CMDLEN; ++k) + pr2ws("%02x ", prin_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, prin_cdb, sizeof(prin_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0 + * when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope, + unsigned int rq_type, void * paramp, + int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Persistent reservation out"; + int res, k, ret, sense_cat; + unsigned char prout_cdb[PERSISTENT_RESERVE_OUT_CMDLEN] = + {PERSISTENT_RESERVE_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (rq_servact > 0) + prout_cdb[1] = (unsigned char)(rq_servact & 0x1f); + prout_cdb[2] = (((rq_scope & 0xf) << 4) | (rq_type & 0xf)); + sg_put_unaligned_be16((uint16_t)param_len, prout_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PERSISTENT_RESERVE_OUT_CMDLEN; ++k) + pr2ws("%02x ", prout_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + pr2ws(" %s parameters:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, 0); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, prout_cdb, sizeof(prout_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +static bool +has_blk_ili(unsigned char * sensep, int sb_len) +{ + int resp_code; + const unsigned char * cup; + + if (sb_len < 8) + return false; + resp_code = (0x7f & sensep[0]); + if (resp_code >= 0x72) { /* descriptor format */ + /* find block command descriptor */ + if ((cup = sg_scsi_sense_desc_find(sensep, sb_len, 0x5))) + return (cup[3] & 0x20); + } else /* fixed */ + return (sensep[2] & 0x20); + return false; +} + +/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "read long(10)"; + int k, res, sense_cat, ret; + unsigned char readLong_cdb[READ_LONG10_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(readLong_cdb, 0, READ_LONG10_CMDLEN); + readLong_cdb[0] = READ_LONG10_CMD; + if (pblock) + readLong_cdb[1] |= 0x4; + if (correct) + readLong_cdb[1] |= 0x2; + + sg_put_unaligned_be32((uint32_t)lba, readLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_LONG10_CMDLEN; ++k) + pr2ws("%02x ", readLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, valid, ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "read long(16)"; + int k, res, sense_cat, ret; + unsigned char readLong_cdb[SERVICE_ACTION_IN_16_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(readLong_cdb, 0, sizeof(readLong_cdb)); + readLong_cdb[0] = SERVICE_ACTION_IN_16_CMD; + readLong_cdb[1] = READ_LONG_16_SA; + if (pblock) + readLong_cdb[14] |= 0x2; + if (correct) + readLong_cdb[14] |= 0x1; + + sg_put_unaligned_be64(llba, readLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 12); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", readLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + unsigned int lba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write long(10)"; + int k, res, sense_cat, ret; + unsigned char writeLong_cdb[WRITE_LONG10_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(writeLong_cdb, 0, WRITE_LONG10_CMDLEN); + writeLong_cdb[0] = WRITE_LONG10_CMD; + if (cor_dis) + writeLong_cdb[1] |= 0x80; + if (wr_uncor) + writeLong_cdb[1] |= 0x40; + if (pblock) + writeLong_cdb[1] |= 0x20; + + sg_put_unaligned_be32((uint32_t)lba, writeLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < (int)sizeof(writeLong_cdb); ++k) + pr2ws("%02x ", writeLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ; + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_ILLEGAL_REQ: + { + int valid, slen, ili; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + uint64_t llba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write long(16)"; + int k, res, sense_cat, ret; + unsigned char writeLong_cdb[SERVICE_ACTION_OUT_16_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(writeLong_cdb, 0, sizeof(writeLong_cdb)); + writeLong_cdb[0] = SERVICE_ACTION_OUT_16_CMD; + writeLong_cdb[1] = WRITE_LONG_16_SA; + if (cor_dis) + writeLong_cdb[1] |= 0x80; + if (wr_uncor) + writeLong_cdb[1] |= 0x40; + if (pblock) + writeLong_cdb[1] |= 0x20; + + sg_put_unaligned_be64(llba, writeLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 12); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_OUT_16_CMDLEN; ++k) + pr2ws("%02x ", writeLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI VERIFY (10) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, * various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytchk, + unsigned int lba, int veri_len, void * data_out, + int data_out_len, unsigned int * infop, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "verify(10)"; + int k, res, ret, sense_cat, slen; + unsigned char v_cdb[VERIFY10_CMDLEN] = + {VERIFY10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */ + v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ; + if (dpo) + v_cdb[1] |= 0x10; + sg_put_unaligned_be32((uint32_t)lba, v_cdb + 2); + sg_put_unaligned_be16((uint16_t)veri_len, v_cdb + 7); + if (verbose > 1) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < VERIFY10_CMDLEN; ++k) + pr2ws("%02x ", v_cdb[k]); + pr2ws("\n"); + if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) { + k = data_out_len > 4104 ? 4104 : data_out_len; + pr2ws(" data_out buffer%s\n", + (data_out_len > 4104 ? ", first 4104 bytes" : "")); + hex2stderr((const uint8_t *)data_out, k, verbose < 5); + } + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + if (data_out_len > 0) + set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, data_out_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_MEDIUM_HARD: + { + bool valid; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + if (valid) { + if (infop) + *infop = (unsigned int)ull; + ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO; + } else + ret = SG_LIB_CAT_MEDIUM_HARD; + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI VERIFY (16) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytchk, uint64_t llba, + int veri_len, int group_num, void * data_out, + int data_out_len, uint64_t * infop, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "verify(16)"; + int k, res, ret, sense_cat, slen; + unsigned char v_cdb[VERIFY16_CMDLEN] = + {VERIFY16_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */ + v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ; + if (dpo) + v_cdb[1] |= 0x10; + sg_put_unaligned_be64(llba, v_cdb + 2); + sg_put_unaligned_be32((uint32_t)veri_len, v_cdb + 10); + v_cdb[14] = group_num & 0x1f; + if (verbose > 1) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < VERIFY16_CMDLEN; ++k) + pr2ws("%02x ", v_cdb[k]); + pr2ws("\n"); + if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) { + k = data_out_len > 4104 ? 4104 : data_out_len; + pr2ws(" data_out buffer%s\n", + (data_out_len > 4104 ? ", first 4104 bytes" : "")); + hex2stderr((const uint8_t *)data_out, k, verbose < 5); + } + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + if (data_out_len > 0) + set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, data_out_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + case SG_LIB_CAT_MEDIUM_HARD: + { + bool valid; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + if (valid) { + if (infop) + *infop = ull; + ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO; + } else + ret = SG_LIB_CAT_MEDIUM_HARD; + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is + * selected by the cdb_len argument that can take values of 12, 16 or 32 + * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9 + * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from + * the control byte, the rest is copied into an internal cdb which is then + * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15 + * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the + * timeout is set to 60 seconds. For data in or out transfers set dinp or + * doutp, and dlen to the number of bytes to transfer. If dlen is zero then + * no data transfer is assumed. If sense buffer obtained then it is written + * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is + * obtained then written to ata_return_dp, else ata_return_dp[0] is set to + * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers. + * Returns SCSI status value (>= 0) or -1 if other error. Users are + * expected to check the sense buffer themselves. If available the data in + * resid is written to residp. Note in SAT-2 and later, fixed format sense + * data may be placed in *sensep in which case sensep[0]==0x70, prior to + * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72). + */ +int +sg_ll_ata_pt(int sg_fd, const unsigned char * cdbp, int cdb_len, + int timeout_secs, void * dinp, void * doutp, int dlen, + unsigned char * sensep, int max_sense_len, + unsigned char * ata_return_dp, int max_ata_return_len, + int * residp, int verbose) +{ + int k, res, slen, duration; + int ret = -1; + unsigned char apt_cdb[ATA_PT_32_CMDLEN]; + unsigned char sense_b[SENSE_BUFF_LEN]; + unsigned char * sp; + const unsigned char * bp; + struct sg_pt_base * ptvp; + const char * cnamep; + char b[256]; + + memset(apt_cdb, 0, sizeof(apt_cdb)); + b[0] = '\0'; + switch (cdb_len) { + case 12: + cnamep = "ATA pass-through(12)"; + apt_cdb[0] = ATA_PT_12_CMD; + memcpy(apt_cdb + 1, cdbp + 1, 10); + /* control byte at cdb[11] left at zero */ + break; + case 16: + cnamep = "ATA pass-through(16)"; + apt_cdb[0] = ATA_PT_16_CMD; + memcpy(apt_cdb + 1, cdbp + 1, 14); + /* control byte at cdb[15] left at zero */ + break; + case 32: + cnamep = "ATA pass-through(32)"; + apt_cdb[0] = SG_VARIABLE_LENGTH_CMD; + /* control byte at cdb[1] left at zero */ + apt_cdb[7] = 0x18; /* length starting at next byte */ + sg_put_unaligned_be16(ATA_PT_32_SA, apt_cdb + 8); + memcpy(apt_cdb + 10, cdbp + 10, 32 - 10); + break; + default: + pr2ws("cdb_len must be 12, 16 or 32\n"); + return -1; + } + if (NULL == cdbp) { + if (verbose) + pr2ws("%s NULL cdb pointer\n", cnamep); + return -1; + } + if (sensep && (max_sense_len >= (int)sizeof(sense_b))) { + sp = sensep; + slen = max_sense_len; + } else { + sp = sense_b; + slen = sizeof(sense_b); + } + if (verbose) { + pr2ws(" %s cdb: ", cnamep); + if (cdb_len < 32) { + for (k = 0; k < cdb_len; ++k) + pr2ws("%02x ", apt_cdb[k]); + pr2ws("\n"); + } else { + pr2ws("\n"); + hex2stderr(apt_cdb, cdb_len, -1); + } + } + if (NULL == ((ptvp = create_pt_obj(cnamep)))) + return -1; + set_scsi_pt_cdb(ptvp, apt_cdb, cdb_len); + set_scsi_pt_sense(ptvp, sp, slen); + if (dlen > 0) { + if (dinp) + set_scsi_pt_data_in(ptvp, (unsigned char *)dinp, dlen); + else if (doutp) + set_scsi_pt_data_out(ptvp, (unsigned char *)doutp, dlen); + } + res = do_scsi_pt(ptvp, sg_fd, + ((timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT), + verbose); + if (SCSI_PT_DO_BAD_PARAMS == res) { + if (verbose) + pr2ws("%s: bad parameters\n", cnamep); + goto out; + } else if (SCSI_PT_DO_TIMEOUT == res) { + if (verbose) + pr2ws("%s: timeout\n", cnamep); + goto out; + } else if (res > 2) { + if (verbose) + pr2ws("%s: do_scsi_pt: errno=%d\n", cnamep, -res); + } + + if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + pr2ws(" duration=%d ms\n", duration); + + switch (get_scsi_pt_result_category(ptvp)) { + case SCSI_PT_RESULT_GOOD: + if ((sensep) && (max_sense_len > 0)) + *sensep = 0; + if ((ata_return_dp) && (max_ata_return_len > 0)) + *ata_return_dp = 0; + if (residp && (dlen > 0)) + *residp = get_scsi_pt_resid(ptvp); + ret = 0; + break; + case SCSI_PT_RESULT_STATUS: /* other than GOOD + CHECK CONDITION */ + if ((sensep) && (max_sense_len > 0)) + *sensep = 0; + if ((ata_return_dp) && (max_ata_return_len > 0)) + *ata_return_dp = 0; + ret = get_scsi_pt_status_response(ptvp); + break; + case SCSI_PT_RESULT_SENSE: + if (sensep && (sp != sensep)) { + k = get_scsi_pt_sense_len(ptvp); + k = (k > max_sense_len) ? max_sense_len : k; + memcpy(sensep, sp, k); + } + if (ata_return_dp && (max_ata_return_len > 0)) { + /* search for ATA return descriptor */ + bp = sg_scsi_sense_desc_find(sp, slen, 0x9); + if (bp) { + k = bp[1] + 2; + k = (k > max_ata_return_len) ? max_ata_return_len : k; + memcpy(ata_return_dp, bp, k); + } else + ata_return_dp[0] = 0x0; + } + if (residp && (dlen > 0)) + *residp = get_scsi_pt_resid(ptvp); + ret = get_scsi_pt_status_response(ptvp); + break; + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose) + pr2ws("%s: transport error: %s\n", cnamep, + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b)); + break; + case SCSI_PT_RESULT_OS_ERR: + if (verbose) + pr2ws("%s: os error: %s\n", cnamep, + get_scsi_pt_os_err_str(ptvp, sizeof(b) , b)); + break; + default: + if (verbose) + pr2ws("%s: unknown pt_result_category=%d\n", cnamep, + get_scsi_pt_result_category(ptvp)); + break; + } + +out: + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ BUFFER(10) command (SPC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read buffer(10)"; + int res, k, ret, sense_cat; + unsigned char rbuf_cdb[READ_BUFFER_CMDLEN] = + {READ_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + rbuf_cdb[1] = (unsigned char)(mode & 0x1f); + rbuf_cdb[2] = (unsigned char)(buffer_id & 0xff); + sg_put_unaligned_be24((uint32_t)buffer_offset, rbuf_cdb + 3); + sg_put_unaligned_be24((uint32_t)mx_resp_len, rbuf_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", rbuf_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rbuf_cdb, sizeof(rbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * paramp, int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write buffer"; + int k, res, ret, sense_cat; + unsigned char wbuf_cdb[WRITE_BUFFER_CMDLEN] = + {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + wbuf_cdb[1] = (unsigned char)(mode & 0x1f); + wbuf_cdb[2] = (unsigned char)(buffer_id & 0xff); + sg_put_unaligned_be24((uint32_t)buffer_offset, wbuf_cdb + 3); + sg_put_unaligned_be24((uint32_t)param_len, wbuf_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", wbuf_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list", cdb_name_s); + if (2 == verbose) { + pr2ws("%s:\n", (param_len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)paramp, + (param_len > 256 ? 256 : param_len), -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)paramp, param_len, 0); + } + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Adds mode specific field (spc4r32) and timeout + * to command abort to override default of 60 seconds. If timeout_secs is + * 0 or less then the default timeout is used instead. */ +int +sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id, + uint32_t buffer_offset, void * paramp, + uint32_t param_len, int timeout_secs, bool noisy, + int verbose) +{ + int k, res, ret, sense_cat; + uint8_t wbuf_cdb[WRITE_BUFFER_CMDLEN] = + {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (buffer_offset > 0xffffff) { + pr2ws("%s: buffer_offset value too large for 24 bits\n", __func__); + return -1; + } + if (param_len > 0xffffff) { + pr2ws("%s: param_len value too large for 24 bits\n", __func__); + return -1; + } + wbuf_cdb[1] = (uint8_t)(mode & 0x1f); + wbuf_cdb[1] |= (uint8_t)((m_specific & 0x7) << 5); + wbuf_cdb[2] = (uint8_t)(buffer_id & 0xff); + sg_put_unaligned_be24(buffer_offset, wbuf_cdb + 3); + sg_put_unaligned_be24(param_len, wbuf_cdb + 6); + if (verbose) { + pr2ws(" Write buffer cdb: "); + for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", wbuf_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" Write buffer parameter list%s:\n", + ((param_len > 256) ? " (first 256 bytes)" : "")); + hex2stderr((const uint8_t *)paramp, + ((param_len > 256) ? 256 : param_len), -1); + } + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) { + pr2ws("%s: out of memory\n", __func__); + return -1; + } + set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, "Write buffer", res, SG_NO_DATA_IN, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI UNMAP command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose) +{ + return sg_ll_unmap_v2(sg_fd, false, group_num, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field + * (sbc3r22). Otherwise same as sg_ll_unmap() . */ +int +sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "unmap"; + int k, res, ret, sense_cat, tmout; + unsigned char u_cdb[UNMAP_CMDLEN] = + {UNMAP_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (anchor) + u_cdb[1] |= 0x1; + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + u_cdb[6] = group_num & 0x1f; + sg_put_unaligned_be16((uint16_t)param_len, u_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < UNMAP_CMDLEN; ++k) + pr2ws("%02x ", u_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, u_cdb, sizeof(u_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read block limits"; + int k, ret, res, sense_cat; + unsigned char rl_cdb[READ_BLOCK_LIMITS_CMDLEN] = + {READ_BLOCK_LIMITS_CMD, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_BLOCK_LIMITS_CMDLEN; ++k) + pr2ws("%02x ", rl_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI RECEIVE COPY RESULTS command. Actually cover all current + * uses of opcode 0x84 (Third-party copy IN). Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + int k, res, ret, sense_cat; + unsigned char rcvcopyres_cdb[THIRD_PARTY_COPY_IN_CMDLEN] = + {THIRD_PARTY_COPY_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + char b[64]; + + sg_get_opcode_sa_name(THIRD_PARTY_COPY_IN_CMD, sa, 0, (int)sizeof(b), b); + rcvcopyres_cdb[1] = (unsigned char)(sa & 0x1f); + if (sa <= 4) /* LID1 variants */ + rcvcopyres_cdb[2] = (unsigned char)(list_id); + else if ((sa >= 5) && (sa <= 7)) /* LID4 variants */ + sg_put_unaligned_be32((uint32_t)list_id, rcvcopyres_cdb + 2); + sg_put_unaligned_be32((uint32_t)mx_resp_len, rcvcopyres_cdb + 10); + + if (verbose) { + pr2ws(" %s cdb: ", b); + for (k = 0; k < THIRD_PARTY_COPY_IN_CMDLEN; ++k) + pr2ws("%02x ", rcvcopyres_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(b)))) + return -1; + set_scsi_pt_cdb(ptvp, rcvcopyres_cdb, sizeof(rcvcopyres_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, b, res, mx_resp_len, sense_b, noisy, + verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + + +/* SPC-4 rev 35 and later calls this opcode (0x83) "Third-party copy OUT" + * The original EXTENDED COPY command (now called EXTENDED COPY (LID1)) + * is the only one supported by sg_ll_extended_copy(). See function + * sg_ll_3party_copy_out() for the other service actions ( > 0 ). */ + +/* Invokes a SCSI EXTENDED COPY (LID1) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose) +{ + int k, res, ret, sense_cat; + unsigned char xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] = + {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + const char * opcode_name = "Extended copy (LID1)"; + + xcopy_cdb[1] = (unsigned char)(EXTENDED_COPY_LID1_SA & 0x1f); + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + + if (verbose) { + pr2ws(" %s cdb: ", opcode_name); + for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k) + pr2ws("%02x ", xcopy_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", opcode_name); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(opcode_name)))) + return -1; + set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, opcode_name, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Handles various service actions associated with opcode 0x83 which is + * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID1 and + * LID4), POPULATE TOKEN and WRITE USING TOKEN commands. + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, int group_num, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose) +{ + int k, res, ret, sense_cat, tmout; + unsigned char xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] = + {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + char cname[80]; + + sg_get_opcode_sa_name(THIRD_PARTY_COPY_OUT_CMD, sa, 0, sizeof(cname), + cname); + xcopy_cdb[1] = (unsigned char)(sa & 0x1f); + switch (sa) { + case 0x0: /* XCOPY(LID1) */ + case 0x1: /* XCOPY(LID4) */ + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + break; + case 0x10: /* POPULATE TOKEN (SBC-3) */ + case 0x11: /* WRITE USING TOKEN (SBC-3) */ + sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 6); + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + xcopy_cdb[14] = (unsigned char)(group_num & 0x1f); + break; + case 0x1c: /* COPY OPERATION ABORT */ + sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 2); + break; + default: + pr2ws("%s: unknown service action 0x%x\n", __func__, sa); + return -1; + } + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + + if (verbose) { + pr2ws(" %s cdb: ", cname); + for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k) + pr2ws("%02x ", xcopy_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cname); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cname)))) + return -1; + set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cname, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC). + * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_* + * positive values or -1 -> other errors. Note that CONDITION MET status + * is returned when immed=true and num_blocks can fit in device's cache, + * somewaht strangely, GOOD status (return 0) is returned if num_blocks + * cannot fit in device's cache. If do_seek10==true then does a SEEK(10) + * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10) + * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then + * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */ +int +sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed, + uint64_t lba, uint32_t num_blocks, int group_num, + int timeout_secs, bool noisy, int verbose) +{ + static const char * const cdb10_name_s = "Pre-fetch(10)"; + static const char * const cdb16_name_s = "Pre-fetch(16)"; + static const char * const cdb_seek_name_s = "Seek(10)"; + int k, res, sense_cat, ret, cdb_len, tmout; + const char *cdb_name_s; + unsigned char preFetchCdb[PRE_FETCH16_CMDLEN]; /* all use longest cdb */ + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(preFetchCdb, 0, sizeof(preFetchCdb)); + if (do_seek10) { + if (lba > UINT32_MAX) { + if (verbose) + pr2ws("%s: LBA exceeds 2**32 in %s\n", __func__, + cdb_seek_name_s); + return -1; + } + preFetchCdb[0] = SEEK10_CMD; + cdb_len = SEEK10_CMDLEN; + cdb_name_s = cdb_seek_name_s; + sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2); + } else { + if ((! cdb16) && + ((lba > UINT32_MAX) || (num_blocks > UINT16_MAX))) { + cdb16 = true; + if (noisy || verbose) + pr2ws("%s: do %s due to %s size\n", __func__, cdb16_name_s, + (lba > UINT32_MAX) ? "LBA" : "NUM_BLOCKS"); + } + if (cdb16) { + preFetchCdb[0] = PRE_FETCH16_CMD; + cdb_len = PRE_FETCH16_CMDLEN; + cdb_name_s = cdb16_name_s; + if (immed) + preFetchCdb[1] = 0x2; + sg_put_unaligned_be64(lba, preFetchCdb + 2); + sg_put_unaligned_be32(num_blocks, preFetchCdb + 10); + preFetchCdb[14] = 0x3f & group_num; + } else { + preFetchCdb[0] = PRE_FETCH10_CMD; + cdb_len = PRE_FETCH10_CMDLEN; + cdb_name_s = cdb10_name_s; + if (immed) + preFetchCdb[1] = 0x2; + sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2); + preFetchCdb[6] = 0x3f & group_num; + sg_put_unaligned_be16((uint16_t)num_blocks, preFetchCdb + 7); + } + } + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < cdb_len; ++k) + pr2ws("%02x ", preFetchCdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, preFetchCdb, cdb_len); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + if (0 == res) { + int sstat = get_scsi_pt_status_response(ptvp); + + if (SG_LIB_CAT_CONDITION_MET == sstat) { + ret = SG_LIB_CAT_CONDITION_MET; + if (verbose > 2) + pr2ws("%s: returns SG_LIB_CAT_CONDITION_MET\n", __func__); + goto fini; + } + } + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; +fini: + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/tools/sg_write_buffer/sg_cmds_mmc.c b/tools/sg_write_buffer/sg_cmds_mmc.c new file mode 100644 index 0000000..18f6ae1 --- /dev/null +++ b/tools/sg_write_buffer/sg_cmds_mmc.c @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2008-2018 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_mmc.h" +#include "sg_pt.h" +#include "sg_unaligned.h" + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ + +#define GET_CONFIG_CMD 0x46 +#define GET_CONFIG_CMD_LEN 10 +#define GET_PERFORMANCE_CMD 0xac +#define GET_PERFORMANCE_CMD_LEN 12 +#define SET_CD_SPEED_CMD 0xbb +#define SET_CD_SPEED_CMDLEN 12 +#define SET_STREAMING_CMD 0xb6 +#define SET_STREAMING_CMDLEN 12 + +#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; +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +/* Invokes a SCSI SET CD SPEED command (MMC). + * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int +sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed, + int drv_write_speed, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "set cd speed"; + int res, ret, k, sense_cat; + unsigned char scsCmdBlk[SET_CD_SPEED_CMDLEN] = {SET_CD_SPEED_CMD, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + scsCmdBlk[1] |= (rot_control & 0x3); + sg_put_unaligned_be16((uint16_t)drv_read_speed, scsCmdBlk + 2); + sg_put_unaligned_be16((uint16_t)drv_write_speed, scsCmdBlk + 4); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SET_CD_SPEED_CMDLEN; ++k) + pr2ws("%02x ", scsCmdBlk[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, scsCmdBlk, sizeof(scsCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI GET CONFIGURATION command (MMC-3,4,5). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int +sg_ll_get_config(int sg_fd, int rt, int starting, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "get configuration"; + int res, k, ret, sense_cat; + unsigned char gcCmdBlk[GET_CONFIG_CMD_LEN] = {GET_CONFIG_CMD, 0, 0, 0, + 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((rt < 0) || (rt > 3)) { + pr2ws("Bad rt value: %d\n", rt); + return -1; + } + gcCmdBlk[1] = (rt & 0x3); + if ((starting < 0) || (starting > 0xffff)) { + pr2ws("Bad starting field number: 0x%x\n", starting); + return -1; + } + sg_put_unaligned_be16((uint16_t)starting, gcCmdBlk + 2); + if ((mx_resp_len < 0) || (mx_resp_len > 0xffff)) { + pr2ws("Bad mx_resp_len: 0x%x\n", starting); + return -1; + } + sg_put_unaligned_be16((uint16_t)mx_resp_len, gcCmdBlk + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GET_CONFIG_CMD_LEN; ++k) + pr2ws("%02x ", gcCmdBlk[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, gcCmdBlk, sizeof(gcCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else { + if ((verbose > 2) && (ret > 3)) { + unsigned char * bp; + int len; + + bp = (unsigned char *)resp; + len = sg_get_unaligned_be32(bp + 0); + if (len < 0) + len = 0; + len = (ret < len) ? ret : len; + pr2ws(" %s: response:\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, len, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int +sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba, + int max_num_desc, int ttype, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "get performance"; + int res, k, ret, sense_cat; + unsigned char gpCmdBlk[GET_PERFORMANCE_CMD_LEN] = {GET_PERFORMANCE_CMD, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((data_type < 0) || (data_type > 0x1f)) { + pr2ws("Bad data_type value: %d\n", data_type); + return -1; + } + gpCmdBlk[1] = (data_type & 0x1f); + sg_put_unaligned_be32((uint32_t)starting_lba, gpCmdBlk + 2); + if ((max_num_desc < 0) || (max_num_desc > 0xffff)) { + pr2ws("Bad max_num_desc: 0x%x\n", max_num_desc); + return -1; + } + sg_put_unaligned_be16((uint16_t)max_num_desc, gpCmdBlk + 8); + if ((ttype < 0) || (ttype > 0xff)) { + pr2ws("Bad type: 0x%x\n", ttype); + return -1; + } + gpCmdBlk[10] = (unsigned char)ttype; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GET_PERFORMANCE_CMD_LEN; ++k) + pr2ws("%02x ", gpCmdBlk[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, gpCmdBlk, sizeof(gpCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else { + if ((verbose > 2) && (ret > 3)) { + unsigned char * bp; + int len; + + bp = (unsigned char *)resp; + len = sg_get_unaligned_be32(bp + 0); + if (len < 0) + len = 0; + len = (ret < len) ? ret : len; + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, len, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int +sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "set streaming"; + int k, res, ret, sense_cat; + unsigned char ssCmdBlk[SET_STREAMING_CMDLEN] = + {SET_STREAMING_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + ssCmdBlk[8] = type; + sg_put_unaligned_be16((uint16_t)param_len, ssCmdBlk + 9); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SET_STREAMING_CMDLEN; ++k) + pr2ws("%02x ", ssCmdBlk[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, ssCmdBlk, sizeof(ssCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) { + int os_err = get_scsi_pt_os_err(ptvp); + + if ((os_err > 0) && (os_err < 47)) + ret = SG_LIB_OS_BASE_ERR + os_err; + } else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/tools/sg_write_buffer/sg_io_linux.c b/tools/sg_write_buffer/sg_io_linux.c new file mode 100644 index 0000000..f9f08e7 --- /dev/null +++ b/tools/sg_write_buffer/sg_io_linux.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef SG_LIB_LINUX + +#include "sg_io_linux.h" + + +/* Version 1.07 20160405 */ + +#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; +} + + +void +sg_print_masked_status(int masked_status) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + sg_print_scsi_status(scsi_status); +} + +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", "DID_TRANSPORT_DISRUPTED", + "DID_TRANSPORT_FAILFAST", "DID_TARGET_FAILURE", "DID_NEXUS_FAILURE", + "DID_ALLOC_FAILURE", "DID_MEDIUM_ERROR", +}; + +#define LINUX_HOST_BYTES_SZ \ + (int)(sizeof(linux_host_bytes) / sizeof(linux_host_bytes[0])) + +void +sg_print_host_status(int host_status) +{ + pr2ws("Host_status=0x%02x ", host_status); + if ((host_status < 0) || (host_status >= LINUX_HOST_BYTES_SZ)) + pr2ws("is invalid "); + else + pr2ws("[%s] ", linux_host_bytes[host_status]); +} + +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 + + +void +sg_print_driver_status(int driver_status) +{ + int driv; + const char * driv_cp = "invalid"; + + driv = driver_status & SG_LIB_DRIVER_MASK; + if (driv < LINUX_DRIVER_BYTES_SZ) + driv_cp = linux_driver_bytes[driv]; +#if 0 + sugg = (driver_status & SG_LIB_SUGGEST_MASK) >> 4; + if (sugg < LINUX_DRIVER_SUGGESTS_SZ) + sugg_cp = linux_driver_suggests[sugg]; +#endif + pr2ws("Driver_status=0x%02x", driver_status); + pr2ws(" [%s] ", driv_cp); +} + +/* Returns 1 if no errors found and thus nothing printed; otherwise + prints error/warning (prefix by 'leadin') and returns 0. */ +static int +sg_linux_sense_print(const char * leadin, int scsi_status, int host_status, + int driver_status, const unsigned char * sense_buffer, + int sb_len, bool raw_sinfo) +{ + bool done_leadin = false; + bool done_sense = false; + + scsi_status &= 0x7e; /*sanity */ + if ((0 == scsi_status) && (0 == host_status) && (0 == driver_status)) + return 1; /* No problems */ + if (0 != scsi_status) { + if (leadin) + pr2ws("%s: ", leadin); + done_leadin = true; + pr2ws("SCSI status: "); + sg_print_scsi_status(scsi_status); + pr2ws("\n"); + if (sense_buffer && ((scsi_status == SAM_STAT_CHECK_CONDITION) || + (scsi_status == SAM_STAT_COMMAND_TERMINATED))) { + /* SAM_STAT_COMMAND_TERMINATED is obsolete */ + sg_print_sense(0, sense_buffer, sb_len, raw_sinfo); + done_sense = true; + } + } + if (0 != host_status) { + if (leadin && (! done_leadin)) + pr2ws("%s: ", leadin); + if (done_leadin) + pr2ws("plus...: "); + else + done_leadin = true; + sg_print_host_status(host_status); + pr2ws("\n"); + } + if (0 != driver_status) { + if (done_sense && + (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status))) + return 0; + if (leadin && (! done_leadin)) + pr2ws("%s: ", leadin); + if (done_leadin) + pr2ws("plus...: "); + sg_print_driver_status(driver_status); + pr2ws("\n"); + if (sense_buffer && (! done_sense) && + (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status))) + sg_print_sense(0, sense_buffer, sb_len, raw_sinfo); + } + return 0; +} + +#ifdef SG_IO + +bool +sg_normalize_sense(const struct sg_io_hdr * hp, + struct sg_scsi_sense_hdr * sshp) +{ + if ((NULL == hp) || (0 == hp->sb_len_wr)) { + if (sshp) + memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr)); + return 0; + } + return sg_scsi_normalize_sense(hp->sbp, hp->sb_len_wr, sshp); +} + +/* Returns 1 if no errors found and thus nothing printed; otherwise + returns 0. */ +int +sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp, + bool raw_sinfo) +{ + return sg_linux_sense_print(leadin, hp->status, hp->host_status, + hp->driver_status, hp->sbp, hp->sb_len_wr, + raw_sinfo); +} +#endif + +/* Returns 1 if no errors found and thus nothing printed; otherwise + returns 0. */ +int +sg_chk_n_print(const char * leadin, int masked_status, int host_status, + int driver_status, const unsigned char * sense_buffer, + int sb_len, bool raw_sinfo) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + return sg_linux_sense_print(leadin, scsi_status, host_status, + driver_status, sense_buffer, sb_len, + raw_sinfo); +} + +#ifdef SG_IO +int +sg_err_category3(struct sg_io_hdr * hp) +{ + return sg_err_category_new(hp->status, hp->host_status, + hp->driver_status, hp->sbp, hp->sb_len_wr); +} +#endif + +int +sg_err_category(int masked_status, int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + return sg_err_category_new(scsi_status, host_status, driver_status, + sense_buffer, sb_len); +} + +int +sg_err_category_new(int scsi_status, int host_status, int driver_status, + const unsigned char * sense_buffer, int sb_len) +{ + int masked_driver_status = (SG_LIB_DRIVER_MASK & driver_status); + + scsi_status &= 0x7e; + if ((0 == scsi_status) && (0 == host_status) && + (0 == masked_driver_status)) + return SG_LIB_CAT_CLEAN; + if ((SAM_STAT_CHECK_CONDITION == scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == scsi_status) || + (SG_LIB_DRIVER_SENSE == masked_driver_status)) + return sg_err_category_sense(sense_buffer, sb_len); + if (0 != host_status) { + if ((SG_LIB_DID_NO_CONNECT == host_status) || + (SG_LIB_DID_BUS_BUSY == host_status) || + (SG_LIB_DID_TIME_OUT == host_status)) + return SG_LIB_CAT_TIMEOUT; + if (SG_LIB_DID_NEXUS_FAILURE == host_status) + return SG_LIB_CAT_RES_CONFLICT; + } + if (SG_LIB_DRIVER_TIMEOUT == masked_driver_status) + return SG_LIB_CAT_TIMEOUT; + return SG_LIB_CAT_OTHER; +} + +#endif /* if SG_LIB_LINUX defined */ diff --git a/tools/sg_write_buffer/sg_lib.c b/tools/sg_write_buffer/sg_lib.c new file mode 100644 index 0000000..e31c816 --- /dev/null +++ b/tools/sg_write_buffer/sg_lib.c @@ -0,0 +1,3494 @@ +/* + * Copyright (c) 1999-2018 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. + */ + +/* NOTICE: + * On 5th October 2004 (v1.00) this file name was changed from sg_err.c + * to sg_lib.c and the previous GPL was changed to a FreeBSD license. + * The intention is to maintain this file and the related sg_lib.h file + * as open source and encourage their unencumbered use. + * + * CONTRIBUTIONS: + * This file started out as a copy of SCSI opcodes, sense keys and + * additional sense codes (ASC/ASCQ) kept in the Linux SCSI subsystem + * in the kernel source file: drivers/scsi/constant.c . That file + * bore this notice: "Copyright (C) 1993, 1994, 1995 Eric Youngdale" + * and a GPL notice. + * + * Much of the data in this file is derived from SCSI draft standards + * found at http://www.t10.org with the "SCSI Primary Commands-4" (SPC-4) + * being the central point of reference. + * + * Contributions: + * sense key specific field decoding [Trent Piepho 20031116] + * + */ + +#define _POSIX_C_SOURCE 200809L /* for posix_memalign() */ +#define __STDC_FORMAT_MACROS 1 +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <inttypes.h> +#include <errno.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +/* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */ + +#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */ + +FILE * sg_warnings_strm = NULL; /* would like to default to stderr */ + +#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; +} + +#if defined(__GNUC__) || defined(__clang__) +static int scnpr(char * cp, int cp_max_len, const char * fmt, ...) + __attribute__ ((format (printf, 3, 4))); +#else +static int scnpr(char * cp, int cp_max_len, const char * fmt, ...); +#endif + +/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of + * functions. Returns number of chars placed in cp excluding the + * trailing null char. So for cp_max_len > 0 the return value is always + * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are + * written to cp. Note this means that when cp_max_len = 1, this function + * assumes that cp[0] is the null character and does nothing (and returns + * 0). Linux kernel has a similar function called scnprintf(). */ +static int +scnpr(char * cp, int cp_max_len, const char * fmt, ...) +{ + va_list args; + int n; + + if (cp_max_len < 2) + return 0; + va_start(args, fmt); + n = vsnprintf(cp, cp_max_len, fmt, args); + va_end(args); + return (n < cp_max_len) ? n : (cp_max_len - 1); +} + +/* Simple ASCII printable (does not use locale), includes space and excludes + * DEL (0x7f). */ +static inline int my_isprint(int ch) +{ + return ((ch >= ' ') && (ch < 0x7f)); +} + +/* Searches 'arr' for match on 'value' then 'peri_type'. If matches + 'value' but not 'peri_type' then yields first 'value' match entry. + Last element of 'arr' has NULL 'name'. If no match returns NULL. */ +static const struct sg_lib_value_name_t * +get_value_name(const struct sg_lib_value_name_t * arr, int value, + int peri_type) +{ + const struct sg_lib_value_name_t * vp = arr; + const struct sg_lib_value_name_t * holdp; + + if (peri_type < 0) + peri_type = 0; + for (; vp->name; ++vp) { + if (value == vp->value) { + if (peri_type == vp->peri_dev_type) + return vp; + holdp = vp; + while ((vp + 1)->name && (value == (vp + 1)->value)) { + ++vp; + if (peri_type == vp->peri_dev_type) + return vp; + } + return holdp; + } + } + return NULL; +} + +/* If this function is not called, sg_warnings_strm will be NULL and all users + * (mainly fprintf() ) need to check and substitute stderr as required */ +void +sg_set_warnings_strm(FILE * warnings_strm) +{ + sg_warnings_strm = warnings_strm; +} + +#define CMD_NAME_LEN 128 + +void +sg_print_command(const unsigned char * command) +{ + int k, sz; + char buff[CMD_NAME_LEN]; + + sg_get_command_name(command, 0, CMD_NAME_LEN, buff); + buff[CMD_NAME_LEN - 1] = '\0'; + + pr2ws("%s [", buff); + if (SG_VARIABLE_LENGTH_CMD == command[0]) + sz = command[7] + 8; + else + sz = sg_get_command_size(command[0]); + for (k = 0; k < sz; ++k) + pr2ws("%02x ", command[k]); + pr2ws("]\n"); +} + +void +sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff) +{ + const char * ccp = NULL; + bool unknown = false; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + scsi_status &= 0x7e; /* sanitize as much as possible */ + switch (scsi_status) { + case 0: ccp = "Good"; break; + case 0x2: ccp = "Check Condition"; break; + case 0x4: ccp = "Condition Met"; break; + case 0x8: ccp = "Busy"; break; + case 0x10: ccp = "Intermediate (obsolete)"; break; + case 0x14: ccp = "Intermediate-Condition Met (obsolete)"; break; + case 0x18: ccp = "Reservation Conflict"; break; + case 0x22: ccp = "Command Terminated (obsolete)"; break; + case 0x28: ccp = "Task set Full"; break; + case 0x30: ccp = "ACA Active"; break; + case 0x40: ccp = "Task Aborted"; break; + default: + unknown = true; + break; + } + if (unknown) + scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status); + else + scnpr(buff, buff_len, "%s", ccp); +} + +void +sg_print_scsi_status(int scsi_status) +{ + char buff[128]; + + sg_get_scsi_status_str(scsi_status, sizeof(buff) - 1, buff); + buff[sizeof(buff) - 1] = '\0'; + pr2ws("%s ", buff); +} + +/* Get sense key from sense buffer. If successful returns a sense key value + * between 0 and 15. If sense buffer cannot be decode, returns -1 . */ +int +sg_get_sense_key(const unsigned char * sbp, int sb_len) +{ + if ((NULL == sbp) || (sb_len < 2)) + return -1; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + return (sb_len < 3) ? -1 : (sbp[2] & 0xf); + case 0x72: + case 0x73: + return sbp[1] & 0xf; + default: + return -1; + } +} + +/* Yield string associated with sense_key value. Returns 'buff'. */ +char * +sg_get_sense_key_str(int sense_key, int buff_len, char * buff) +{ + if (1 == buff_len) { + buff[0] = '\0'; + return buff; + } + if ((sense_key >= 0) && (sense_key < 16)) + scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]); + else + scnpr(buff, buff_len, "invalid value: 0x%x", sense_key); + return buff; +} + +/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */ +char * +sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff) +{ + int k, num, rlen; + bool found = false; + struct sg_lib_asc_ascq_t * eip; + struct sg_lib_asc_ascq_range_t * ei2p; + + if (1 == buff_len) { + buff[0] = '\0'; + return buff; + } + for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) { + ei2p = &sg_lib_asc_ascq_range[k]; + if ((ei2p->asc == asc) && + (ascq >= ei2p->ascq_min) && + (ascq <= ei2p->ascq_max)) { + found = true; + num = scnpr(buff, buff_len, "Additional sense: "); + rlen = buff_len - num; + scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq); + } + } + if (found) + return buff; + + for (k = 0; sg_lib_asc_ascq[k].text; ++k) { + eip = &sg_lib_asc_ascq[k]; + if (eip->asc == asc && + eip->ascq == ascq) { + found = true; + scnpr(buff, buff_len, "Additional sense: %s", eip->text); + } + } + if (! found) { + if (asc >= 0x80) + scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x " + "(hex)", asc, ascq); + else if (ascq >= 0x80) + scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification " + "ASCQ=%02x (hex)", asc, ascq); + else + scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq); + } + return buff; +} + +/* Attempt to find the first SCSI sense data descriptor that matches the + * given 'desc_type'. If found return pointer to start of sense data + * descriptor; otherwise (including fixed format sense data) returns NULL. */ +const unsigned char * +sg_scsi_sense_desc_find(const unsigned char * sbp, int sb_len, + int desc_type) +{ + int add_sb_len, add_d_len, desc_len, k; + const unsigned char * descp; + + if ((sb_len < 8) || (0 == (add_sb_len = sbp[7]))) + return NULL; + if ((sbp[0] < 0x72) || (sbp[0] > 0x73)) + return NULL; + add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8); + descp = &sbp[8]; + for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) { + descp += desc_len; + add_d_len = (k < (add_sb_len - 1)) ? descp[1]: -1; + desc_len = add_d_len + 2; + if (descp[0] == desc_type) + return descp; + if (add_d_len < 0) /* short descriptor ?? */ + break; + } + return NULL; +} + +/* Returns true if valid bit set, false if valid bit clear. Irrespective the + * information field is written out via 'info_outp' (except when it is + * NULL). Handles both fixed and descriptor sense formats. */ +bool +sg_get_sense_info_fld(const unsigned char * sbp, int sb_len, + uint64_t * info_outp) +{ + const unsigned char * bp; + uint64_t ull; + + if (info_outp) + *info_outp = 0; + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (info_outp) + *info_outp = sg_get_unaligned_be32(sbp + 3); + return !!(sbp[0] & 0x80); + case 0x72: + case 0x73: + bp = sg_scsi_sense_desc_find(sbp, sb_len, 0 /* info desc */); + if (bp && (0xa == bp[1])) { + ull = sg_get_unaligned_be64(bp + 4); + if (info_outp) + *info_outp = ull; + return !!(bp[2] & 0x80); /* since spc3r23 should be set */ + } else + return false; + default: + return false; + } +} + +/* Returns true if fixed format or command specific information descriptor + * is found in the descriptor sense; else false. If available the command + * specific information field (4 byte integer in fixed format, 8 byte + * integer in descriptor format) is written out via 'cmd_spec_outp'. + * Handles both fixed and descriptor sense formats. */ +bool +sg_get_sense_cmd_spec_fld(const unsigned char * sbp, int sb_len, + uint64_t * cmd_spec_outp) +{ + const unsigned char * bp; + + if (cmd_spec_outp) + *cmd_spec_outp = 0; + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (cmd_spec_outp) + *cmd_spec_outp = sg_get_unaligned_be32(sbp + 8); + return true; + case 0x72: + case 0x73: + bp = sg_scsi_sense_desc_find(sbp, sb_len, + 1 /* command specific info desc */); + if (bp && (0xa == bp[1])) { + if (cmd_spec_outp) + *cmd_spec_outp = sg_get_unaligned_be64(bp + 4); + return true; + } else + return false; + default: + return false; + } +} + +/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set. + * In descriptor format if the stream commands descriptor not found + * then returns false. Writes true or false corresponding to these bits to + * the last three arguments if they are non-NULL. */ +bool +sg_get_sense_filemark_eom_ili(const unsigned char * sbp, int sb_len, + bool * filemark_p, bool * eom_p, bool * ili_p) +{ + const unsigned char * bp; + + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (sbp[2] & 0xe0) { + if (filemark_p) + *filemark_p = !!(sbp[2] & 0x80); + if (eom_p) + *eom_p = !!(sbp[2] & 0x40); + if (ili_p) + *ili_p = !!(sbp[2] & 0x20); + return true; + } else + return false; + case 0x72: + case 0x73: + /* Look for stream commands sense data descriptor */ + bp = sg_scsi_sense_desc_find(sbp, sb_len, 4); + if (bp && (bp[1] >= 2)) { + if (bp[3] & 0xe0) { + if (filemark_p) + *filemark_p = !!(bp[3] & 0x80); + if (eom_p) + *eom_p = !!(bp[3] & 0x40); + if (ili_p) + *ili_p = !!(bp[3] & 0x20); + return true; + } + } + return false; + default: + return false; + } +} + +/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also + * returns true if progress indication sense data descriptor found. Places + * progress field from sense data where progress_outp points. If progress + * field is not available returns false and *progress_outp is unaltered. + * Handles both fixed and descriptor sense formats. + * Hint: if true is returned *progress_outp may be multiplied by 100 then + * divided by 65536 to get the percentage completion. */ +bool +sg_get_sense_progress_fld(const unsigned char * sbp, int sb_len, + int * progress_outp) +{ + const unsigned char * bp; + int sk, sk_pr; + + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + sk = (sbp[2] & 0xf); + if ((sb_len < 18) || + ((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk))) + return false; + if (sbp[15] & 0x80) { /* SKSV bit set */ + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(sbp + 16); + return true; + } else + return false; + case 0x72: + case 0x73: + /* sense key specific progress (0x2) or progress descriptor (0xa) */ + sk = (sbp[1] & 0xf); + sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk); + if (sk_pr && ((bp = sg_scsi_sense_desc_find(sbp, sb_len, 2))) && + (0x6 == bp[1]) && (0x80 & bp[4])) { + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(bp + 5); + return true; + } else if (((bp = sg_scsi_sense_desc_find(sbp, sb_len, 0xa))) && + ((0x6 == bp[1]))) { + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(bp + 6); + return true; + } else + return false; + default: + return false; + } +} + +char * +sg_get_pdt_str(int pdt, int buff_len, char * buff) +{ + if ((pdt < 0) || (pdt > 31)) + scnpr(buff, buff_len, "bad pdt"); + else + scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]); + return buff; +} + +int +sg_lib_pdt_decay(int pdt) +{ + if ((pdt < 0) || (pdt > 31)) + return 0; + return sg_lib_pdt_decay_arr[pdt]; +} + +char * +sg_get_trans_proto_str(int tpi, int buff_len, char * buff) +{ + if ((tpi < 0) || (tpi > 15)) + scnpr(buff, buff_len, "bad tpi"); + else + scnpr(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]); + return buff; +} + +#define TRANSPORT_ID_MIN_LEN 24 + +char * +sg_decode_transportid_str(const char * lip, unsigned char * bp, int bplen, + bool only_one, int blen, char * b) +{ + int proto_id, num, k, n, normal_len, tpid_format; + uint64_t ull; + int bump; + + if ((NULL == b) || (blen < 1)) + return b; + else if (1 == blen) { + b[0] = '\0'; + return b; + } + if (NULL == lip) + lip = ""; + bump = TRANSPORT_ID_MIN_LEN; /* should be overwritten in all loop paths */ + for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) { + if ((k > 0) && only_one) + break; + if ((bplen < 24) || (0 != (bplen % 4))) + n += scnpr(b + n, blen - n, "%sTransport Id short or not " + "multiple of 4 [length=%d]:\n", lip, blen); + else + n += scnpr(b + n, blen - n, "%sTransport Id of initiator:\n", + lip); + tpid_format = ((bp[0] >> 6) & 0x3); + proto_id = (bp[0] & 0xf); + normal_len = (bplen > TRANSPORT_ID_MIN_LEN) ? + TRANSPORT_ID_MIN_LEN : bplen; + switch (proto_id) { + case TPROTO_FCP: /* Fibre channel */ + n += scnpr(b + n, blen - n, "%s FCP-2 World Wide Name:\n", lip); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(bp + 8, 8, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SPI: /* Scsi Parallel Interface, obsolete */ + n += scnpr(b + n, blen - n, "%s Parallel SCSI initiator SCSI " + "address: 0x%x\n", lip, sg_get_unaligned_be16(bp + 2)); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += scnpr(b + n, blen - n, "%s relative port number (of " + "corresponding target): 0x%x\n", lip, + sg_get_unaligned_be16(bp + 6)); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SSA: + n += scnpr(b + n, blen - n, "%s SSA (transport id not " + "defined):\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_1394: /* IEEE 1394 */ + n += scnpr(b + n, blen - n, "%s IEEE 1394 EUI-64 name:\n", lip); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(&bp[8], 8, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SRP: /* SCSI over RDMA */ + n += scnpr(b + n, blen - n, "%s RDMA initiator port " + "identifier:\n", lip); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(bp + 8, 16, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ISCSI: + n += scnpr(b + n, blen - n, "%s iSCSI ", lip); + num = sg_get_unaligned_be16(bp + 2); + if (0 == tpid_format) + n += scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]); + else if (1 == tpid_format) + n += scnpr(b + n, blen - n, "world wide unique port id: " + "%.*s\n", num, &bp[4]); + else { + n += scnpr(b + n, blen - n, " [Unexpected TPID format: " + "%d]\n", tpid_format); + n += hex2str(bp, num + 4, lip, 0, blen - n, b + n); + } + bump = (((num + 4) < TRANSPORT_ID_MIN_LEN) ? + TRANSPORT_ID_MIN_LEN : num + 4); + break; + case TPROTO_SAS: + ull = sg_get_unaligned_be64(bp + 4); + n += scnpr(b + n, blen - n, "%s SAS address: 0x%" PRIx64 "\n", + lip, ull); + if (0 != tpid_format) + n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ADT: /* no TransportID defined by T10 yet */ + n += scnpr(b + n, blen - n, "%s ADT:\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ATA: /* no TransportID defined by T10 yet */ + n += scnpr(b + n, blen - n, "%s ATAPI:\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_UAS: /* no TransportID defined by T10 yet */ + n += scnpr(b + n, blen - n, "%s UAS:\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SOP: + n += scnpr(b + n, blen - n, "%s SOP ", lip); + num = sg_get_unaligned_be16(bp + 2); + if (0 == tpid_format) + n += scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num); + else { + n += scnpr(b + n, blen - n, " [Unexpected TPID format: " + "%d]\n", tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + } + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_PCIE: /* no TransportID defined by T10 yet */ + n += scnpr(b + n, blen - n, "%s PCIE:\n", lip); + n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_NONE: /* no TransportID defined by T10 */ + n += scnpr(b + n, blen - n, "%s No specified protocol\n", lip); + /* n += hex2str(bp, ((bplen > 24) ? 24 : bplen), + * lip, 0, blen - n, b + n); */ + bump = TRANSPORT_ID_MIN_LEN; + break; + default: + n += scnpr(b + n, blen - n, "%s unknown protocol id=0x%x " + "TPID format=%d\n", lip, proto_id, tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + } + } + return b; +} + + +static const char * desig_code_set_str_arr[] = +{ + "Reserved [0x0]", + "Binary", + "ASCII", + "UTF-8", + "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]", + "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]", + "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]", +}; + +const char * +sg_get_desig_code_set_str(int val) +{ + if ((val >= 0) && (val < 16)) + return desig_code_set_str_arr[val]; + else + return NULL; +} + +static const char * desig_assoc_str_arr[] = +{ + "Addressed logical unit", + "Target port", /* that received request; unless SCSI ports VPD */ + "Target device that contains addressed lu", + "Reserved [0x3]", +}; + +const char * +sg_get_desig_assoc_str(int val) +{ + if ((val >= 0) && (val < 4)) + return desig_assoc_str_arr[val]; + else + return NULL; +} + +static const char * desig_type_str_arr[] = +{ + "vendor specific [0x0]", + "T10 vendor identification", + "EUI-64 based", + "NAA", + "Relative target port", + "Target port group", /* spc4r09: _primary_ target port group */ + "Logical unit group", + "MD5 logical unit identifier", + "SCSI name string", + "Protocol specific port identifier", /* spc4r36 */ + "UUID identifier", /* spc5r08 */ + "Reserved [0xb]", + "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]", +}; + +const char * +sg_get_desig_type_str(int val) +{ + if ((val >= 0) && (val < 16)) + return desig_type_str_arr[val]; + else + return NULL; +} + +int +sg_get_designation_descriptor_str(const char * lip, const unsigned char * ddp, + int dd_len, bool print_assoc, bool do_long, + int blen, char * b) +{ + int m, p_id, piv, c_set, assoc, desig_type, ci_off, c_id, d_id, naa; + int vsi, k, n, dlen; + const unsigned char * ip; + uint64_t vsei; + uint64_t id_ext; + char e[64]; + const char * cp; + + n = 0; + if (NULL == lip) + lip = ""; + if (dd_len < 4) { + n += scnpr(b + n, blen - n, "%sdesignator desc too short: got " + "length of %d want 4 or more\n", lip, dd_len); + return n; + } + dlen = ddp[3]; + if (dlen > (dd_len - 4)) { + n += scnpr(b + n, blen - n, "%sdesignator too long: says it is %d " + "bytes, but given %d bytes\n", lip, dlen, dd_len - 4); + return n; + } + ip = ddp + 4; + p_id = ((ddp[0] >> 4) & 0xf); + c_set = (ddp[0] & 0xf); + piv = ((ddp[1] & 0x80) ? 1 : 0); + assoc = ((ddp[1] >> 4) & 0x3); + desig_type = (ddp[1] & 0xf); + if (print_assoc && ((cp = sg_get_desig_assoc_str(assoc)))) + n += scnpr(b + n, blen - n, "%s %s:\n", lip, cp); + n += scnpr(b + n, blen - n, "%s designator type: ", lip); + cp = sg_get_desig_type_str(desig_type); + if (cp) + n += scnpr(b + n, blen - n, "%s", cp); + n += scnpr(b + n, blen - n, ", code set: "); + cp = sg_get_desig_code_set_str(c_set); + if (cp) + n += scnpr(b + n, blen - n, "%s", cp); + n += scnpr(b + n, blen - n, "\n"); + if (piv && ((1 == assoc) || (2 == assoc))) + n += scnpr(b + n, blen - n, "%s transport: %s\n", lip, + sg_get_trans_proto_str(p_id, sizeof(e), e)); + /* printf(" associated with the %s\n", sdparm_assoc_arr[assoc]); */ + switch (desig_type) { + case 0: /* vendor specific */ + k = 0; + if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */ + for (k = 0; (k < dlen) && my_isprint(ip[k]); ++k) + ; + if (k >= dlen) + k = 1; + } + if (k) + n += scnpr(b + n, blen - n, "%s vendor specific: %.*s\n", + lip, dlen, ip); + else { + n += scnpr(b + n, blen - n, "%s vendor specific:\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + } + break; + case 1: /* T10 vendor identification */ + n += scnpr(b + n, blen - n, "%s vendor id: %.8s\n", lip, ip); + if (dlen > 8) { + if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */ + n += scnpr(b + n, blen - n, "%s vendor specific: " + "%.*s\n", lip, dlen - 8, ip + 8); + } else { + n += scnpr(b + n, blen - n, "%s vendor specific: 0x", + lip); + for (m = 8; m < dlen; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + } + } + break; + case 2: /* EUI-64 based */ + if (! do_long) { + if ((8 != dlen) && (12 != dlen) && (16 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expect 8, 12 and 16 " + "byte EUI, got %d >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < dlen; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + break; + } + n += scnpr(b + n, blen - n, "%s EUI-64 based %d byte " + "identifier\n", lip, dlen); + if (1 != c_set) { + n += scnpr(b + n, blen - n, "%s << expected binary code_set " + "(1) >>\n", lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + ci_off = 0; + if (16 == dlen) { + ci_off = 8; + id_ext = sg_get_unaligned_be64(ip); + n += scnpr(b + n, blen - n, "%s Identifier extension: 0x%" + PRIx64 "\n", lip, id_ext); + } else if ((8 != dlen) && (12 != dlen)) { + n += scnpr(b + n, blen - n, "%s << can only decode 8, 12 " + "and 16 byte ids >>\n", lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = sg_get_unaligned_be24(ip + ci_off); + n += scnpr(b + n, blen - n, "%s IEEE Company_id: 0x%x\n", lip, + c_id); + vsei = 0; + for (m = 0; m < 5; ++m) { + if (m > 0) + vsei <<= 8; + vsei |= ip[ci_off + 3 + m]; + } + n += scnpr(b + n, blen - n, "%s Vendor Specific Extension " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + if (12 == dlen) { + d_id = sg_get_unaligned_be32(ip + 8); + n += scnpr(b + n, blen - n, "%s Directory ID: 0x%x\n", lip, + d_id); + } + break; + case 3: /* NAA <n> */ + if (1 != c_set) { + n += scnpr(b + n, blen - n, "%s << unexpected code set %d " + "for NAA >>\n", lip, c_set); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + naa = (ip[0] >> 4) & 0xff; + switch (naa) { + case 2: /* NAA 2: IEEE Extended */ + if (8 != dlen) { + n += scnpr(b + n, blen - n, "%s << unexpected NAA 2 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = (((ip[0] & 0xf) << 8) | ip[1]); + c_id = sg_get_unaligned_be24(ip + 2); + vsi = sg_get_unaligned_be24(ip + 5); + if (do_long) { + n += scnpr(b + n, blen - n, "%s NAA 2, vendor specific " + "identifier A: 0x%x\n", lip, d_id); + n += scnpr(b + n, blen - n, "%s IEEE Company_id: 0x%x\n", + lip, c_id); + n += scnpr(b + n, blen - n, "%s vendor specific " + "identifier B: 0x%x\n", lip, vsi); + n += scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "]\n"); + } + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + break; + case 3: /* NAA 3: Locally assigned */ + if (8 != dlen) { + n += scnpr(b + n, blen - n, "%s << unexpected NAA 3 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + if (do_long) + n += scnpr(b + n, blen - n, "%s NAA 3, Locally " + "assigned:\n", lip); + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + break; + case 5: /* NAA 5: IEEE Registered */ + if (8 != dlen) { + n += scnpr(b + n, blen - n, "%s << unexpected NAA 5 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | + (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); + vsei = ip[3] & 0xf; + for (m = 1; m < 5; ++m) { + vsei <<= 8; + vsei |= ip[3 + m]; + } + if (do_long) { + n += scnpr(b + n, blen - n, "%s NAA 5, IEEE " + "Company_id: 0x%x\n", lip, c_id); + n += scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + n += scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "]\n"); + } else { + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + } + break; + case 6: /* NAA 6: IEEE Registered extended */ + if (16 != dlen) { + n += scnpr(b + n, blen - n, "%s << unexpected NAA 6 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | + (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); + vsei = ip[3] & 0xf; + for (m = 1; m < 5; ++m) { + vsei <<= 8; + vsei |= ip[3 + m]; + } + if (do_long) { + n += scnpr(b + n, blen - n, "%s NAA 6, IEEE " + "Company_id: 0x%x\n", lip, c_id); + n += scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + vsei = sg_get_unaligned_be64(ip + 8); + n += scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier Extension: 0x%" PRIx64 "\n", lip, + vsei); + n += scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 16; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "]\n"); + } else { + n += scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 16; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); + n += scnpr(b + n, blen - n, "\n"); + } + break; + default: + n += scnpr(b + n, blen - n, "%s << unexpected NAA [0x%x] " + ">>\n", lip, naa); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + break; + case 4: /* Relative target port */ + if ((1 != c_set) || (1 != assoc) || (4 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set, target port association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, "", 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += scnpr(b + n, blen - n, "%s Relative target port: 0x%x\n", + lip, d_id); + break; + case 5: /* (primary) Target port group */ + if ((1 != c_set) || (1 != assoc) || (4 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set, target port association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += scnpr(b + n, blen - n, "%s Target port group: 0x%x\n", lip, + d_id); + break; + case 6: /* Logical unit group */ + if ((1 != c_set) || (0 != assoc) || (4 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set, logical unit association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += scnpr(b + n, blen - n, "%s Logical unit group: 0x%x\n", lip, + d_id); + break; + case 7: /* MD5 logical unit identifier */ + if ((1 != c_set) || (0 != assoc)) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set, logical unit association >>\n", lip); + n += hex2str(ip, dlen, "", 1, blen - n, b + n); + break; + } + n += scnpr(b + n, blen - n, "%s MD5 logical unit identifier:\n", + lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + case 8: /* SCSI name string */ + if (3 != c_set) { /* accept ASCII as subset of UTF-8 */ + if (2 == c_set) { + if (do_long) + n += scnpr(b + n, blen - n, "%s << expected UTF-8, " + "use ASCII >>\n", lip); + } else { + n += scnpr(b + n, blen - n, "%s << expected UTF-8 " + "code_set >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + } + n += scnpr(b + n, blen - n, "%s SCSI name string:\n", lip); + /* does %s print out UTF-8 ok?? + * Seems to depend on the locale. Looks ok here with my + * locale setting: en_AU.UTF-8 + */ + n += scnpr(b + n, blen - n, "%s %.*s\n", lip, dlen, + (const char *)ip); + break; + case 9: /* Protocol specific port identifier */ + /* added in spc4r36, PIV must be set, proto_id indicates */ + /* whether UAS (USB) or SOP (PCIe) or ... */ + if (! piv) + n += scnpr(b + n, blen - n, " %s >>>> Protocol specific " + "port identifier expects protocol\n" + "%s identifier to be valid and it is not\n", + lip, lip); + if (TPROTO_UAS == p_id) { + n += scnpr(b + n, blen - n, "%s USB device address: 0x%x\n", + lip, 0x7f & ip[0]); + n += scnpr(b + n, blen - n, "%s USB interface number: " + "0x%x\n", lip, ip[2]); + } else if (TPROTO_SOP == p_id) { + n += scnpr(b + n, blen - n, "%s PCIe routing ID, bus " + "number: 0x%x\n", lip, ip[0]); + n += scnpr(b + n, blen - n, "%s function number: 0x%x\n", + lip, ip[1]); + n += scnpr(b + n, blen - n, "%s [or device number: " + "0x%x, function number: 0x%x]\n", lip, + (0x1f & (ip[1] >> 3)), 0x7 & ip[1]); + } else + n += scnpr(b + n, blen - n, "%s >>>> unexpected protocol " + "indentifier: %s\n%s with Protocol specific " + "port identifier\n", lip, + sg_get_trans_proto_str(p_id, sizeof(e), e), lip); + break; + case 0xa: /* UUID identifier */ + if (1 != c_set) { + n += scnpr(b + n, blen - n, "%s << expected binary " + "code_set >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + if ((1 != ((ip[0] >> 4) & 0xf)) || (18 != dlen)) { + n += scnpr(b + n, blen - n, "%s << expected locally " + "assigned UUID, 16 bytes long >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + n += scnpr(b + n, blen - n, "%s Locally assigned UUID: ", lip); + for (m = 0; m < 16; ++m) { + if ((4 == m) || (6 == m) || (8 == m) || (10 == m)) + n += scnpr(b + n, blen - n, "-"); + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]); + } + n += scnpr(b + n, blen - n, "\n"); + if (do_long) { + n += scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 16; ++m) + n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]); + n += scnpr(b + n, blen - n, "]\n"); + } + break; + default: /* reserved */ + n += scnpr(b + n, blen - n, "%s reserved designator=0x%x\n", lip, + desig_type); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + return n; +} + +static int +decode_sks(const char * lip, const unsigned char * descp, int add_d_len, + int sense_key, bool * processedp, int blen, char * b) +{ + int progress, pr, rem, n; + + n = 0; + if (NULL == lip) + lip = ""; + switch (sense_key) { + case SPC_SK_ILLEGAL_REQUEST: + if (add_d_len < 6) { + n += scnpr(b + n, blen - n, "Field pointer: "); + goto too_short; + } + /* abbreviate to fit on one line */ + n += scnpr(b + n, blen - n, "Field pointer:\n"); + n += scnpr(b + n, blen - n, "%s Error in %s: byte %d", lip, + (descp[4] & 0x40) ? "Command" : + "Data parameters", + sg_get_unaligned_be16(descp + 5)); + if (descp[4] & 0x08) { + n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07); + } else + n += scnpr(b + n, blen - n, "\n"); + break; + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_RECOVERED_ERROR: + n += scnpr(b + n, blen - n, "Actual retry count: "); + if (add_d_len < 6) + goto too_short; + n += scnpr(b + n, blen - n,"%u\n", sg_get_unaligned_be16(descp + 5)); + break; + case SPC_SK_NO_SENSE: + case SPC_SK_NOT_READY: + n += scnpr(b + n, blen - n, "Progress indication: "); + if (add_d_len < 6) + goto too_short; + progress = sg_get_unaligned_be16(descp + 5); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + n += scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem); + break; + case SPC_SK_COPY_ABORTED: + n += scnpr(b + n, blen - n, "Segment pointer:\n"); + if (add_d_len < 6) + goto too_short; + n += scnpr(b + n, blen - n, "%s Relative to start of %s, byte " + "%d", lip, (descp[4] & 0x20) ? "segment descriptor" : + "parameter list", + sg_get_unaligned_be16(descp + 5)); + if (descp[4] & 0x08) + n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07); + else + n += scnpr(b + n, blen - n, "\n"); + break; + case SPC_SK_UNIT_ATTENTION: + n += scnpr(b + n, blen - n, "Unit attention condition queue:\n"); + n += scnpr(b + n, blen - n, "%s overflow flag is %d\n", lip, + !!(descp[4] & 0x1)); + break; + default: + n += scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n", + sense_key); + *processedp = false; + break; + } + return n; + +too_short: + n += scnpr(b + n, blen - n, "%s\n", " >> descriptor too short"); + *processedp = false; + return n; +} + +#define TPGS_STATE_OPTIMIZED 0x0 +#define TPGS_STATE_NONOPTIMIZED 0x1 +#define TPGS_STATE_STANDBY 0x2 +#define TPGS_STATE_UNAVAILABLE 0x3 +#define TPGS_STATE_OFFLINE 0xe +#define TPGS_STATE_TRANSITIONING 0xf + +static int +decode_tpgs_state(int st, char * b, int blen) +{ + switch (st) { + case TPGS_STATE_OPTIMIZED: + return scnpr(b, blen, "active/optimized"); + case TPGS_STATE_NONOPTIMIZED: + return scnpr(b, blen, "active/non optimized"); + case TPGS_STATE_STANDBY: + return scnpr(b, blen, "standby"); + case TPGS_STATE_UNAVAILABLE: + return scnpr(b, blen, "unavailable"); + case TPGS_STATE_OFFLINE: + return scnpr(b, blen, "offline"); + case TPGS_STATE_TRANSITIONING: + return scnpr(b, blen, "transitioning between states"); + default: + return scnpr(b, blen, "unknown: 0x%x", st); + } +} + +static int +uds_referral_descriptor_str(char * b, int blen, const unsigned char * dp, + int alen, const char * lip) +{ + int n = 0; + int dlen = alen - 2; + int k, j, g, f, tpgd; + const unsigned char * tp; + uint64_t ull; + char c[40]; + + if (NULL == lip) + lip = ""; + n += scnpr(b + n, blen - n, "%s Not all referrals: %d\n", lip, + !!(dp[2] & 0x1)); + dp += 4; + for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) { + tpgd = dp[3]; + g = (tpgd * 4) + 20; + n += scnpr(b + n, blen - n, "%s Descriptor %d\n", lip, f); + if ((k + g) > dlen) { + n += scnpr(b + n, blen - n, "%s truncated descriptor, " + "stop\n", lip); + return n; + } + ull = sg_get_unaligned_be64(dp + 4); + n += scnpr(b + n, blen - n, "%s first uds LBA: 0x%" PRIx64 "\n", + lip, ull); + ull = sg_get_unaligned_be64(dp + 12); + n += scnpr(b + n, blen - n, "%s last uds LBA: 0x%" PRIx64 "\n", + lip, ull); + for (j = 0; j < tpgd; ++j) { + tp = dp + 20 + (j * 4); + decode_tpgs_state(tp[0] & 0xf, c, sizeof(c)); + n += scnpr(b + n, blen - n, "%s tpg: %d state: %s\n", + lip, sg_get_unaligned_be16(tp + 2), c); + } + } + return n; +} + +static const char * dd_usage_reason_str_arr[] = { + "Unknown", + "resend this and further commands to:", + "resend this command to:", + "new subsiduary lu added to this administrative lu:", + "administrative lu associated with a preferred binding:", + }; + + +/* Decode descriptor format sense descriptors (assumes sense buffer is + * in descriptor format) */ +int +sg_get_sense_descriptors_str(const char * lip, const unsigned char * sbp, + int sb_len, int blen, char * b) +{ + int add_sb_len, add_d_len, desc_len, k, j, sense_key; + int n, progress, pr, rem; + bool processed; + const unsigned char * descp; + const char * dtsp = " >> descriptor too short"; + const char * eccp = "Extended copy command"; + const char * ddp = "destination device"; + char z[64]; + + if ((NULL == b) || (blen <= 0)) + return 0; + b[0] = '\0'; + if (lip) + scnpr(z, sizeof(z), "%.60s ", lip); + else + scnpr(z, sizeof(z), " "); + if ((sb_len < 8) || (0 == (add_sb_len = sbp[7]))) + return 0; + add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8); + sense_key = (sbp[1] & 0xf); + + for (descp = (sbp + 8), k = 0, n = 0; + (k < add_sb_len) && (n < blen); + k += desc_len, descp += desc_len) { + add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1; + if ((k + add_d_len + 2) > add_sb_len) + add_d_len = add_sb_len - k - 2; + desc_len = add_d_len + 2; + n += scnpr(b + n, blen - n, "%s Descriptor type: ", lip); + processed = true; + switch (descp[0]) { + case 0: + n += scnpr(b + n, blen - n, "Information: "); + if ((add_d_len >= 10) && (0x80 & descp[2])) { + n += scnpr(b + n, blen - n, "0x"); + for (j = 0; j < 8; ++j) + n += scnpr(b + n, blen - n, "%02x", descp[4 + j]); + n += scnpr(b + n, blen - n, "\n"); + } else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 1: + n += scnpr(b + n, blen - n, "Command specific: "); + if (add_d_len >= 10) { + n += scnpr(b + n, blen - n, "0x"); + for (j = 0; j < 8; ++j) + n += scnpr(b + n, blen - n, "%02x", descp[4 + j]); + n += scnpr(b + n, blen - n, "\n"); + } else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 2: /* Sense Key Specific */ + n += scnpr(b + n, blen - n, "Sense key specific: "); + n += decode_sks(lip, descp, add_d_len, sense_key, &processed, + blen - n, b + n); + break; + case 3: + n += scnpr(b + n, blen - n, "Field replaceable unit code: "); + if (add_d_len >= 2) + n += scnpr(b + n, blen - n, "0x%x\n", descp[3]); + else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 4: + n += scnpr(b + n, blen - n, "Stream commands: "); + if (add_d_len >= 2) { + if (descp[3] & 0x80) + n += scnpr(b + n, blen - n, "FILEMARK"); + if (descp[3] & 0x40) + n += scnpr(b + n, blen - n, "End Of Medium (EOM)"); + if (descp[3] & 0x20) + n += scnpr(b + n, blen - n, "Incorrect Length Indicator " + "(ILI)"); + n += scnpr(b + n, blen - n, "\n"); + } else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 5: + n += scnpr(b + n, blen - n, "Block commands: "); + if (add_d_len >= 2) + n += scnpr(b + n, blen - n, "Incorrect Length Indicator " + "(ILI) %s\n", (descp[3] & 0x20) ? "set" : "clear"); + else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 6: + n += scnpr(b + n, blen - n, "OSD object identification\n"); + processed = false; + break; + case 7: + n += scnpr(b + n, blen - n, "OSD response integrity check " + "value\n"); + processed = false; + break; + case 8: + n += scnpr(b + n, blen - n, "OSD attribute identification\n"); + processed = false; + break; + case 9: /* this is defined in SAT (SAT-2) */ + n += scnpr(b + n, blen - n, "ATA Status Return: "); + if (add_d_len >= 12) { + int extend, count; + + extend = descp[2] & 1; + count = descp[5] + (extend ? (descp[4] << 8) : 0); + n += scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s" + " count=0x%x ", extend, descp[3], lip, + count); + if (extend) + n += scnpr(b + n, blen - n, + "lba=0x%02x%02x%02x%02x%02x%02x ", + descp[10], descp[8], descp[6], descp[11], + descp[9], descp[7]); + else + n += scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ", + descp[11], descp[9], descp[7]); + n += scnpr(b + n, blen - n, "device=0x%x status=0x%x\n", + descp[12], descp[13]); + } else { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 0xa: + /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */ + n += scnpr(b + n, blen - n, "Another progress indication: "); + if (add_d_len < 6) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + progress = sg_get_unaligned_be16(descp + 6); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + n += scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem); + n += scnpr(b + n, blen - n, "%s [sense_key=0x%x " + "asc,ascq=0x%x,0x%x]\n", lip, descp[2], descp[3], + descp[4]); + break; + case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */ + n += scnpr(b + n, blen - n, "User data segment referral: "); + if (add_d_len < 2) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + n += scnpr(b + n, blen - n, "\n"); + n += uds_referral_descriptor_str(b + n, blen - n, descp, + add_d_len, lip); + break; + case 0xc: /* Added in SPC-4 rev 28 */ + n += scnpr(b + n, blen - n, "Forwarded sense data\n"); + if (add_d_len < 2) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + n += scnpr(b + n, blen - n, "%s FSDT: %s\n", lip, + (descp[2] & 0x80) ? "set" : "clear"); + j = descp[2] & 0xf; + n += scnpr(b + n, blen - n, "%s Sense data source: ", lip); + switch (j) { + case 0: + n += scnpr(b + n, blen - n, "%s source device\n", eccp); + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + n += scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1); + break; + default: + n += scnpr(b + n, blen - n, "unknown [%d]\n", j); + } + { + char c[480]; + + sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c); + c[sizeof(c) - 1] = '\0'; + n += scnpr(b + n, blen - n, "%s Forwarded status: %s\n", + lip, c); + if (add_d_len > 2) { + /* recursing; hope not to get carried away */ + n += scnpr(b + n, blen - n, "%s vvvvvvvvvvvvvvvv\n", lip); + sg_get_sense_str(lip, descp + 4, add_d_len - 2, false, + sizeof(c), c); + n += scnpr(b + n, blen - n, "%s", c); + n += scnpr(b + n, blen - n, "%s ^^^^^^^^^^^^^^^^\n", lip); + } + } + break; + case 0xd: /* Added in SBC-3 rev 36d */ + /* this descriptor combines descriptors 0, 1, 2 and 3 */ + n += scnpr(b + n, blen - n, "Direct-access block device\n"); + if (add_d_len < 28) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + if (0x20 & descp[2]) + n += scnpr(b + n, blen - n, "%s ILI (incorrect length " + "indication) set\n", lip); + if (0x80 & descp[4]) { + n += scnpr(b + n, blen - n, "%s Sense key specific: ", + lip); + n += decode_sks(lip, descp, add_d_len, sense_key, &processed, + blen - n, b + n); + } + n += scnpr(b + n, blen - n, "%s Field replaceable unit code: " + "0x%x\n", lip, descp[7]); + if (0x80 & descp[2]) { + n += scnpr(b + n, blen - n, "%s Information: 0x", lip); + for (j = 0; j < 8; ++j) + n += scnpr(b + n, blen - n, "%02x", descp[8 + j]); + n += scnpr(b + n, blen - n, "\n"); + } + n += scnpr(b + n, blen - n, "%s Command specific: 0x", lip); + for (j = 0; j < 8; ++j) + n += scnpr(b + n, blen - n, "%02x", descp[16 + j]); + n += scnpr(b + n, blen - n, "\n"); + break; + case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */ + n += scnpr(b + n, blen - n, "Device designation\n"); + j = (int)(sizeof(dd_usage_reason_str_arr) / + sizeof(dd_usage_reason_str_arr[0])); + if (descp[3] < j) + n += scnpr(b + n, blen - n, "%s Usage reason: %s\n", lip, + dd_usage_reason_str_arr[descp[3]]); + else + n += scnpr(b + n, blen - n, "%s Usage reason: " + "reserved[%d]\n", lip, descp[3]); + n += sg_get_designation_descriptor_str(z, descp + 4, descp[1] - 2, + true, false, blen - n, + b + n); + break; + case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */ + n += scnpr(b + n, blen - n, "Microcode activation "); + if (add_d_len < 6) { + n += scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + progress = sg_get_unaligned_be16(descp + 6); + n += scnpr(b + n, blen - n, "time: "); + if (0 == progress) + n += scnpr(b + n, blen - n, "unknown\n"); + else + n += scnpr(b + n, blen - n, "%d seconds\n", progress); + break; + default: + if (descp[0] >= 0x80) + n += scnpr(b + n, blen - n, "Vendor specific [0x%x]\n", + descp[0]); + else + n += scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]); + processed = false; + break; + } + if (! processed) { + if (add_d_len > 0) { + n += scnpr(b + n, blen - n, "%s ", lip); + for (j = 0; j < add_d_len; ++j) { + if ((j > 0) && (0 == (j % 24))) + n += scnpr(b + n, blen - n, "\n%s ", lip); + n += scnpr(b + n, blen - n, "%02x ", descp[j + 2]); + } + n += scnpr(b + n, blen - n, "\n"); + } + } + if (add_d_len < 0) + n += scnpr(b + n, blen - n, "%s short descriptor\n", lip); + } + return n; +} + +/* Decode SAT ATA PASS-THROUGH fixed format sense. Shows "+" after 'count' + * and/or 'lba' values to indicate that not all data in those fields is shown. + * That extra field information may be available in the ATA pass-through + * results log page parameter with the corresponding 'log_index'. */ +static int +sg_get_sense_sat_pt_fixed_str(const char * lip, const unsigned char * sp, + int slen, int blen, char * b) +{ + int n = 0; + bool extend, count_upper_nz, lba_upper_nz; + + if ((blen < 1) || (slen < 12)) + return n; + if (NULL == lip) + lip = ""; + if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2])) + n += scnpr(b + n, blen - n, "%s >> expected Sense key: Recovered " + "Error ??\n", lip); + /* Fixed sense command-specific information field starts at sp + 8 */ + extend = !!(0x80 & sp[8]); + count_upper_nz = !!(0x40 & sp[8]); + lba_upper_nz = !!(0x20 & sp[8]); + /* Fixed sense information field starts at sp + 3 */ + n += scnpr(b + n, blen - n, "%s error=0x%x, status=0x%x, device=0x%x, " + "count(7:0)=0x%x%c\n", lip, sp[3], sp[4], sp[5], sp[6], + (count_upper_nz ? '+' : ' ')); + n += scnpr(b + n, blen - n, "%s extend=%d, log_index=0x%x, " + "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", lip, (int)extend, + (0xf & sp[8]), sp[9], sp[10], sp[11], + (lba_upper_nz ? '+' : ' ')); + return n; +} + +/* Fetch sense information */ +int +sg_get_sense_str(const char * lip, const unsigned char * sbp, int sb_len, + bool raw_sinfo, int cblen, char * cbp) +{ + bool descriptor_format = false; + bool sdat_ovfl = false; + bool valid; + int len, progress, n, r, pr, rem, blen; + unsigned int info; + uint8_t resp_code; + const char * ebp = NULL; + char ebuff[64]; + char b[256]; + struct sg_scsi_sense_hdr ssh; + + if ((NULL == cbp) || (cblen <= 0)) + return 0; + else if (1 == cblen) { + cbp[0] = '\0'; + return 0; + } + blen = sizeof(b); + n = 0; + if (NULL == lip) + lip = ""; + if ((NULL == sbp) || (sb_len < 1)) { + n += scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip); + return n; + } + resp_code = 0x7f & sbp[0]; + valid = !!(sbp[0] & 0x80); + len = sb_len; + if (sg_scsi_normalize_sense(sbp, sb_len, &ssh)) { + switch (ssh.response_code) { + case 0x70: /* fixed, current */ + ebp = "Fixed format, current"; + len = (sb_len > 7) ? (sbp[7] + 8) : sb_len; + len = (len > sb_len) ? sb_len : len; + sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false; + break; + case 0x71: /* fixed, deferred */ + /* error related to a previous command */ + ebp = "Fixed format, <<<deferred>>>"; + len = (sb_len > 7) ? (sbp[7] + 8) : sb_len; + len = (len > sb_len) ? sb_len : len; + sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false; + break; + case 0x72: /* descriptor, current */ + descriptor_format = true; + ebp = "Descriptor format, current"; + sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false; + break; + case 0x73: /* descriptor, deferred */ + descriptor_format = true; + ebp = "Descriptor format, <<<deferred>>>"; + sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false; + break; + case 0x0: + ebp = "Response code: 0x0 (?)"; + break; + default: + scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x", + ssh.response_code); + ebp = ebuff; + break; + } + n += scnpr(cbp + n, cblen - n, "%s%s; Sense key: %s\n", lip, ebp, + sg_lib_sense_key_desc[ssh.sense_key]); + if (sdat_ovfl) + n += scnpr(cbp + n, cblen - n, "%s<<<Sense data overflow>>>\n", + lip); + if (descriptor_format) { + n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + n += sg_get_sense_descriptors_str(lip, sbp, len, + cblen - n, cbp + n); + } else if ((len > 12) && (0 == ssh.asc) && + (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) { + /* SAT ATA PASS-THROUGH fixed format */ + n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + n += sg_get_sense_sat_pt_fixed_str(lip, sbp, len, + cblen - n, cbp + n); + } else if (len > 2) { /* fixed format */ + if (len > 12) + n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + r = 0; + if (strlen(lip) > 0) + r += scnpr(b + r, blen - r, "%s", lip); + if (len > 6) { + info = sg_get_unaligned_be32(sbp + 3); + if (valid) + r += scnpr(b + r, blen - r, " Info fld=0x%x [%u] ", + info, info); + else if (info > 0) + r += scnpr(b + r, blen - r, " Valid=0, Info fld=0x%x " + "[%u] ", info, info); + } else + info = 0; + if (sbp[2] & 0xe0) { + if (sbp[2] & 0x80) + r += scnpr(b + r, blen - r, " FMK"); + /* current command has read a filemark */ + if (sbp[2] & 0x40) + r += scnpr(b + r, blen - r, " EOM"); + /* end-of-medium condition exists */ + if (sbp[2] & 0x20) + r += scnpr(b + r, blen - r, " ILI"); + /* incorrect block length requested */ + r += scnpr(b + r, blen - r, "\n"); + } else if (valid || (info > 0)) + r += scnpr(b + r, blen - r, "\n"); + if ((len >= 14) && sbp[14]) + r += scnpr(b + r, blen - r, "%s Field replaceable unit " + "code: %d\n", lip, sbp[14]); + if ((len >= 18) && (sbp[15] & 0x80)) { + /* sense key specific decoding */ + switch (ssh.sense_key) { + case SPC_SK_ILLEGAL_REQUEST: + r += scnpr(b + r, blen - r, "%s Sense Key Specific: " + "Error in %s: byte %d", lip, + ((sbp[15] & 0x40) ? "Command" : + "Data parameters"), + sg_get_unaligned_be16(sbp + 16)); + if (sbp[15] & 0x08) + r += scnpr(b + r, blen - r, " bit %d\n", + sbp[15] & 0x07); + else + r += scnpr(b + r, blen - r, "\n"); + break; + case SPC_SK_NO_SENSE: + case SPC_SK_NOT_READY: + progress = sg_get_unaligned_be16(sbp + 16); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + r += scnpr(b + r, blen - r, "%s Progress indication: " + "%d.%02d%%\n", lip, pr, rem); + break; + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_RECOVERED_ERROR: + r += scnpr(b + r, blen - r, "%s Actual retry count: " + "0x%02x%02x\n", lip, sbp[16], sbp[17]); + break; + case SPC_SK_COPY_ABORTED: + r += scnpr(b + r, blen - r, "%s Segment pointer: ", lip); + r += scnpr(b + r, blen - r, "Relative to start of %s, " + "byte %d", ((sbp[15] & 0x20) ? + "segment descriptor" : "parameter list"), + sg_get_unaligned_be16(sbp + 16)); + if (sbp[15] & 0x08) + r += scnpr(b + r, blen - r, " bit %d\n", + sbp[15] & 0x07); + else + r += scnpr(b + r, blen - r, "\n"); + break; + case SPC_SK_UNIT_ATTENTION: + r += scnpr(b + r, blen - r, "%s Unit attention " + "condition queue: ", lip); + r += scnpr(b + r, blen - r, "overflow flag is %d\n", + !!(sbp[15] & 0x1)); + break; + default: + r += scnpr(b + r, blen - r, "%s Sense_key: 0x%x " + "unexpected\n", lip, ssh.sense_key); + break; + } + } + if (r > 0) + n += scnpr(cbp + n, cblen - n, "%s", b); + } else + n += scnpr(cbp + n, cblen - n, "%s fixed descriptor length " + "too short, len=%d\n", lip, len); + } else { /* unable to normalise sense buffer, something irregular */ + if (sb_len < 4) { /* Too short */ + n += scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 " + "byte minimum)\n", lip); + goto check_raw; + } + if (0x7f == resp_code) { /* Vendor specific */ + n += scnpr(cbp + n, cblen - n, "%sVendor specific sense buffer, " + "in hex:\n", lip); + n += hex2str(sbp, sb_len, lip, -1, cblen - n, cbp + n); + return n; /* no need to check raw, just output in hex */ + } + /* non-extended SCSI-1 sense data ?? */ + r = 0; + if (strlen(lip) > 0) + r += scnpr(b + r, blen - r, "%s", lip); + r += scnpr(b + r, blen - r, "Probably uninitialized data.\n%s Try " + "to view as SCSI-1 non-extended sense:\n", lip); + r += scnpr(b + r, blen - r, " AdValid=%d Error class=%d Error " + "code=%d\n", valid, ((sbp[0] >> 4) & 0x7), + (sbp[0] & 0xf)); + if (valid) + scnpr(b + r, blen - r, "%s lba=0x%x\n", lip, + sg_get_unaligned_be24(sbp + 1) & 0x1fffff); + n += scnpr(cbp + n, cblen - n, "%s\n", b); + len = sb_len; + if (len > 32) + len = 32; /* trim in case there is a lot of rubbish */ + } +check_raw: + if (raw_sinfo) { + char z[64]; + + n += scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex):\n", + lip); + if (n >= (cblen - 1)) + return n; + scnpr(z, sizeof(z), "%.50s ", lip); + n += hex2str(sbp, len, z, -1, cblen - n, cbp + n); + } + return n; +} + +/* Print sense information */ +void +sg_print_sense(const char * leadin, const unsigned char * sbp, int sb_len, + bool raw_sinfo) +{ + uint32_t pg_sz = sg_get_page_size(); + char *cp; + uint8_t *free_cp; + + cp = (char *)sg_memalign(pg_sz, pg_sz, &free_cp, 0); + if (NULL == cp) + return; + sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp); + pr2ws("%s", cp); + free(free_cp); +} + +/* Following examines exit_status and outputs a clear error message to + * warnings_strm (usually stderr) if one is known and returns true. + * Otherwise it doesn't print anything and returns false. Note that + * if exit_status==0 then returns true but prints nothing and if + * exit_status<0 ("some error occurred") false is returned. If leadin is + * non-NULL then it is printed before the error message. */ +bool +sg_if_can2stderr(const char * leadin, int exit_status) +{ + const char * s = leadin ? leadin : ""; + + if (exit_status < 0) + return false; + else if (0 == exit_status) + return true; + + switch (exit_status) { + case SG_LIB_CAT_NOT_READY: /* 2 */ + pr2ws("%sDevice not ready\n", s); + return true; + case SG_LIB_CAT_MEDIUM_HARD: /* 3 */ + pr2ws("%sMedium or hardware error\n", s); /* 3 sense keys: Medium, */ + return true; /* hardware error or 'Blank check' for tapes */ + case SG_LIB_CAT_UNIT_ATTENTION: /* 6 */ + pr2ws("%sDevice reported 'Unit attention'\n", s); + return true; + case SG_LIB_CAT_DATA_PROTECT: /* 7 */ + pr2ws("%sDevice reported 'Data protect', read-only?\n", s); + return true; + case SG_LIB_CAT_COPY_ABORTED: /* 10 */ + pr2ws("%sCopy aborted\n", s); + return true; + case SG_LIB_CAT_ABORTED_COMMAND: /* 11 */ + pr2ws("%sCommand aborted\n", s); + return true; + case SG_LIB_CAT_MISCOMPARE: /* 14 */ + pr2ws("%sMiscompare\n", s); + return true; + case SG_LIB_CAT_RES_CONFLICT: /* 24 */ + pr2ws("%sReservation conflict\n", s); + return true; + case SG_LIB_CAT_BUSY: /* 26 */ + pr2ws("%sDevice is busy, try again\n", s); + return true; + case SG_LIB_CAT_TASK_ABORTED: /* 29 */ + pr2ws("%sTask aborted\n", s); + return true; + case SG_LIB_CAT_TIMEOUT: /* 33 */ + pr2ws("%sTime out\n", s); + return true; + case SG_LIB_CAT_PROTECTION: /* 40 */ + pr2ws("%sProtection error\n", s); + return true; + case SG_LIB_NVME_STATUS: /* 48 */ + pr2ws("%sNVMe error (non-zero status)\n", s); + return true; + case SG_LIB_OS_BASE_ERR + EACCES: /* 50 + */ + pr2ws("%sPermission denied\n", s); + return true; + case SG_LIB_OS_BASE_ERR + ENOMEM: + pr2ws("%sUtility unable to allocate memory\n", s); + return true; + case SG_LIB_OS_BASE_ERR + ENOTTY: + pr2ws("%sInappropriate I/O control operation\n", s); + return true; + case SG_LIB_OS_BASE_ERR + EPERM: + pr2ws("%sNot permitted\n", s); + return true; + case SG_LIB_OS_BASE_ERR + EINTR: + pr2ws("%sInterrupted system call\n", s); + return true; + case SG_LIB_OS_BASE_ERR + EIO: + pr2ws("%sInput/output error\n", s); + return true; + case SG_LIB_OS_BASE_ERR + ENODEV: + pr2ws("%sNo such device\n", s); + return true; + case SG_LIB_OS_BASE_ERR + ENOENT: + pr2ws("%sNo such file or directory\n", s); + return true; + default: + return false; + } + return false; +} + +/* If os_err_num is within bounds then the returned value is 'os_err_num + + * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0 + * is returned. */ +int +sg_convert_errno(int os_err_num) +{ + if (os_err_num <= 0) { + if (os_err_num < -1) + return -1; + return os_err_num; + } + if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR)) + return SG_LIB_OS_BASE_ERR + os_err_num; + return -1; +} + +/* See description in sg_lib.h header file */ +bool +sg_scsi_normalize_sense(const unsigned char * sbp, int sb_len, + struct sg_scsi_sense_hdr * sshp) +{ + uint8_t resp_code; + if (sshp) + memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr)); + if ((NULL == sbp) || (sb_len < 1)) + return false; + resp_code = 0x7f & sbp[0]; + if ((resp_code < 0x70) || (resp_code > 0x73)) + return false; + if (sshp) { + sshp->response_code = resp_code; + if (sshp->response_code >= 0x72) { /* descriptor format */ + if (sb_len > 1) + sshp->sense_key = (0xf & sbp[1]); + if (sb_len > 2) + sshp->asc = sbp[2]; + if (sb_len > 3) + sshp->ascq = sbp[3]; + if (sb_len > 7) + sshp->additional_length = sbp[7]; + } else { /* fixed format */ + if (sb_len > 2) + sshp->sense_key = (0xf & sbp[2]); + if (sb_len > 7) { + sb_len = (sb_len < (sbp[7] + 8)) ? sb_len : (sbp[7] + 8); + if (sb_len > 12) + sshp->asc = sbp[12]; + if (sb_len > 13) + sshp->ascq = sbp[13]; + } + } + } + return true; +} + +/* Returns a SG_LIB_CAT_* value. If cannot decode sense buffer (sbp) or a + * less common sense key then return SG_LIB_CAT_SENSE .*/ +int +sg_err_category_sense(const unsigned char * sbp, int sb_len) +{ + struct sg_scsi_sense_hdr ssh; + + if ((sbp && (sb_len > 2)) && + (sg_scsi_normalize_sense(sbp, sb_len, &ssh))) { + switch (ssh.sense_key) { /* 0 to 0x1f */ + case SPC_SK_NO_SENSE: + return SG_LIB_CAT_NO_SENSE; + case SPC_SK_RECOVERED_ERROR: + return SG_LIB_CAT_RECOVERED; + case SPC_SK_NOT_READY: + return SG_LIB_CAT_NOT_READY; + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_BLANK_CHECK: + return SG_LIB_CAT_MEDIUM_HARD; + case SPC_SK_UNIT_ATTENTION: + return SG_LIB_CAT_UNIT_ATTENTION; + /* used to return SG_LIB_CAT_MEDIA_CHANGED when ssh.asc==0x28 */ + case SPC_SK_ILLEGAL_REQUEST: + if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) + return SG_LIB_CAT_INVALID_OP; + else + return SG_LIB_CAT_ILLEGAL_REQ; + break; + case SPC_SK_ABORTED_COMMAND: + if (0x10 == ssh.asc) + return SG_LIB_CAT_PROTECTION; + else + return SG_LIB_CAT_ABORTED_COMMAND; + case SPC_SK_MISCOMPARE: + return SG_LIB_CAT_MISCOMPARE; + case SPC_SK_DATA_PROTECT: + return SG_LIB_CAT_DATA_PROTECT; + case SPC_SK_COPY_ABORTED: + return SG_LIB_CAT_COPY_ABORTED; + case SPC_SK_COMPLETED: + case SPC_SK_VOLUME_OVERFLOW: + return SG_LIB_CAT_SENSE; + default: + ; /* reserved and vendor specific sense keys fall through */ + } + } + return SG_LIB_CAT_SENSE; +} + +/* Beware: gives wrong answer for variable length command (opcode=0x7f) */ +int +sg_get_command_size(unsigned char opcode) +{ + switch ((opcode >> 5) & 0x7) { + case 0: + return 6; + case 1: case 2: case 6: case 7: + return 10; + case 3: case 5: + return 12; + break; + case 4: + return 16; + default: + return 10; + } +} + +void +sg_get_command_name(const unsigned char * cmdp, int peri_type, int buff_len, + char * buff) +{ + int service_action; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + if (NULL == cmdp) { + scnpr(buff, buff_len, "%s", "<null> command pointer"); + return; + } + service_action = (SG_VARIABLE_LENGTH_CMD == cmdp[0]) ? + sg_get_unaligned_be16(cmdp + 8) : (cmdp[1] & 0x1f); + sg_get_opcode_sa_name(cmdp[0], service_action, peri_type, buff_len, buff); +} + +struct op_code2sa_t { + int op_code; + int pdt_match; /* -1->all; 0->disk,ZBC,RCB, 1->tape+adc+smc */ + struct sg_lib_value_name_t * arr; + const char * prefix; +}; + +static struct op_code2sa_t op_code2sa_arr[] = { + {SG_VARIABLE_LENGTH_CMD, -1, sg_lib_variable_length_arr, NULL}, + {SG_MAINTENANCE_IN, -1, sg_lib_maint_in_arr, NULL}, + {SG_MAINTENANCE_OUT, -1, sg_lib_maint_out_arr, NULL}, + {SG_SERVICE_ACTION_IN_12, -1, sg_lib_serv_in12_arr, NULL}, + {SG_SERVICE_ACTION_OUT_12, -1, sg_lib_serv_out12_arr, NULL}, + {SG_SERVICE_ACTION_IN_16, -1, sg_lib_serv_in16_arr, NULL}, + {SG_SERVICE_ACTION_OUT_16, -1, sg_lib_serv_out16_arr, NULL}, + {SG_SERVICE_ACTION_BIDI, -1, sg_lib_serv_bidi_arr, NULL}, + {SG_PERSISTENT_RESERVE_IN, -1, sg_lib_pr_in_arr, "Persistent reserve in"}, + {SG_PERSISTENT_RESERVE_OUT, -1, sg_lib_pr_out_arr, + "Persistent reserve out"}, + {SG_3PARTY_COPY_OUT, -1, sg_lib_xcopy_sa_arr, NULL}, + {SG_3PARTY_COPY_IN, -1, sg_lib_rec_copy_sa_arr, NULL}, + {SG_READ_BUFFER, -1, sg_lib_read_buff_arr, "Read buffer(10)"}, + {SG_READ_BUFFER_16, -1, sg_lib_read_buff_arr, "Read buffer(16)"}, + {SG_READ_ATTRIBUTE, -1, sg_lib_read_attr_arr, "Read attribute"}, + {SG_READ_POSITION, 1, sg_lib_read_pos_arr, "Read position"}, + {SG_SANITIZE, 0, sg_lib_sanitize_sa_arr, "Sanitize"}, + {SG_WRITE_BUFFER, -1, sg_lib_write_buff_arr, "Write buffer"}, + {SG_ZONING_IN, 0, sg_lib_zoning_in_arr, NULL}, + {SG_ZONING_OUT, 0, sg_lib_zoning_out_arr, NULL}, + {0xffff, -1, NULL, NULL}, +}; + +void +sg_get_opcode_sa_name(unsigned char cmd_byte0, int service_action, + int peri_type, int buff_len, char * buff) +{ + int d_pdt; + const struct sg_lib_value_name_t * vnp; + const struct op_code2sa_t * osp; + char b[80]; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + + if (peri_type < 0) + peri_type = 0; + d_pdt = sg_lib_pdt_decay(peri_type); + for (osp = op_code2sa_arr; osp->arr; ++osp) { + if ((int)cmd_byte0 == osp->op_code) { + if ((osp->pdt_match < 0) || (d_pdt == osp->pdt_match)) { + vnp = get_value_name(osp->arr, service_action, peri_type); + if (vnp) { + if (osp->prefix) + scnpr(buff, buff_len, "%s, %s", osp->prefix, + vnp->name); + else + scnpr(buff, buff_len, "%s", vnp->name); + } else { + sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b); + scnpr(buff, buff_len, "%s service action=0x%x", b, + service_action); + } + } else + sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff); + return; + } + } + sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff); +} + +void +sg_get_opcode_name(unsigned char cmd_byte0, int peri_type, int buff_len, + char * buff) +{ + const struct sg_lib_value_name_t * vnp; + int grp; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + if (SG_VARIABLE_LENGTH_CMD == cmd_byte0) { + scnpr(buff, buff_len, "%s", "Variable length"); + return; + } + grp = (cmd_byte0 >> 5) & 0x7; + switch (grp) { + case 0: + case 1: + case 2: + case 4: + case 5: + vnp = get_value_name(sg_lib_normal_opcodes, cmd_byte0, peri_type); + if (vnp) + scnpr(buff, buff_len, "%s", vnp->name); + else + scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0); + break; + case 3: + scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0); + break; + case 6: + case 7: + scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0); + break; + default: + scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0); + break; + } +} + +/* Iterates to next designation descriptor in the device identification + * VPD page. The 'initial_desig_desc' should point to start of first + * descriptor with 'page_len' being the number of valid bytes in that + * and following descriptors. To start, 'off' should point to a negative + * value, thereafter it should point to the value yielded by the previous + * call. If 0 returned then 'initial_desig_desc + *off' should be a valid + * descriptor; returns -1 if normal end condition and -2 for an abnormal + * termination. Matches association, designator_type and/or code_set when + * any of those values are greater than or equal to zero. */ +int +sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len, + int * off, int m_assoc, int m_desig_type, int m_code_set) +{ + bool fltr = ((m_assoc >= 0) || (m_desig_type >= 0) || (m_code_set >= 0)); + int k = *off; + const unsigned char * bp = initial_desig_desc; + + while ((k + 3) < page_len) { + k = (k < 0) ? 0 : (k + bp[k + 3] + 4); + if ((k + 4) > page_len) + break; + if (fltr) { + if (m_code_set >= 0) { + if ((bp[k] & 0xf) != m_code_set) + continue; + } + if (m_assoc >= 0) { + if (((bp[k + 1] >> 4) & 0x3) != m_assoc) + continue; + } + if (m_desig_type >= 0) { + if ((bp[k + 1] & 0xf) != m_desig_type) + continue; + } + } + *off = k; + return 0; + } + return (k == page_len) ? -1 : -2; +} + +static const char * const bad_sense_cat = "Bad sense category"; + +/* Yield string associated with sense category. Returns 'buff' (or pointer + * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then + * yield "Sense category: <sense_cat>" string. */ +const char * +sg_get_category_sense_str(int sense_cat, int buff_len, char * buff, + int verbose) +{ + int n; + + if (NULL == buff) + return bad_sense_cat; + if (buff_len <= 0) + return buff; + switch (sense_cat) { + case SG_LIB_CAT_CLEAN: /* 0 */ + scnpr(buff, buff_len, "No errors"); + break; + case SG_LIB_SYNTAX_ERROR: /* 1 */ + scnpr(buff, buff_len, "Syntax error"); + break; + case SG_LIB_CAT_NOT_READY: /* 2 */ + n = scnpr(buff, buff_len, "Not ready"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_MEDIUM_HARD: /* 3 */ + n = scnpr(buff, buff_len, "Medium or hardware error"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key (plus blank check)"); + break; + case SG_LIB_CAT_ILLEGAL_REQ: /* 5 */ + n = scnpr(buff, buff_len, "Illegal request"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key, apart from Invalid " + "opcode"); + break; + case SG_LIB_CAT_UNIT_ATTENTION: /* 6 */ + n = scnpr(buff, buff_len, "Unit attention"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_DATA_PROTECT: /* 7 */ + n = scnpr(buff, buff_len, "Data protect"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key, write protected " + "media?"); + break; + case SG_LIB_CAT_INVALID_OP: /* 9 */ + n = scnpr(buff, buff_len, "Illegal request, invalid opcode"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_COPY_ABORTED: /* 10 */ + n = scnpr(buff, buff_len, "Copy aborted"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_ABORTED_COMMAND: /* 11 */ + n = scnpr(buff, buff_len, "Aborted command"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key, other than " + "protection related (asc=0x10)"); + break; + case SG_LIB_CAT_MISCOMPARE: /* 14 */ + n = scnpr(buff, buff_len, "Miscompare"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_FILE_ERROR: /* 15 */ + scnpr(buff, buff_len, "File error"); + break; + case SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO: /* 17 */ + scnpr(buff, buff_len, "Illegal request with info"); + break; + case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO: /* 18 */ + scnpr(buff, buff_len, "Medium or hardware error with info"); + break; + case SG_LIB_CAT_NO_SENSE: /* 20 */ + n = scnpr(buff, buff_len, "No sense key"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " probably additional sense " + "information"); + break; + case SG_LIB_CAT_RECOVERED: /* 21 */ + n = scnpr(buff, buff_len, "Recovered error"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " sense key"); + break; + case SG_LIB_CAT_RES_CONFLICT: /* 24 */ + n = scnpr(buff, buff_len, "Reservation conflict"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_CONDITION_MET: /* 25 */ + n = scnpr(buff, buff_len, "Condition met"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_BUSY: /* 26 */ + n = scnpr(buff, buff_len, "Busy"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_TS_FULL: /* 27 */ + n = scnpr(buff, buff_len, "Task set full"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_ACA_ACTIVE: /* 28 */ + n = scnpr(buff, buff_len, "ACA active"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_TASK_ABORTED: /* 29 */ + n = scnpr(buff, buff_len, "Task aborted"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " SCSI status"); + break; + case SG_LIB_CAT_TIMEOUT: /* 33 */ + scnpr(buff, buff_len, "SCSI command timeout"); + break; + case SG_LIB_CAT_PROTECTION: /* 40 */ + n = scnpr(buff, buff_len, "Aborted command, protection"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " information (PI) problem"); + break; + case SG_LIB_CAT_PROTECTION_WITH_INFO: /* 41 */ + n = scnpr(buff, buff_len, "Aborted command with info, protection"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " information (PI) problem"); + break; + case SG_LIB_CAT_MALFORMED: /* 97 */ + n = scnpr(buff, buff_len, "Malformed response"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, " to SCSI command"); + break; + case SG_LIB_CAT_SENSE: /* 98 */ + n = scnpr(buff, buff_len, "Some other sense data problem"); + if (verbose && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, ", try '-v' option for more " + "information"); + break; + case SG_LIB_CAT_OTHER: /* 99 */ + n = scnpr(buff, buff_len, "Some other error/warning has occurred"); + if ((0 == verbose) && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, ", possible transport of driver " + "issue"); + break; + default: + if ((sense_cat > SG_LIB_OS_BASE_ERR) && + (sense_cat < (SG_LIB_OS_BASE_ERR + 47))) { + int k = sense_cat - SG_LIB_OS_BASE_ERR; + + n = scnpr(buff, buff_len, "OS error: %s [%d]", safe_strerror(k), + k); + } else { + n = scnpr(buff, buff_len, "Sense category: %d", sense_cat); + if ((0 == verbose) && (n < (buff_len - 1))) + scnpr(buff + n, buff_len - n, ", try '-v' option for more " + "information"); + } + break; + } + return buff; +} + +static const char * sg_sfs_spc_reserved = "SPC Reserved"; +static const char * sg_sfs_sbc_reserved = "SBC Reserved"; +static const char * sg_sfs_ssc_reserved = "SSC Reserved"; +static const char * sg_sfs_zbc_reserved = "ZBC Reserved"; +static const char * sg_sfs_reserved = "Reserved"; + +/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31) + * returns pointer to string (same as 'buff') associated with 'sfs_code'. + * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match + * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL + * then where it points is set to true if a match is found else it is set to + * false. If 'buff' is not NULL then in the case of a match a descriptive + * string is written to 'buff' while if there is not a not then a string + * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC + * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL. + * Example: + * char b[64]; + * ... + * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0)); + */ +const char * +sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, char * buff, + bool * foundp, int verbose) +{ + const struct sg_lib_value_name_t * vnp = NULL; + int n = 0; + int my_pdt; + + if ((NULL == buff) || (buff_len < 1)) { + if (foundp) + *foundp = false; + return NULL; + } else if (1 == buff_len) { + buff[0] = '\0'; + if (foundp) + *foundp = false; + return NULL; + } + my_pdt = ((peri_type < -1) || (peri_type > 0x1f)) ? -2 : peri_type; + vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt); + if (vnp && (-2 != my_pdt)) { + if (peri_type != vnp->peri_dev_type) + vnp = NULL; /* shouldn't really happen */ + } + if (foundp) + *foundp = vnp ? true : false; + if (sfs_code < 0x100) { /* SPC Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "SPC %s", vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved); + } else if (sfs_code < 0x200) { /* SBC Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "SBC %s", vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved); + } else if (sfs_code < 0x300) { /* SSC Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "SSC %s", vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved); + } else if (sfs_code < 0x400) { /* ZBC Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "ZBC %s", vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved); + } else { /* Other SCSI Feature Sets */ + if (vnp) { + if (verbose) + n += scnpr(buff, buff_len, "[unrecognized PDT] %s", + vnp->name); + else + n += scnpr(buff, buff_len, "%s", vnp->name); + } else + n += scnpr(buff, buff_len, "%s", sg_sfs_reserved); + + } + if (verbose > 4) + pr2serr("%s: length of returned string (n) %d\n", __func__, n); + return buff; +} + +/* This is a heuristic that takes into account the command bytes and length + * to decide whether the presented unstructured sequence of bytes could be + * a SCSI command. If so it returns true otherwise false. Vendor specific + * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed + * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The + * only SCSI commands considered above 16 bytes of length are the Variable + * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e). + * Both have an inbuilt length field which can be cross checked with clen. + * No NVMe commands (64 bytes long plus some extra added by some OSes) have + * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS + * structures that are sent across the wire. The FIS register structure is + * used to move a command from a SATA host to device, but the ATA 'command' + * is not the first byte. So it is harder to say what will happen if a + * FIS structure is presented as a SCSI command, hopfully there is a low + * probability this function will yield true in that case. */ +bool +sg_is_scsi_cdb(const uint8_t * cdbp, int clen) +{ + int ilen, sa; + uint8_t opcode; + uint8_t top3bits; + + if (clen < 6) + return false; + opcode = cdbp[0]; + top3bits = opcode >> 5; + if (0x3 == top3bits) { + if ((clen < 12) || (clen % 4)) + return false; /* must be modulo 4 and 12 or more bytes */ + switch (opcode) { + case 0x7e: /* Extended cdb (XCDB) */ + ilen = 4 + sg_get_unaligned_be16(cdbp + 2); + return (ilen == clen); + case 0x7f: /* Variable Length cdb */ + ilen = 8 + cdbp[7]; + sa = sg_get_unaligned_be16(cdbp + 8); + /* service action (sa) 0x0 is reserved */ + return ((ilen == clen) && sa); + default: + return false; + } + } else if (clen <= 16) { + switch (clen) { + case 6: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x0 == top3bits); /* 6 byte cdb */ + case 10: + if (top3bits > 0x5) /* vendor */ + return true; + return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */ + case 16: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x4 == top3bits); /* 16 byte cdb */ + case 12: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x5 == top3bits); /* 12 byte cdb */ + default: + return false; + } + } + /* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or + * opcode > 0x7f). */ + return false; +} + +/* Yield string associated with NVMe command status value in sct_sc. It + * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25 + * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC). + * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found + * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated. + * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/ +char * +sg_get_nvme_cmd_status_str(uint16_t sct_sc, int b_len, char * b) +{ + int k; + uint16_t s = 0x3ff & sct_sc; + const struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr; + + if ((b_len <= 0) || (NULL == b)) + return b; + else if (1 == b_len) { + b[0] = '\0'; + return b; + } + for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) { + if (s == (uint16_t)vp->value) { + strncpy(b, vp->name, b_len); + b[b_len - 1] = '\0'; + return b; + } + } + if (k >= 1000) + pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n", + __func__); + snprintf(b, b_len, "Reserved [0x%x]", sct_sc); + return b; +} + +/* Attempts to map NVMe status value ((SCT << 8) | SC) to SCSI status, + * sense_key, asc and ascq tuple. If successful returns true and writes to + * non-NULL pointer arguments; otherwise returns false. */ +bool +sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p, + uint8_t * asc_p, uint8_t * ascq_p) +{ + int k, ind; + uint16_t s = 0x3ff & sct_sc; + struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr; + struct sg_lib_4tuple_u8 * mp = sg_lib_scsi_status_sense_arr; + + for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) { + if (s == (uint16_t)vp->value) + break; + } + if (k >= 1000) { + pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n", + __func__); + return false; + } + if (NULL == vp->name) + return false; + ind = vp->peri_dev_type; + + + for (k = 0; (0xff != mp->t2) && k < 1000; ++k, ++mp) + ; /* count entries for valid index range */ + if (k >= 1000) { + pr2ws("%s: where is sentinel for sg_lib_scsi_status_sense_arr ??\n", + __func__); + return false; + } else if (ind >= k) + return false; + mp = sg_lib_scsi_status_sense_arr + ind; + if (status_p) + *status_p = mp->t1; + if (sk_p) + *sk_p = mp->t2; + if (asc_p) + *asc_p = mp->t3; + if (ascq_p) + *ascq_p = mp->t4; + return true; +} + +/* safe_strerror() contributed by Clayton Weaver <cgweav at email dot com> + * Allows for situation in which strerror() is given a wild value (or the + * C library is incomplete) and returns NULL. Still not thread safe. + */ + +static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ', + 'e', 'r', 'r', 'n', 'o', ':', ' ', 0}; + +char * +safe_strerror(int errnum) +{ + size_t len; + char * errstr; + + if (errnum < 0) + errnum = -errnum; + errstr = strerror(errnum); + if (NULL == errstr) { + len = strlen(safe_errbuf); + scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum); + return safe_errbuf; + } + return errstr; +} + +static void +trimTrailingSpaces(char * b) +{ + int k; + + for (k = ((int)strlen(b) - 1); k >= 0; --k) { + if (' ' != b[k]) + break; + } + if ('\0' != b[k + 1]) + b[k + 1] = '\0'; +} + +/* Note the ASCII-hex output goes to stdout. [Most other output from functions + * in this file go to sg_warnings_strm (default stderr).] + * 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 16 ASCII-hex bytes + * = 0 in addition, the bytes are listed in ASCII to the right + * < 0 only the ASCII-hex bytes are listed (i.e. without address) */ +static void +dStrHexFp(const char* str, int len, int no_ascii, FILE * fp) +{ + const char * p = str; + const char * formatstr; + unsigned char c; + char buff[82]; + int a = 0; + int bpstart = 5; + const int cpstart = 60; + int cpos = cpstart; + int bpos = bpstart; + int i, k, blen; + + if (len <= 0) + return; + blen = (int)sizeof(buff); + if (0 == no_ascii) /* address at left and ASCII at right */ + formatstr = "%.76s\n"; + else /* previously when > 0 str was "%.58s\n" */ + formatstr = "%s\n"; /* when < 0 str was: "%.48s\n" */ + memset(buff, ' ', 80); + buff[80] = '\0'; + if (no_ascii < 0) { + bpstart = 0; + bpos = bpstart; + for (k = 0; k < len; k++) { + c = *p++; + if (bpos == (bpstart + (8 * 3))) + bpos++; + scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c); + buff[bpos + 2] = ' '; + if ((k > 0) && (0 == ((k + 1) % 16))) { + trimTrailingSpaces(buff); + fprintf(fp, formatstr, buff); + bpos = bpstart; + memset(buff, ' ', 80); + } else + bpos += 3; + } + if (bpos > bpstart) { + buff[bpos + 2] = '\0'; + trimTrailingSpaces(buff); + fprintf(fp, "%s\n", buff); + } + return; + } + /* no_ascii>=0, start each line with address (offset) */ + k = scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + + for (i = 0; i < len; i++) { + c = *p++; + bpos += 3; + if (bpos == (bpstart + (9 * 3))) + bpos++; + scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c); + buff[bpos + 2] = ' '; + if (no_ascii) + buff[cpos++] = ' '; + else { + if (! my_isprint(c)) + c = '.'; + buff[cpos++] = c; + } + if (cpos > (cpstart + 15)) { + if (no_ascii) + trimTrailingSpaces(buff); + fprintf(fp, formatstr, buff); + bpos = bpstart; + cpos = cpstart; + a += 16; + memset(buff, ' ', 80); + k = scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + } + } + if (cpos > cpstart) { + buff[cpos] = '\0'; + if (no_ascii) + trimTrailingSpaces(buff); + fprintf(fp, "%s\n", buff); + } +} + +void +dStrHex(const char* str, int len, int no_ascii) +{ + dStrHexFp(str, len, no_ascii, stdout); +} + +void +dStrHexErr(const char* str, int len, int no_ascii) +{ + dStrHexFp(str, len, no_ascii, + (sg_warnings_strm ? sg_warnings_strm : stderr)); +} + +#define DSHS_LINE_BLEN 160 +#define DSHS_BPL 16 + +/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space + * separated) to 'b' not to exceed 'b_len' characters. Each line + * starts with 'leadin' (NULL for no leadin) and there are 16 bytes + * per line with an extra space between the 8th and 9th bytes. 'format' + * is 0 for repeat in printable ASCII ('.' for non printable) to + * right of each line; 1 don't (so just output ASCII hex). Returns + * number of bytes written to 'b' excluding the trailing '\0'. */ +int +dStrHexStr(const char * str, int len, const char * leadin, int format, + int b_len, char * b) +{ + unsigned char c; + int bpstart, bpos, k, n, prior_ascii_len; + bool want_ascii; + char buff[DSHS_LINE_BLEN + 2]; + char a[DSHS_BPL + 1]; + const char * p = str; + + if (len <= 0) { + if (b_len > 0) + b[0] = '\0'; + return 0; + } + if (b_len <= 0) + return 0; + want_ascii = !format; + if (want_ascii) { + memset(a, ' ', DSHS_BPL); + a[DSHS_BPL] = '\0'; + } + if (leadin) { + bpstart = strlen(leadin); + /* Cap leadin at (DSHS_LINE_BLEN - 70) characters */ + if (bpstart > (DSHS_LINE_BLEN - 70)) + bpstart = DSHS_LINE_BLEN - 70; + } else + bpstart = 0; + bpos = bpstart; + prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1; + n = 0; + memset(buff, ' ', DSHS_LINE_BLEN); + buff[DSHS_LINE_BLEN] = '\0'; + if (bpstart > 0) + memcpy(buff, leadin, bpstart); + for (k = 0; k < len; k++) { + c = *p++; + if (bpos == (bpstart + ((DSHS_BPL / 2) * 3))) + bpos++; /* for extra space in middle of each line's hex */ + scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x", + (int)(unsigned char)c); + buff[bpos + 2] = ' '; + if (want_ascii) + a[k % DSHS_BPL] = my_isprint(c) ? c : '.'; + if ((k > 0) && (0 == ((k + 1) % DSHS_BPL))) { + trimTrailingSpaces(buff); + if (want_ascii) { + n += scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len, + buff, a); + memset(a, ' ', DSHS_BPL); + } else + n += scnpr(b + n, b_len - n, "%s\n", buff); + if (n >= (b_len - 1)) + return n; + memset(buff, ' ', DSHS_LINE_BLEN); + bpos = bpstart; + if (bpstart > 0) + memcpy(buff, leadin, bpstart); + } else + bpos += 3; + } + if (bpos > bpstart) { + trimTrailingSpaces(buff); + if (want_ascii) + n += scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len, + buff, a); + else + n += scnpr(b + n, b_len - n, "%s\n", buff); + } + return n; +} + +void +hex2stdout(const uint8_t * b_str, int len, int no_ascii) +{ + dStrHex((const char *)b_str, len, no_ascii); +} + +void +hex2stderr(const uint8_t * b_str, int len, int no_ascii) +{ + dStrHexErr((const char *)b_str, len, no_ascii); +} + +int +hex2str(const uint8_t * b_str, int len, const char * leadin, int format, + int b_len, char * b) +{ + return dStrHexStr((const char *)b_str, len, leadin, format, b_len, b); +} + +/* Returns true when executed on big endian machine; else returns false. + * Useful for displaying ATA identify words (which need swapping on a + * big endian machine). */ +bool +sg_is_big_endian() +{ + union u_t { + uint16_t s; + unsigned char c[sizeof(uint16_t)]; + } u; + + u.s = 0x0102; + return (u.c[0] == 0x01); /* The lowest address contains + the most significant byte */ +} + +bool +sg_all_zeros(const uint8_t * bp, int b_len) +{ + if ((NULL == bp) || (b_len <= 0)) + return false; + for (--b_len; b_len >= 0; --b_len) { + if (0x0 != bp[b_len]) + return false; + } + return true; +} + +bool +sg_all_ffs(const uint8_t * bp, int b_len) +{ + if ((NULL == bp) || (b_len <= 0)) + return false; + for (--b_len; b_len >= 0; --b_len) { + if (0xff != bp[b_len]) + return false; + } + return true; +} + +static uint16_t +swapb_uint16(uint16_t u) +{ + uint16_t r; + + r = (u >> 8) & 0xff; + r |= ((u & 0xff) << 8); + return r; +} + +/* Note the ASCII-hex output goes to stdout. [Most other output from functions + * in this file go to sg_warnings_strm (default stderr).] + * 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 8 ASCII-hex 16 bit words + * = 0 in addition, the ASCI bytes pairs are listed to the right + * = -1 only the ASCII-hex words are listed (i.e. without address) + * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin" + * < -2 same as -1 + * If 'swapb' is true then bytes in each word swapped. Needs to be set + * for ATA IDENTIFY DEVICE response on big-endian machines. */ +void +dWordHex(const uint16_t* words, int num, int no_ascii, bool swapb) +{ + const uint16_t * p = words; + uint16_t c; + char buff[82]; + unsigned char upp, low; + int a = 0; + const int bpstart = 3; + const int cpstart = 52; + int cpos = cpstart; + int bpos = bpstart; + int i, k, blen; + + if (num <= 0) + return; + blen = (int)sizeof(buff); + memset(buff, ' ', 80); + buff[80] = '\0'; + if (no_ascii < 0) { + for (k = 0; k < num; k++) { + c = *p++; + if (swapb) + c = swapb_uint16(c); + bpos += 5; + scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c); + buff[bpos + 4] = ' '; + if ((k > 0) && (0 == ((k + 1) % 8))) { + if (-2 == no_ascii) + printf("%.39s\n", buff +8); + else + printf("%.47s\n", buff); + bpos = bpstart; + memset(buff, ' ', 80); + } + } + if (bpos > bpstart) { + if (-2 == no_ascii) + printf("%.39s\n", buff +8); + else + printf("%.47s\n", buff); + } + return; + } + /* no_ascii>=0, start each line with address (offset) */ + k = scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + + for (i = 0; i < num; i++) { + c = *p++; + if (swapb) + c = swapb_uint16(c); + bpos += 5; + scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c); + buff[bpos + 4] = ' '; + if (no_ascii) { + buff[cpos++] = ' '; + buff[cpos++] = ' '; + buff[cpos++] = ' '; + } else { + upp = (c >> 8) & 0xff; + low = c & 0xff; + if (! my_isprint(upp)) + upp = '.'; + buff[cpos++] = upp; + if (! my_isprint(low)) + low = '.'; + buff[cpos++] = low; + buff[cpos++] = ' '; + } + if (cpos > (cpstart + 23)) { + printf("%.76s\n", buff); + bpos = bpstart; + cpos = cpstart; + a += 8; + memset(buff, ' ', 80); + k = scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + } + } + if (cpos > cpstart) + printf("%.76s\n", buff); +} + +/* If the number in 'buf' can be decoded or the multiplier is unknown + * then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal + * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)). + * Main (SI) multipliers supported: K, M, G. Ignore leading spaces and + * tabs; accept comma, hyphen, space, tab and hash as terminator. */ +int +sg_get_num(const char * buf) +{ + int res, num, n, len; + unsigned int unum; + char * cp; + const char * b; + char c = 'c'; + char c2 = '\0'; /* keep static checker happy */ + char c3 = '\0'; /* keep static checker happy */ + char lb[16]; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + n = strspn(buf, " \t"); + if (n > 0) { + if (n == len) + return -1; + buf += n; + len -= n; + } + /* following hack to keep C++ happy */ + cp = strpbrk((char *)buf, " \t,#-"); + if (cp) { + len = cp - buf; + n = (int)sizeof(lb) - 1; + len = (len < n) ? len : n; + memcpy(lb, buf, len); + lb[len] = '\0'; + b = lb; + } else + b = buf; + if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) { + res = sscanf(b + 2, "%x", &unum); + num = unum; + } else if ('H' == toupper((int)b[len - 1])) { + res = sscanf(b, "%x", &unum); + num = unum; + } else + res = sscanf(b, "%d%c%c%c", &num, &c, &c2, &c3); + if (res < 1) + return -1LL; + else if (1 == res) + return num; + else { + if (res > 2) + c2 = toupper((int)c2); + if (res > 3) + c3 = toupper((int)c3); + switch (toupper((int)c)) { + case 'C': + return num; + case 'W': + return num * 2; + case 'B': + return num * 512; + case 'K': + if (2 == res) + return num * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1024; + return -1; + case 'M': + if (2 == res) + return num * 1048576; + if (('B' == c2) || ('D' == c2)) + return num * 1000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1048576; + return -1; + case 'G': + if (2 == res) + return num * 1073741824; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1073741824; + return -1; + case 'X': + cp = (char *)strchr(b, 'x'); + if (NULL == cp) + cp = (char *)strchr(b, 'X'); + if (cp) { + n = sg_get_num(cp + 1); + if (-1 != n) + return num * n; + } + return -1; + default: + pr2ws("unrecognized multiplier\n"); + return -1; + } + } +} + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. */ +int +sg_get_num_nomult(const char * buf) +{ + int res, len, num; + unsigned int unum; + char * commap; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + commap = (char *)strchr(buf + 1, ','); + if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { + res = sscanf(buf + 2, "%x", &unum); + num = unum; + } else if (commap && ('H' == toupper((int)*(commap - 1)))) { + res = sscanf(buf, "%x", &unum); + num = unum; + } else if ((NULL == commap) && ('H' == toupper((int)buf[len - 1]))) { + res = sscanf(buf, "%x", &unum); + num = unum; + } else + res = sscanf(buf, "%d", &num); + if (1 == res) + return num; + else + return -1; +} + +/* If the number in 'buf' can be decoded or the multiplier is unknown + * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a decimal + * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)). + * Main (SI) multipliers supported: K, M, G, T, P. Ignore leading spaces + * and tabs; accept comma, hyphen, space, tab and hash as terminator. */ +int64_t +sg_get_llnum(const char * buf) +{ + int res, len, n; + int64_t num, ll; + uint64_t unum; + char * cp; + const char * b; + char c = 'c'; + char c2 = '\0'; /* keep static checker happy */ + char c3 = '\0'; /* keep static checker happy */ + char lb[32]; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1LL; + len = strlen(buf); + n = strspn(buf, " \t"); + if (n > 0) { + if (n == len) + return -1LL; + buf += n; + len -= n; + } + /* following hack to keep C++ happy */ + cp = strpbrk((char *)buf, " \t,#-"); + if (cp) { + len = cp - buf; + n = (int)sizeof(lb) - 1; + len = (len < n) ? len : n; + memcpy(lb, buf, len); + lb[len] = '\0'; + b = lb; + } else + b = buf; + if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) { + res = sscanf(b + 2, "%" SCNx64 , &unum); + num = unum; + } else if ('H' == toupper((int)b[len - 1])) { + res = sscanf(b, "%" SCNx64 , &unum); + num = unum; + } else + res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3); + if (res < 1) + return -1LL; + else if (1 == res) + return num; + else { + if (res > 2) + c2 = toupper((int)c2); + if (res > 3) + c3 = toupper((int)c3); + switch (toupper((int)c)) { + case 'C': + return num; + case 'W': + return num * 2; + case 'B': + return num * 512; + case 'K': + if (2 == res) + return num * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1024; + return -1LL; + case 'M': + if (2 == res) + return num * 1048576; + if (('B' == c2) || ('D' == c2)) + return num * 1000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1048576; + return -1LL; + case 'G': + if (2 == res) + return num * 1073741824; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1073741824; + return -1LL; + case 'T': + if (2 == res) + return num * 1099511627776LL; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000000LL; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1099511627776LL; + return -1LL; + case 'P': + if (2 == res) + return num * 1099511627776LL * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000000LL * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1099511627776LL * 1024; + return -1LL; + case 'X': + cp = (char *)strchr(b, 'x'); + if (NULL == cp) + cp = (char *)strchr(b, 'X'); + if (cp) { + ll = sg_get_llnum(cp + 1); + if (-1LL != ll) + return num * ll; + } + return -1LL; + default: + pr2ws("unrecognized multiplier\n"); + return -1LL; + } + } +} + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int64_t +sg_get_llnum_nomult(const char * buf) +{ + int res, len; + int64_t num; + uint64_t unum; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { + res = sscanf(buf + 2, "%" SCNx64 "", &unum); + num = unum; + } else if ('H' == toupper(buf[len - 1])) { + res = sscanf(buf, "%" SCNx64 "", &unum); + num = unum; + } else + res = sscanf(buf, "%" SCNd64 "", &num); + return (1 == res) ? num : -1; +} + +/* Extract character sequence from ATA words as in the model string + * in a IDENTIFY DEVICE response. Returns number of characters + * written to 'ochars' before 0 character is found or 'num' words + * are processed. */ +int +sg_ata_get_chars(const uint16_t * word_arr, int start_word, + int num_words, bool is_big_endian, char * ochars) +{ + int k; + uint16_t s; + char a, b; + char * op = ochars; + + for (k = start_word; k < (start_word + num_words); ++k) { + s = word_arr[k]; + if (is_big_endian) { + a = s & 0xff; + b = (s >> 8) & 0xff; + } else { + a = (s >> 8) & 0xff; + b = s & 0xff; + } + if (a == 0) + break; + *op++ = a; + if (b == 0) + break; + *op++ = b; + } + return op - ochars; +} + +int +pr2serr(const char * fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + n = vfprintf(stderr, fmt, args); + va_end(args); + return n; +} + +#ifdef SG_LIB_FREEBSD +#include <sys/param.h> +#elif defined(SG_LIB_WIN32) +#include <windows.h> + +static bool got_page_size = false; +static uint32_t win_page_size; +#endif + +uint32_t +sg_get_page_size(void) +{ +#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) + return sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */ +#elif defined(SG_LIB_WIN32) + if (! got_page_size) { + SYSTEM_INFO si; + + GetSystemInfo(&si); + win_page_size = si.dwPageSize; + got_page_size = true; + } + return win_page_size; +#elif defined(SG_LIB_FREEBSD) + return PAGE_SIZE; +#else + return 4096; /* give up, pick likely figure */ +#endif +} + +/* Returns pointer to heap (or NULL) that is aligned to a align_to byte + * boundary. Sends back *buff_to_free pointer in third argument that may be + * different from the return value. If it is different then the *buff_to_free + * pointer should be freed (rather than the returned value) when the heap is + * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all + * returned heap to zeros. If num_bytes is 0 then set to page size. */ +uint8_t * +sg_memalign(uint32_t num_bytes, uint32_t align_to, uint8_t ** buff_to_free, + bool vb) +{ + size_t psz; + uint8_t * res; + + if (buff_to_free) /* make sure buff_to_free is NULL if alloc fails */ + *buff_to_free = NULL; + psz = (align_to > 0) ? align_to : sg_get_page_size(); + if (0 == num_bytes) + num_bytes = psz; /* ugly to handle otherwise */ + +#ifdef HAVE_POSIX_MEMALIGN + { + int err; + void * wp = NULL; + + err = posix_memalign(&wp, psz, num_bytes); + if (err || (NULL == wp)) { + pr2ws("%s: posix_memalign: error [%d], out of memory?\n", + __func__, err); + return NULL; + } + memset(wp, 0, num_bytes); + if (buff_to_free) + *buff_to_free = (uint8_t *)wp; + res = (uint8_t *)wp; + if (vb) { + pr2ws("%s: posix_ma, len=%d, ", __func__, num_bytes); + if (buff_to_free) + pr2ws("wrkBuffp=%p, ", (void *)res); + pr2ws("psz=%u, rp=%p\n", (unsigned int)psz, (void *)res); + } + return res; + } +#else + { + void * wrkBuff; + sg_uintptr_t align_1 = psz - 1; + + wrkBuff = (uint8_t *)calloc(num_bytes + psz, 1); + if (NULL == wrkBuff) { + if (buff_to_free) + *buff_to_free = NULL; + return NULL; + } else if (buff_to_free) + *buff_to_free = (uint8_t *)wrkBuff; + res = (uint8_t *)(void *) + (((sg_uintptr_t)wrkBuff + align_1) & (~align_1)); + if (vb) { + pr2ws("%s: hack, len=%d, ", __func__, num_bytes); + if (buff_to_free) + pr2ws("buff_to_free=%p, ", wrkBuff); + pr2ws("align_1=%lu, rp=%p\n", (unsigned long)align_1, (void *)res); + } + return res; + } +#endif +} + +const char * +sg_lib_version() +{ + return sg_lib_version_str; +} + + +#ifdef SG_LIB_MINGW +/* Non Unix OSes distinguish between text and binary files. + Set text mode on fd. Does nothing in Unix. Returns negative number on + failure. */ + +#include <unistd.h> +#include <fcntl.h> + +int +sg_set_text_mode(int fd) +{ + return setmode(fd, O_TEXT); +} + +/* Set binary mode on fd. Does nothing in Unix. Returns negative number on + failure. */ +int +sg_set_binary_mode(int fd) +{ + return setmode(fd, O_BINARY); +} + +#else +/* For Unix the following functions are dummies. */ +int +sg_set_text_mode(int fd) +{ + return fd; /* fd should be >= 0 */ +} + +int +sg_set_binary_mode(int fd) +{ + return fd; +} + +#endif diff --git a/tools/sg_write_buffer/sg_lib_data.c b/tools/sg_write_buffer/sg_lib_data.c new file mode 100644 index 0000000..e59c355 --- /dev/null +++ b/tools/sg_write_buffer/sg_lib_data.c @@ -0,0 +1,1688 @@ +/* + * Copyright (c) 2007-2018 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. + */ + +#include <stdlib.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#else +#define SG_SCSI_STRINGS 1 +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" + + +const char * sg_lib_version_str = "2.38 20180122";/* spc5r17, sbc4r15 */ + + +/* indexed by pdt; those that map to own index do not decay */ +int sg_lib_pdt_decay_arr[32] = { + PDT_DISK, PDT_TAPE, PDT_TAPE /* printer */, PDT_PROCESSOR, + PDT_DISK /* WO */, PDT_MMC, PDT_SCANNER, PDT_DISK /* optical */, + PDT_MCHANGER, PDT_COMMS, 0xa, 0xb, + PDT_SAC, PDT_SES, PDT_DISK /* rbc */, PDT_OCRW, + PDT_BCC, PDT_OSD, PDT_TAPE /* adc */, PDT_SMD, + PDT_DISK /* zbc */, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, PDT_WLUN, PDT_UNKNOWN +}; + +#ifdef SG_SCSI_STRINGS +struct sg_lib_value_name_t sg_lib_normal_opcodes[] = { + {0, 0, "Test Unit Ready"}, + {0x1, 0, "Rezero Unit"}, + {0x1, PDT_TAPE, "Rewind"}, + {0x3, 0, "Request Sense"}, + {0x4, 0, "Format Unit"}, + {0x4, PDT_TAPE, "Format medium"}, + {0x4, PDT_PRINTER, "Format"}, + {0x5, 0, "Read Block Limits"}, + {0x7, 0, "Reassign Blocks"}, + {0x7, PDT_MCHANGER, "Initialize element status"}, + {0x8, 0, "Read(6)"}, /* obsolete in sbc3r30 */ + {0x8, PDT_PROCESSOR, "Receive"}, + {0xa, 0, "Write(6)"}, /* obsolete in sbc3r30 */ + {0xa, PDT_PRINTER, "Print"}, + {0xa, PDT_PROCESSOR, "Send"}, + {0xb, 0, "Seek(6)"}, + {0xb, PDT_TAPE, "Set capacity"}, + {0xb, PDT_PRINTER, "Slew and print"}, + {0xf, 0, "Read reverse(6)"}, + {0x10, 0, "Write filemarks(6)"}, + {0x10, PDT_PRINTER, "Synchronize buffer"}, + {0x11, 0, "Space(6)"}, + {0x12, 0, "Inquiry"}, + {0x13, 0, "Verify(6)"}, /* SSC */ + {0x14, 0, "Recover buffered data"}, + {0x15, 0, "Mode select(6)"}, /* SBC-3 r31 recommends Mode select(10) */ + {0x16, 0, "Reserve(6)"}, /* obsolete in SPC-4 r11 */ + {0x16, PDT_MCHANGER, "Reserve element(6)"}, + {0x17, 0, "Release(6)"}, /* obsolete in SPC-4 r11 */ + {0x17, PDT_MCHANGER, "Release element(6)"}, + {0x18, 0, "Copy"}, /* obsolete in SPC-4 r11 */ + {0x19, 0, "Erase(6)"}, + {0x1a, 0, "Mode sense(6)"}, /* SBC-3 r31 recommends Mode sense(10) */ + {0x1b, 0, "Start stop unit"}, + {0x1b, PDT_TAPE, "Load unload"}, + {0x1b, PDT_ADC, "Load unload"}, + {0x1b, PDT_PRINTER, "Stop print"}, + {0x1c, 0, "Receive diagnostic results"}, + {0x1d, 0, "Send diagnostic"}, + {0x1e, 0, "Prevent allow medium removal"}, + {0x23, 0, "Read Format capacities"}, + {0x24, 0, "Set window"}, + {0x25, 0, "Read capacity(10)"}, + /* SBC-3 r31 recommends Read capacity(16) */ + {0x25, PDT_OCRW, "Read card capacity"}, + {0x28, 0, "Read(10)"}, /* SBC-3 r31 recommends Read(16) */ + {0x29, 0, "Read generation"}, + {0x2a, 0, "Write(10)"}, /* SBC-3 r31 recommends Write(16) */ + {0x2b, 0, "Seek(10)"}, + {0x2b, PDT_TAPE, "Locate(10)"}, + {0x2b, PDT_MCHANGER, "Position to element"}, + {0x2c, 0, "Erase(10)"}, + {0x2d, 0, "Read updated block"}, + {0x2e, 0, "Write and verify(10)"}, + /* SBC-3 r31 recommends Write and verify(16) */ + {0x2f, 0, "Verify(10)"}, /* SBC-3 r31 recommends Verify(16) */ + {0x30, 0, "Search data high(10)"}, + {0x31, 0, "Search data equal(10)"}, + {0x32, 0, "Search data low(10)"}, + {0x33, 0, "Set limits(10)"}, + {0x34, 0, "Pre-fetch(10)"}, /* SBC-3 r31 recommends Pre-fetch(16) */ + {0x34, PDT_TAPE, "Read position"}, + {0x35, 0, "Synchronize cache(10)"}, + /* SBC-3 r31 recommends Synchronize cache(16) */ + {0x36, 0, "Lock unlock cache(10)"}, + {0x37, 0, "Read defect data(10)"}, + /* SBC-3 r31 recommends Read defect data(12) */ + {0x37, PDT_MCHANGER, "Initialize element status with range"}, + {0x38, 0, "Medium scan"}, + {0x39, 0, "Compare"}, /* obsolete in SPC-4 r11 */ + {0x3a, 0, "Copy and verify"}, /* obsolete in SPC-4 r11 */ + {0x3b, 0, "Write buffer"}, + {0x3c, 0, "Read buffer(10)"}, + {0x3d, 0, "Update block"}, + {0x3e, 0, "Read long(10)"}, /* obsolete in SBC-4 r7 */ + {0x3f, 0, "Write long(10)"}, /* SBC-3 r31 recommends Write long(16) */ + {0x40, 0, "Change definition"}, /* obsolete in SPC-4 r11 */ + {0x41, 0, "Write same(10)"}, /* SBC-3 r31 recommends Write same(16) */ + {0x42, 0, "Unmap"}, /* added SPC-4 rev 18 */ + {0x42, PDT_MMC, "Read sub-channel"}, + {0x43, PDT_MMC, "Read TOC/PMA/ATIP"}, + {0x44, 0, "Report density support"}, + {0x45, PDT_MMC, "Play audio(10)"}, + {0x46, PDT_MMC, "Get configuration"}, + {0x47, PDT_MMC, "Play audio msf"}, + {0x48, 0, "Sanitize"}, + {0x4a, PDT_MMC, "Get event status notification"}, + {0x4b, PDT_MMC, "Pause/resume"}, + {0x4c, 0, "Log select"}, + {0x4d, 0, "Log sense"}, + {0x4e, 0, "Stop play/scan"}, + {0x50, 0, "Xdwrite(10)"}, /* obsolete in SBC-3 r31 */ + {0x51, 0, "Xpwrite(10)"}, /* obsolete in SBC-4 r15 */ + {0x51, PDT_MMC, "Read disk information"}, + {0x52, 0, "Xdread(10)"}, /* obsolete in SBC-3 r31 */ + {0x52, PDT_MMC, "Read track information"}, + {0x53, 0, "Xdwriteread(10)"}, /* obsolete in SBC-4 r15 */ + {0x54, 0, "Send OPC information"}, + {0x55, 0, "Mode select(10)"}, + {0x56, 0, "Reserve(10)"}, /* obsolete in SPC-4 r11 */ + {0x56, PDT_MCHANGER, "Reserve element(10)"}, + {0x57, 0, "Release(10)"}, /* obsolete in SPC-4 r11 */ + {0x57, PDT_MCHANGER, "Release element(10)"}, + {0x58, 0, "Repair track"}, + {0x5a, 0, "Mode sense(10)"}, + {0x5b, 0, "Close track/session"}, + {0x5c, 0, "Read buffer capacity"}, + {0x5d, 0, "Send cue sheet"}, + {0x5e, 0, "Persistent reserve in"}, + {0x5f, 0, "Persistent reserve out"}, + {0x7e, 0, "Extended cdb (XCBD)"}, /* added in SPC-4 r12 */ + {0x80, 0, "Xdwrite extended(16)"}, /* obsolete in SBC-4 r15 */ + {0x80, PDT_TAPE, "Write filemarks(16)"}, + {0x81, 0, "Rebuild(16)"}, + {0x81, PDT_TAPE, "Read reverse(16)"}, + {0x82, 0, "Regenerate(16)"}, + {0x83, 0, "Third party copy out"}, /* Extended copy, before spc4r34 */ + /* Following was "Receive copy results", before spc4r34 */ + {0x84, 0, "Third party copy in"}, + {0x85, 0, "ATA pass-through(16)"}, /* was 0x98 in spc3 rev21c */ + {0x86, 0, "Access control in"}, + {0x87, 0, "Access control out"}, + {0x88, 0, "Read(16)"}, + {0x89, 0, "Compare and write"}, + {0x8a, 0, "Write(16)"}, + {0x8b, 0, "Orwrite(16)"}, + {0x8c, 0, "Read attribute"}, + {0x8d, 0, "Write attribute"}, + {0x8e, 0, "Write and verify(16)"}, + {0x8f, 0, "Verify(16)"}, + {0x90, 0, "Pre-fetch(16)"}, + {0x91, 0, "Synchronize cache(16)"}, + {0x91, PDT_TAPE, "Space(16)"}, + {0x92, 0, "Lock unlock cache(16)"}, + {0x92, PDT_TAPE, "Locate(16)"}, + {0x93, 0, "Write same(16)"}, + {0x93, PDT_TAPE, "Erase(16)"}, + {0x94, PDT_ZBC, "ZBC out"}, /* new sbc4r04, has service actions */ + {0x95, PDT_ZBC, "ZBC in"}, /* new sbc4r04, has service actions */ + {0x9a, 0, "Write stream(16)"}, /* added sbc4r07 */ + {0x9b, 0, "Read buffer(16)"}, /* added spc5r02 */ + {0x9c, 0, "Write atomic(16)"}, + {0x9d, 0, "Service action bidirectional"}, /* added spc4r35 */ + {0x9e, 0, "Service action in(16)"}, + {0x9f, 0, "Service action out(16)"}, + {0xa0, 0, "Report luns"}, + {0xa1, 0, "ATA pass-through(12)"}, + {0xa1, PDT_MMC, "Blank"}, + {0xa2, 0, "Security protocol in"}, + {0xa3, 0, "Maintenance in"}, + {0xa3, PDT_MMC, "Send key"}, + {0xa4, 0, "Maintenance out"}, + {0xa4, PDT_MMC, "Report key"}, + {0xa5, 0, "Move medium"}, + {0xa5, PDT_MMC, "Play audio(12)"}, + {0xa6, 0, "Exchange medium"}, + {0xa6, PDT_MMC, "Load/unload medium"}, + {0xa7, 0, "Move medium attached"}, + {0xa7, PDT_MMC, "Set read ahead"}, + {0xa8, 0, "Read(12)"}, /* SBC-3 r31 recommends Read(16) */ + {0xa9, 0, "Service action out(12)"}, + {0xaa, 0, "Write(12)"}, /* SBC-3 r31 recommends Write(16) */ + {0xab, 0, "Service action in(12)"}, + {0xac, 0, "erase(12)"}, + {0xac, PDT_MMC, "Get performance"}, + {0xad, PDT_MMC, "Read DVD/BD structure"}, + {0xae, 0, "Write and verify(12)"}, + /* SBC-3 r31 recommends Write and verify(16) */ + {0xaf, 0, "Verify(12)"}, /* SBC-3 r31 recommends Verify(16) */ + {0xb0, 0, "Search data high(12)"}, + {0xb1, 0, "Search data equal(12)"}, + {0xb1, PDT_MCHANGER, "Open/close import/export element"}, + {0xb2, 0, "Search data low(12)"}, + {0xb3, 0, "Set limits(12)"}, + {0xb4, 0, "Read element status attached"}, + {0xb5, 0, "Security protocol out"}, + {0xb5, PDT_MCHANGER, "Request volume element address"}, + {0xb6, 0, "Send volume tag"}, + {0xb6, PDT_MMC, "Set streaming"}, + {0xb7, 0, "Read defect data(12)"}, + {0xb8, 0, "Read element status"}, + {0xb9, 0, "Read CD msf"}, + {0xba, 0, "Redundancy group in"}, + {0xba, PDT_MMC, "Scan"}, + {0xbb, 0, "Redundancy group out"}, + {0xbb, PDT_MMC, "Set CD speed"}, + {0xbc, 0, "Spare in"}, + {0xbd, 0, "Spare out"}, + {0xbd, PDT_MMC, "Mechanism status"}, + {0xbe, 0, "Volume set in"}, + {0xbe, PDT_MMC, "Read CD"}, + {0xbf, 0, "Volume set out"}, + {0xbf, PDT_MMC, "Send DVD/BD structure"}, + {0xffff, 0, NULL}, +}; + +/* Read buffer(10) [0x3c] and Read buffer(16) [0x9b] service actions (sa), + * need prefix */ +struct sg_lib_value_name_t sg_lib_read_buff_arr[] = { + {0x0, 0, "combined header and data [or multiple modes]"}, + {0x2, 0, "data"}, + {0x3, 0, "descriptor"}, + {0xa, 0, "read data from echo buffer"}, + {0xb, 0, "echo buffer descriptor"}, + {0x1a, 0, "enable expander comms protocol and echo buffer"}, + {0x1c, 0, "error history"}, + {0xffff, 0, NULL}, +}; + +/* Write buffer [0x3b] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_write_buff_arr[] = { + {0x0, 0, "combined header and data [or multiple modes]"}, + {0x2, 0, "data"}, + {0x4, 0, "download microcode and activate"}, + {0x5, 0, "download microcode, save, and activate"}, + {0x6, 0, "download microcode with offsets and activate"}, + {0x7, 0, "download microcode with offsets, save, and activate"}, + {0xa, 0, "write data to echo buffer"}, + {0xd, 0, "download microcode with offsets, select activation events, " + "save and defer activate"}, + {0xe, 0, "download microcode with offsets, save and defer activate"}, + {0xf, 0, "activate deferred microcode"}, + {0x1a, 0, "enable expander comms protocol and echo buffer"}, + {0x1b, 0, "disable expander comms protocol"}, + {0x1c, 0, "download application client error history"}, + {0xffff, 0, NULL}, +}; + +/* Read position (SSC) [0x34] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_read_pos_arr[] = { + {0x0, PDT_TAPE, "short form - block id"}, + {0x1, PDT_TAPE, "short form - vendor specific"}, + {0x6, PDT_TAPE, "long form"}, + {0x8, PDT_TAPE, "extended form"}, + {0xffff, 0, NULL}, +}; + +/* Maintenance in [0xa3] service actions */ +struct sg_lib_value_name_t sg_lib_maint_in_arr[] = { + {0x0, PDT_SAC, "Report assigned/unassigned p_extent"}, + {0x1, PDT_SAC, "Report component device"}, + {0x2, PDT_SAC, "Report component device attachments"}, + {0x3, PDT_SAC, "Report peripheral device"}, + {0x4, PDT_SAC, "Report peripheral device associations"}, + {0x5, 0, "Report identifying information"}, + /* was "Report device identifier" prior to spc4r07 */ + {0x6, PDT_SAC, "Report states"}, + {0x7, PDT_SAC, "Report device identification"}, + {0x8, PDT_SAC, "Report unconfigured capacity"}, + {0x9, PDT_SAC, "Report supported configuration method"}, + {0xa, 0, "Report target port groups"}, + {0xb, 0, "Report aliases"}, + {0xc, 0, "Report supported operation codes"}, + {0xd, 0, "Report supported task management functions"}, + {0xe, 0, "Report priority"}, + {0xf, 0, "Report timestamp"}, + {0x10, 0, "Management protocol in"}, + {0x1d, PDT_DISK, "Report provisioning initialization pattern"}, + /* added in sbc4r07, shares sa 0x1d with ssc5r01 (tape) */ + {0x1d, PDT_TAPE, "Receive recommended access order"}, + {0x1e, PDT_TAPE, "Read dynamic runtime attribute"}, + {0x1e, PDT_ADC, "Report automation device attributes"}, + {0x1f, 0, "Maintenance in vendor specific"}, + {0xffff, 0, NULL}, +}; + +/* Maintenance out [0xa4] service actions */ +struct sg_lib_value_name_t sg_lib_maint_out_arr[] = { + {0x0, PDT_SAC, "Add peripheral device / component device"}, + {0x1, PDT_SAC, "Attach to component device"}, + {0x2, PDT_SAC, "Exchange p_extent"}, + {0x3, PDT_SAC, "Exchange peripheral device / component device"}, + {0x4, PDT_SAC, "Instruct component device"}, + {0x5, PDT_SAC, "Remove peripheral device / component device"}, + {0x6, 0, "Set identifying information"}, + /* was "Set device identifier" prior to spc4r07 */ + {0x7, PDT_SAC, "Break peripheral device / component device"}, + {0xa, 0, "Set target port groups"}, + {0xb, 0, "Change aliases"}, + {0xc, 0, "Remove I_T nexus"}, + {0xe, 0, "Set priority"}, + {0xf, 0, "Set timestamp"}, + {0x10, 0, "Management protocol out"}, + {0x1d, PDT_TAPE, "Generate recommended access order"}, + {0x1e, PDT_TAPE, "write dynamic runtime attribute"}, + {0x1e, PDT_ADC, "Set automation device attributes"}, + {0x1f, 0, "Maintenance out vendor specific"}, + {0xffff, 0, NULL}, +}; + +/* Sanitize [0x48] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = { + {0x1, 0, "overwrite"}, + {0x2, 0, "block erase"}, + {0x3, 0, "cryptographic erase"}, + {0x1f, 0, "exit failure mode"}, + {0xffff, 0, NULL}, +}; + +/* Service action in(12) [0xab] service actions */ +struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { + {0x1, 0, "Read media serial number"}, + {0xffff, 0, NULL}, +}; + +/* Service action out(12) [0xa9] service actions */ +struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { + {0xff, 0, "Impossible command name"}, + {0xffff, 0, NULL}, +}; + +/* Service action in(16) [0x9e] service actions */ +struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { + {0xf, 0, "Receive binding report"}, /* added spc5r11 */ + {0x10, 0, "Read capacity(16)"}, + {0x11, 0, "Read long(16)"}, /* obsolete in SBC-4 r7 */ + {0x12, 0, "Get LBA status(16)"}, /* 32 byte variant added in sbc4r14 */ + {0x13, 0, "Report referrals"}, + {0x14, 0, "Stream control"}, + {0x15, 0, "Background control"}, + {0x16, 0, "Get stream status"}, + {0x17, 0, "Get physical element status"}, /* added sbc4r13 */ + {0x18, 0, "Remove element and truncate"}, /* added sbc4r13 */ + {0xffff, 0, NULL}, +}; + +/* Service action out(16) [0x9f] service actions */ +struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { + {0x0b, 0, "Test bind"}, /* added spc5r13 */ + {0x0c, 0, "Prepare bind report"}, /* added spc5r11 */ + {0x0d, 0, "Set affiliation"}, + {0x0e, 0, "Bind"}, + {0x0f, 0, "Unbind"}, + {0x11, 0, "Write long(16)"}, + {0x12, 0, "Write scattered(16)"}, /* added sbc4r11 */ + {0x14, PDT_ZBC, "Reset write pointer"}, + {0x1f, PDT_ADC, "Notify data transfer device(16)"}, + {0xffff, 0, NULL}, +}; + +/* Service action bidirectional [0x9d] service actions */ +struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { + {0xffff, 0, NULL}, +}; + +/* Persistent reserve in [0x5e] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { + {0x0, 0, "read keys"}, + {0x1, 0, "read reservation"}, + {0x2, 0, "report capabilities"}, + {0x3, 0, "read full status"}, + {0xffff, 0, NULL}, +}; + +/* Persistent reserve out [0x5f] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { + {0x0, 0, "register"}, + {0x1, 0, "reserve"}, + {0x2, 0, "release"}, + {0x3, 0, "clear"}, + {0x4, 0, "preempt"}, + {0x5, 0, "preempt and abort"}, + {0x6, 0, "register and ignore existing key"}, + {0x7, 0, "register and move"}, + {0x8, 0, "replace lost reservation"}, + {0xffff, 0, NULL}, +}; + +/* Third party copy in [0x83] service actions + * Opcode 'Receive copy results' was renamed 'Third party copy in' in spc4r34 + * LID1 is an abbreviation of List Identifier length of 1 byte */ +struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { + {0x0, 0, "Extended copy(LID1)"}, + {0x1, 0, "Extended copy(LID4)"}, + {0x10, 0, "Populate token"}, + {0x11, 0, "Write using token"}, + {0x1c, 0, "Copy operation abort"}, + {0xffff, 0, NULL}, +}; + +/* Third party copy out [0x84] service actions + * Opcode 'Extended copy' was renamed 'Third party copy out' in spc4r34 + * LID4 is an abbreviation of List Identifier length of 4 bytes */ +struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { + {0x0, 0, "Receive copy status(LID1)"}, + {0x1, 0, "Receive copy data(LID1)"}, + {0x3, 0, "Receive copy operating parameters"}, + {0x4, 0, "Receive copy failure details(LID1)"}, + {0x5, 0, "Receive copy status(LID4)"}, + {0x6, 0, "Receive copy data(LID4)"}, + {0x7, 0, "Receive ROD token information"}, + {0x8, 0, "Report all ROD tokens"}, + {0xffff, 0, NULL}, +}; + +/* Variable length cdb [0x7f] service actions (more than 16 bytes long) */ +struct sg_lib_value_name_t sg_lib_variable_length_arr[] = { + {0x1, 0, "Rebuild(32)"}, + {0x2, 0, "Regenerate(32)"}, + {0x3, 0, "Xdread(32)"}, /* obsolete in SBC-3 r31 */ + {0x4, 0, "Xdwrite(32)"}, /* obsolete in SBC-3 r31 */ + {0x5, 0, "Xdwrite extended(32)"}, /* obsolete in SBC-4 r15 */ + {0x6, 0, "Xpwrite(32)"}, /* obsolete in SBC-4 r15 */ + {0x7, 0, "Xdwriteread(32)"}, /* obsolete in SBC-4 r15 */ + {0x8, 0, "Xdwrite extended(64)"}, /* obsolete in SBC-4 r15 */ + {0x9, 0, "Read(32)"}, + {0xa, 0, "Verify(32)"}, + {0xb, 0, "Write(32)"}, + {0xc, 0, "Write and verify(32)"}, + {0xd, 0, "Write same(32)"}, + {0xe, 0, "Orwrite(32)"}, /* added sbc3r25 */ + {0xf, 0, "Atomic write(32)"}, /* added sbc4r02 */ + {0x10, 0, "Write stream(32)"}, /* added sbc4r07 */ + {0x11, 0, "Write scattered(32)"}, /* added sbc4r11 */ + {0x12, 0, "Get LBA status(32)"}, /* added sbc4r14 */ + {0x1800, 0, "Receive credential"}, + {0x1ff0, 0, "ATA pass-through(32)"},/* added sat4r05 */ + {0x8801, 0, "Format OSD (osd)"}, + {0x8802, 0, "Create (osd)"}, + {0x8803, 0, "List (osd)"}, + {0x8805, 0, "Read (osd)"}, + {0x8806, 0, "Write (osd)"}, + {0x8807, 0, "Append (osd)"}, + {0x8808, 0, "Flush (osd)"}, + {0x880a, 0, "Remove (osd)"}, + {0x880b, 0, "Create partition (osd)"}, + {0x880c, 0, "Remove partition (osd)"}, + {0x880e, 0, "Get attributes (osd)"}, + {0x880f, 0, "Set attributes (osd)"}, + {0x8812, 0, "Create and write (osd)"}, + {0x8815, 0, "Create collection (osd)"}, + {0x8816, 0, "Remove collection (osd)"}, + {0x8817, 0, "List collection (osd)"}, + {0x8818, 0, "Set key (osd)"}, + {0x8819, 0, "Set master key (osd)"}, + {0x881a, 0, "Flush collection (osd)"}, + {0x881b, 0, "Flush partition (osd)"}, + {0x881c, 0, "Flush OSD (osd)"}, + {0x8880, 0, "Object structure check (osd-2)"}, + {0x8881, 0, "Format OSD (osd-2)"}, + {0x8882, 0, "Create (osd-2)"}, + {0x8883, 0, "List (osd-2)"}, + {0x8884, 0, "Punch (osd-2)"}, + {0x8885, 0, "Read (osd-2)"}, + {0x8886, 0, "Write (osd-2)"}, + {0x8887, 0, "Append (osd-2)"}, + {0x8888, 0, "Flush (osd-2)"}, + {0x8889, 0, "Clear (osd-2)"}, + {0x888a, 0, "Remove (osd-2)"}, + {0x888b, 0, "Create partition (osd-2)"}, + {0x888c, 0, "Remove partition (osd-2)"}, + {0x888e, 0, "Get attributes (osd-2)"}, + {0x888f, 0, "Set attributes (osd-2)"}, + {0x8892, 0, "Create and write (osd-2)"}, + {0x8895, 0, "Create collection (osd-2)"}, + {0x8896, 0, "Remove collection (osd-2)"}, + {0x8897, 0, "List collection (osd-2)"}, + {0x8898, 0, "Set key (osd-2)"}, + {0x8899, 0, "Set master key (osd-2)"}, + {0x889a, 0, "Flush collection (osd-2)"}, + {0x889b, 0, "Flush partition (osd-2)"}, + {0x889c, 0, "Flush OSD (osd-2)"}, + {0x88a0, 0, "Query (osd-2)"}, + {0x88a1, 0, "Remove member objects (osd-2)"}, + {0x88a2, 0, "Get member attributes (osd-2)"}, + {0x88a3, 0, "Set member attributes (osd-2)"}, + {0x88b1, 0, "Read map (osd-2)"}, + {0x8f7c, 0, "Perform SCSI command (osd-2)"}, + {0x8f7d, 0, "Perform task management function (osd-2)"}, + {0x8f7e, 0, "Perform SCSI command (osd)"}, + {0x8f7f, 0, "Perform task management function (osd)"}, + {0xffff, 0, NULL}, +}; + +/* Zoning out [0x94] service actions */ +struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = { + {0x1, PDT_ZBC, "Close zone"}, + {0x2, PDT_ZBC, "Finish zone"}, + {0x3, PDT_ZBC, "Open zone"}, + {0x4, PDT_ZBC, "Reset write pointer"}, + {0xffff, 0, NULL}, +}; + +/* Zoning in [0x95] service actions */ +struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = { + {0x0, PDT_ZBC, "Report zones"}, + {0xffff, 0, NULL}, +}; + +/* Read attribute [0x8c] service actions */ +struct sg_lib_value_name_t sg_lib_read_attr_arr[] = { + {0x0, 0, "attribute values"}, + {0x1, 0, "attribute list"}, + {0x2, 0, "logical volume list"}, + {0x3, 0, "partition list"}, + {0x5, 0, "supported attributes"}, + {0xffff, 0, NULL}, +}; + +#else /* SG_SCSI_STRINGS */ + +struct sg_lib_value_name_t sg_lib_normal_opcodes[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_read_buff_arr[] = { /* opcode 0x3c */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_write_buff_arr[] = { /* opcode 0x3b */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_read_pos_arr[] = { /* opcode 0x34 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_maint_in_arr[] = { /* opcode 0xa3 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_maint_out_arr[] = { /* opcode 0xa4 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = { /* opcode 0x94 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { /* opcode 0xab */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { /* opcode 0xa9 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { /* opcode 0x9e */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { /* opcode 0x9f */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { /* opcode 0x9d */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { /* opcode 0x5e */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { /* opcode 0x5f */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { /* opcode 0x83 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { /* opcode 0x84 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_variable_length_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_read_attr_arr[] = { + {0xffff, 0, NULL}, +}; + +#endif /* SG_SCSI_STRINGS */ + +/* A conveniently formatted list of SCSI ASC/ASCQ codes and their + * corresponding text can be found at: www.t10.org/lists/asc-num.txt + * The following should match asc-num.txt dated 20150423 */ + +#ifdef SG_SCSI_STRINGS +struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] = +{ + {0x40,0x01,0x7f,"Ram failure [0x%x]"}, + {0x40,0x80,0xff,"Diagnostic failure on component [0x%x]"}, + {0x41,0x01,0xff,"Data path failure [0x%x]"}, + {0x42,0x01,0xff,"Power-on or self-test failure [0x%x]"}, + {0x4d,0x00,0xff,"Tagged overlapped commands [0x%x]"}, + {0x70,0x00,0xff,"Decompression exception short algorithm id of 0x%x"}, + {0, 0, 0, NULL} +}; + +struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] = +{ + {0x00,0x00,"No additional sense information"}, + {0x00,0x01,"Filemark detected"}, + {0x00,0x02,"End-of-partition/medium detected"}, + {0x00,0x03,"Setmark detected"}, + {0x00,0x04,"Beginning-of-partition/medium detected"}, + {0x00,0x05,"End-of-data detected"}, + {0x00,0x06,"I/O process terminated"}, + {0x00,0x07,"Programmable early warning detected"}, + {0x00,0x11,"Audio play operation in progress"}, + {0x00,0x12,"Audio play operation paused"}, + {0x00,0x13,"Audio play operation successfully completed"}, + {0x00,0x14,"Audio play operation stopped due to error"}, + {0x00,0x15,"No current audio status to return"}, + {0x00,0x16,"operation in progress"}, + {0x00,0x17,"Cleaning requested"}, + {0x00,0x18,"Erase operation in progress"}, + {0x00,0x19,"Locate operation in progress"}, + {0x00,0x1a,"Rewind operation in progress"}, + {0x00,0x1b,"Set capacity operation in progress"}, + {0x00,0x1c,"Verify operation in progress"}, + {0x00,0x1d,"ATA pass through information available"}, + {0x00,0x1e,"Conflicting SA creation request"}, + {0x00,0x1f,"Logical unit transitioning to another power condition"}, + {0x00,0x20,"Extended copy information available"}, + {0x00,0x21,"Atomic command aborted due to ACA"}, + {0x00,0x22,"Deferred microcode is pending"}, + {0x01,0x00,"No index/sector signal"}, + {0x02,0x00,"No seek complete"}, + {0x03,0x00,"Peripheral device write fault"}, + {0x03,0x01,"No write current"}, + {0x03,0x02,"Excessive write errors"}, + {0x04,0x00,"Logical unit not ready, cause not reportable"}, + {0x04,0x01,"Logical unit is in process of becoming ready"}, + {0x04,0x02,"Logical unit not ready, " + "initializing command required"}, + {0x04,0x03,"Logical unit not ready, " + "manual intervention required"}, + {0x04,0x04,"Logical unit not ready, format in progress"}, + {0x04,0x05,"Logical unit not ready, rebuild in progress"}, + {0x04,0x06,"Logical unit not ready, recalculation in progress"}, + {0x04,0x07,"Logical unit not ready, operation in progress"}, + {0x04,0x08,"Logical unit not ready, long write in progress"}, + {0x04,0x09,"Logical unit not ready, self-test in progress"}, + {0x04,0x0a,"Logical unit " + "not accessible, asymmetric access state transition"}, + {0x04,0x0b,"Logical unit " + "not accessible, target port in standby state"}, + {0x04,0x0c,"Logical unit " + "not accessible, target port in unavailable state"}, + {0x04,0x0d,"Logical unit not ready, structure check required"}, + {0x04,0x0e,"Logical unit not ready, security session in progress"}, + {0x04,0x10,"Logical unit not ready, " + "auxiliary memory not accessible"}, + {0x04,0x11,"Logical unit not ready, " + "notify (enable spinup) required"}, + {0x04,0x12,"Logical unit not ready, offline"}, + {0x04,0x13,"Logical unit not ready, SA creation in progress"}, + {0x04,0x14,"Logical unit not ready, space allocation in progress"}, + {0x04,0x15,"Logical unit not ready, robotics disabled"}, + {0x04,0x16,"Logical unit not ready, configuration required"}, + {0x04,0x17,"Logical unit not ready, calibration required"}, + {0x04,0x18,"Logical unit not ready, a door is open"}, + {0x04,0x19,"Logical unit not ready, operating in sequential mode"}, + {0x04,0x1a,"Logical unit not ready, start stop unit command in progress"}, + {0x04,0x1b,"Logical unit not ready, sanitize in progress"}, + {0x04,0x1c,"Logical unit not ready, additional power use not yet " + "granted"}, + {0x04,0x1d,"Logical unit not ready, configuration in progress"}, + {0x04,0x1e,"Logical unit not ready, microcode activation required"}, + {0x04,0x1f,"Logical unit not ready, microcode download required"}, + {0x04,0x20,"Logical unit not ready, logical unit reset required"}, + {0x04,0x21,"Logical unit not ready, hard reset required"}, + {0x04,0x22,"Logical unit not ready, power cycle required"}, + {0x04,0x23,"Logical unit not ready, affiliation required"}, + {0x05,0x00,"Logical unit does not respond to selection"}, + {0x06,0x00,"No reference position found"}, + {0x07,0x00,"Multiple peripheral devices selected"}, + {0x08,0x00,"Logical unit communication failure"}, + {0x08,0x01,"Logical unit communication time-out"}, + {0x08,0x02,"Logical unit communication parity error"}, + {0x08,0x03,"Logical unit communication CRC error (Ultra-DMA/32)"}, + {0x08,0x04,"Unreachable copy target"}, + {0x09,0x00,"Track following error"}, + {0x09,0x01,"Tracking servo failure"}, + {0x09,0x02,"Focus servo failure"}, + {0x09,0x03,"Spindle servo failure"}, + {0x09,0x04,"Head select fault"}, + {0x09,0x05,"Vibration induced tracking error"}, + {0x0A,0x00,"Error log overflow"}, + {0x0B,0x00,"Warning"}, + {0x0B,0x01,"Warning - specified temperature exceeded"}, + {0x0B,0x02,"Warning - enclosure degraded"}, + {0x0B,0x03,"Warning - background self-test failed"}, + {0x0B,0x04,"Warning - background pre-scan detected medium error"}, + {0x0B,0x05,"Warning - background medium scan detected medium error"}, + {0x0B,0x06,"Warning - non-volatile cache now volatile"}, + {0x0B,0x07,"Warning - degraded power to non-volatile cache"}, + {0x0B,0x08,"Warning - power loss expected"}, + {0x0B,0x09,"Warning - device statistics notification active"}, + {0x0B,0x0A,"Warning - high critical temperature limit exceeded"}, + {0x0B,0x0B,"Warning - low critical temperature limit exceeded"}, + {0x0B,0x0C,"Warning - high operating temperature limit exceeded"}, + {0x0B,0x0D,"Warning - low operating temperature limit exceeded"}, + {0x0B,0x0E,"Warning - high critical humidity limit exceeded"}, + {0x0B,0x0F,"Warning - low critical humidity limit exceeded"}, + {0x0B,0x10,"Warning - high operating humidity limit exceeded"}, + {0x0B,0x11,"Warning - low operating humidity limit exceeded"}, + {0x0B,0x12,"Warning - microcode security at risk"}, + {0x0B,0x13,"Warning - microcode digital signature validation failure"}, + {0x0C,0x00,"Write error"}, + {0x0C,0x01,"Write error - recovered with auto reallocation"}, + {0x0C,0x02,"Write error - auto reallocation failed"}, + {0x0C,0x03,"Write error - recommend reassignment"}, + {0x0C,0x04,"Compression check miscompare error"}, + {0x0C,0x05,"Data expansion occurred during compression"}, + {0x0C,0x06,"Block not compressible"}, + {0x0C,0x07,"Write error - recovery needed"}, + {0x0C,0x08,"Write error - recovery failed"}, + {0x0C,0x09,"Write error - loss of streaming"}, + {0x0C,0x0A,"Write error - padding blocks added"}, + {0x0C,0x0B,"Auxiliary memory write error"}, + {0x0C,0x0C,"Write error - unexpected unsolicited data"}, + {0x0C,0x0D,"Write error - not enough unsolicited data"}, + {0x0C,0x0E,"Multiple write errors"}, + {0x0C,0x0F,"Defects in error window"}, + {0x0C,0x10,"Incomplete multiple atomic write operations"}, + {0x0C,0x11,"Write error - recovery scan needed"}, + {0x0C,0x12,"Write error - insufficient zone resources"}, + {0x0D,0x00,"Error detected by third party temporary initiator"}, + {0x0D,0x01,"Third party device failure"}, + {0x0D,0x02,"Copy target device not reachable"}, + {0x0D,0x03,"Incorrect copy target device type"}, + {0x0D,0x04,"Copy target device data underrun"}, + {0x0D,0x05,"Copy target device data overrun"}, + {0x0E,0x00,"Invalid information unit"}, + {0x0E,0x01,"Information unit too short"}, + {0x0E,0x02,"Information unit too long"}, + {0x0E,0x03,"Invalid field in command information unit"}, + {0x10,0x00,"Id CRC or ECC error"}, + {0x10,0x01,"Logical block guard check failed"}, + {0x10,0x02,"Logical block application tag check failed"}, + {0x10,0x03,"Logical block reference tag check failed"}, + {0x10,0x04,"Logical block protection error on recover buffered data"}, + {0x10,0x05,"Logical block protection method error"}, + {0x11,0x00,"Unrecovered read error"}, + {0x11,0x01,"Read retries exhausted"}, + {0x11,0x02,"Error too long to correct"}, + {0x11,0x03,"Multiple read errors"}, + {0x11,0x04,"Unrecovered read error - auto reallocate failed"}, + {0x11,0x05,"L-EC uncorrectable error"}, + {0x11,0x06,"CIRC unrecovered error"}, + {0x11,0x07,"Data re-synchronization error"}, + {0x11,0x08,"Incomplete block read"}, + {0x11,0x09,"No gap found"}, + {0x11,0x0A,"Miscorrected error"}, + {0x11,0x0B,"Unrecovered read error - recommend reassignment"}, + {0x11,0x0C,"Unrecovered read error - recommend rewrite the data"}, + {0x11,0x0D,"De-compression CRC error"}, + {0x11,0x0E,"Cannot decompress using declared algorithm"}, + {0x11,0x0F,"Error reading UPC/EAN number"}, + {0x11,0x10,"Error reading ISRC number"}, + {0x11,0x11,"Read error - loss of streaming"}, + {0x11,0x12,"Auxiliary memory read error"}, + {0x11,0x13,"Read error - failed retransmission request"}, + {0x11,0x14,"Read error - LBA marked bad by application client"}, + {0x11,0x15,"Write after sanitize required"}, + {0x12,0x00,"Address mark not found for id field"}, + {0x13,0x00,"Address mark not found for data field"}, + {0x14,0x00,"Recorded entity not found"}, + {0x14,0x01,"Record not found"}, + {0x14,0x02,"Filemark or setmark not found"}, + {0x14,0x03,"End-of-data not found"}, + {0x14,0x04,"Block sequence error"}, + {0x14,0x05,"Record not found - recommend reassignment"}, + {0x14,0x06,"Record not found - data auto-reallocated"}, + {0x14,0x07,"Locate operation failure"}, + {0x15,0x00,"Random positioning error"}, + {0x15,0x01,"Mechanical positioning error"}, + {0x15,0x02,"Positioning error detected by read of medium"}, + {0x16,0x00,"Data synchronization mark error"}, + {0x16,0x01,"Data sync error - data rewritten"}, + {0x16,0x02,"Data sync error - recommend rewrite"}, + {0x16,0x03,"Data sync error - data auto-reallocated"}, + {0x16,0x04,"Data sync error - recommend reassignment"}, + {0x17,0x00,"Recovered data with no error correction applied"}, + {0x17,0x01,"Recovered data with retries"}, + {0x17,0x02,"Recovered data with positive head offset"}, + {0x17,0x03,"Recovered data with negative head offset"}, + {0x17,0x04,"Recovered data with retries and/or circ applied"}, + {0x17,0x05,"Recovered data using previous sector id"}, + {0x17,0x06,"Recovered data without ECC - data auto-reallocated"}, + {0x17,0x07,"Recovered data without ECC - recommend reassignment"}, + {0x17,0x08,"Recovered data without ECC - recommend rewrite"}, + {0x17,0x09,"Recovered data without ECC - data rewritten"}, + {0x18,0x00,"Recovered data with error correction applied"}, + {0x18,0x01,"Recovered data with error corr. & retries applied"}, + {0x18,0x02,"Recovered data - data auto-reallocated"}, + {0x18,0x03,"Recovered data with CIRC"}, + {0x18,0x04,"Recovered data with L-EC"}, + {0x18,0x05,"Recovered data - recommend reassignment"}, + {0x18,0x06,"Recovered data - recommend rewrite"}, + {0x18,0x07,"Recovered data with ECC - data rewritten"}, + {0x18,0x08,"Recovered data with linking"}, + {0x19,0x00,"Defect list error"}, + {0x19,0x01,"Defect list not available"}, + {0x19,0x02,"Defect list error in primary list"}, + {0x19,0x03,"Defect list error in grown list"}, + {0x1A,0x00,"Parameter list length error"}, + {0x1B,0x00,"Synchronous data transfer error"}, + {0x1C,0x00,"Defect list not found"}, + {0x1C,0x01,"Primary defect list not found"}, + {0x1C,0x02,"Grown defect list not found"}, + {0x1D,0x00,"Miscompare during verify operation"}, + {0x1D,0x01,"Miscompare verify of unmapped lba"}, + {0x1E,0x00,"Recovered id with ECC correction"}, + {0x1F,0x00,"Partial defect list transfer"}, + {0x20,0x00,"Invalid command operation code"}, + {0x20,0x01,"Access denied - initiator pending-enrolled"}, + {0x20,0x02,"Access denied - no access rights"}, + {0x20,0x03,"Access denied - invalid mgmt id key"}, + {0x20,0x04,"Illegal command while in write capable state"}, + {0x20,0x05,"Write type operation while in read capable state (obs)"}, + {0x20,0x06,"Illegal command while in explicit address mode"}, + {0x20,0x07,"Illegal command while in implicit address mode"}, + {0x20,0x08,"Access denied - enrollment conflict"}, + {0x20,0x09,"Access denied - invalid LU identifier"}, + {0x20,0x0A,"Access denied - invalid proxy token"}, + {0x20,0x0B,"Access denied - ACL LUN conflict"}, + {0x20,0x0C,"Illegal command when not in append-only mode"}, + {0x20,0x0D,"Not an administrative logical unit"}, + {0x20,0x0E,"Not a subsidiary logical unit"}, + {0x20,0x0F,"Not a conglomerate logical unit"}, + {0x21,0x00,"Logical block address out of range"}, + {0x21,0x01,"Invalid element address"}, + {0x21,0x02,"Invalid address for write"}, + {0x21,0x03,"Invalid write crossing layer jump"}, + {0x21,0x04,"Unaligned write command"}, + {0x21,0x05,"Write boundary violation"}, + {0x21,0x06,"Attempt to read invalid data"}, + {0x21,0x07,"Read boundary violation"}, + {0x21,0x08,"Misaligned write command"}, + {0x22,0x00,"Illegal function (use 20 00, 24 00, or 26 00)"}, + {0x23,0x00,"Invalid token operation, cause not reportable"}, + {0x23,0x01,"Invalid token operation, unsupported token type"}, + {0x23,0x02,"Invalid token operation, remote token usage not supported"}, + {0x23,0x03,"invalid token operation, remote rod token creation not " + "supported"}, + {0x23,0x04,"Invalid token operation, token unknown"}, + {0x23,0x05,"Invalid token operation, token corrupt"}, + {0x23,0x06,"Invalid token operation, token revoked"}, + {0x23,0x07,"Invalid token operation, token expired"}, + {0x23,0x08,"Invalid token operation, token cancelled"}, + {0x23,0x09,"Invalid token operation, token deleted"}, + {0x23,0x0a,"Invalid token operation, invalid token length"}, + {0x24,0x00,"Invalid field in cdb"}, + {0x24,0x01,"CDB decryption error"}, + {0x24,0x02,"Invalid cdb field while in explicit block model (obs)"}, + {0x24,0x03,"Invalid cdb field while in implicit block model (obs)"}, + {0x24,0x04,"Security audit value frozen"}, + {0x24,0x05,"Security working key frozen"}, + {0x24,0x06,"Nonce not unique"}, + {0x24,0x07,"Nonce timestamp out of range"}, + {0x24,0x08,"Invalid xcdb"}, + {0x24,0x09,"Invalid fast format"}, + {0x25,0x00,"Logical unit not supported"}, + {0x26,0x00,"Invalid field in parameter list"}, + {0x26,0x01,"Parameter not supported"}, + {0x26,0x02,"Parameter value invalid"}, + {0x26,0x03,"Threshold parameters not supported"}, + {0x26,0x04,"Invalid release of persistent reservation"}, + {0x26,0x05,"Data decryption error"}, + {0x26,0x06,"Too many target descriptors"}, + {0x26,0x07,"Unsupported target descriptor type code"}, + {0x26,0x08,"Too many segment descriptors"}, + {0x26,0x09,"Unsupported segment descriptor type code"}, + {0x26,0x0A,"Unexpected inexact segment"}, + {0x26,0x0B,"Inline data length exceeded"}, + {0x26,0x0C,"Invalid operation for copy source or destination"}, + {0x26,0x0D,"Copy segment granularity violation"}, + {0x26,0x0E,"Invalid parameter while port is enabled"}, + {0x26,0x0F,"Invalid data-out buffer integrity check value"}, + {0x26,0x10,"Data decryption key fail limit reached"}, + {0x26,0x11,"Incomplete key-associated data set"}, + {0x26,0x12,"Vendor specific key reference not found"}, + {0x26,0x13,"Application tag mode page is invalid"}, + {0x26,0x14,"Tape stream mirroring prevented"}, + {0x26,0x15,"Copy source or copy destination not authorized"}, + {0x27,0x00,"Write protected"}, + {0x27,0x01,"Hardware write protected"}, + {0x27,0x02,"Logical unit software write protected"}, + {0x27,0x03,"Associated write protect"}, + {0x27,0x04,"Persistent write protect"}, + {0x27,0x05,"Permanent write protect"}, + {0x27,0x06,"Conditional write protect"}, + {0x27,0x07,"Space allocation failed write protect"}, + {0x27,0x08,"Zone is read only"}, + {0x28,0x00,"Not ready to ready change, medium may have changed"}, + {0x28,0x01,"Import or export element accessed"}, + {0x28,0x02,"Format-layer may have changed"}, + {0x28,0x03,"Import/export element accessed, medium changed"}, + {0x29,0x00,"Power on, reset, or bus device reset occurred"}, + {0x29,0x01,"Power on occurred"}, + {0x29,0x02,"SCSI bus reset occurred"}, + {0x29,0x03,"Bus device reset function occurred"}, + {0x29,0x04,"Device internal reset"}, + {0x29,0x05,"Transceiver mode changed to single-ended"}, + {0x29,0x06,"Transceiver mode changed to lvd"}, + {0x29,0x07,"I_T nexus loss occurred"}, + {0x2A,0x00,"Parameters changed"}, + {0x2A,0x01,"Mode parameters changed"}, + {0x2A,0x02,"Log parameters changed"}, + {0x2A,0x03,"Reservations preempted"}, + {0x2A,0x04,"Reservations released"}, + {0x2A,0x05,"Registrations preempted"}, + {0x2A,0x06,"Asymmetric access state changed"}, + {0x2A,0x07,"Implicit asymmetric access state transition failed"}, + {0x2A,0x08,"Priority changed"}, + {0x2A,0x09,"Capacity data has changed"}, + {0x2A,0x0c, "Error recovery attributes have changed"}, + {0x2A,0x0d, "Data encryption capabilities changed"}, + {0x2A,0x10,"Timestamp changed"}, + {0x2A,0x11,"Data encryption parameters changed by another i_t nexus"}, + {0x2A,0x12,"Data encryption parameters changed by vendor specific event"}, + {0x2A,0x13,"Data encryption key instance counter has changed"}, + {0x2A,0x0a,"Error history i_t nexus cleared"}, + {0x2A,0x0b,"Error history snapshot released"}, + {0x2A,0x14,"SA creation capabilities data has changed"}, + {0x2A,0x15,"Medium removal prevention preempted"}, + {0x2A,0x16,"Zone reset write pointer recommended"}, + {0x2B,0x00,"Copy cannot execute since host cannot disconnect"}, + {0x2C,0x00,"Command sequence error"}, + {0x2C,0x01,"Too many windows specified"}, + {0x2C,0x02,"Invalid combination of windows specified"}, + {0x2C,0x03,"Current program area is not empty"}, + {0x2C,0x04,"Current program area is empty"}, + {0x2C,0x05,"Illegal power condition request"}, + {0x2C,0x06,"Persistent prevent conflict"}, + {0x2C,0x07,"Previous busy status"}, + {0x2C,0x08,"Previous task set full status"}, + {0x2C,0x09,"Previous reservation conflict status"}, + {0x2C,0x0A,"Partition or collection contains user objects"}, + {0x2C,0x0B,"Not reserved"}, + {0x2C,0x0C,"ORWRITE generation does not match"}, + {0x2C,0x0D,"Reset write pointer not allowed"}, + {0x2C,0x0E,"Zone is offline"}, + {0x2C,0x0F,"Stream not open"}, + {0x2C,0x10,"Unwritten data in zone"}, + {0x2C,0x11,"Descriptor format sense data required"}, + {0x2D,0x00,"Overwrite error on update in place"}, + {0x2E,0x00,"Insufficient time for operation"}, + {0x2E,0x01,"Command timeout before processing"}, + {0x2E,0x02,"Command timeout during processing"}, + {0x2E,0x03,"Command timeout during processing due to error recovery"}, + {0x2F,0x00,"Commands cleared by another initiator"}, + {0x2F,0x01,"Commands cleared by power loss notification"}, + {0x2F,0x02,"Commands cleared by device server"}, + {0x2F,0x03,"Some commands cleared by queuing layer event"}, + {0x30,0x00,"Incompatible medium installed"}, + {0x30,0x01,"Cannot read medium - unknown format"}, + {0x30,0x02,"Cannot read medium - incompatible format"}, + {0x30,0x03,"Cleaning cartridge installed"}, + {0x30,0x04,"Cannot write medium - unknown format"}, + {0x30,0x05,"Cannot write medium - incompatible format"}, + {0x30,0x06,"Cannot format medium - incompatible medium"}, + {0x30,0x07,"Cleaning failure"}, + {0x30,0x08,"Cannot write - application code mismatch"}, + {0x30,0x09,"Current session not fixated for append"}, + {0x30,0x0A,"Cleaning request rejected"}, + {0x30,0x0B,"Cleaning tape expired"}, + {0x30,0x0C,"WORM medium - overwrite attempted"}, + {0x30,0x0D,"WORM medium - integrity check"}, + {0x30,0x10,"Medium not formatted"}, + {0x30,0x11,"Incompatible volume type"}, + {0x30,0x12,"Incompatible volume qualifier"}, + {0x30,0x13,"Cleaning volume expired"}, + {0x31,0x00,"Medium format corrupted"}, + {0x31,0x01,"Format command failed"}, + {0x31,0x02,"Zoned formatting failed due to spare linking"}, + {0x31,0x03,"Sanitize command failed"}, + {0x32,0x00,"No defect spare location available"}, + {0x32,0x01,"Defect list update failure"}, + {0x33,0x00,"Tape length error"}, + {0x34,0x00,"Enclosure failure"}, + {0x35,0x00,"Enclosure services failure"}, + {0x35,0x01,"Unsupported enclosure function"}, + {0x35,0x02,"Enclosure services unavailable"}, + {0x35,0x03,"Enclosure services transfer failure"}, + {0x35,0x04,"Enclosure services transfer refused"}, + {0x35,0x05,"Enclosure services checksum error"}, + {0x36,0x00,"Ribbon, ink, or toner failure"}, + {0x37,0x00,"Rounded parameter"}, + {0x38,0x00,"Event status notification"}, + {0x38,0x02,"Esn - power management class event"}, + {0x38,0x04,"Esn - media class event"}, + {0x38,0x06,"Esn - device busy class event"}, + {0x38,0x07,"Thin provisioning soft threshold reached"}, + {0x39,0x00,"Saving parameters not supported"}, + {0x3A,0x00,"Medium not present"}, + {0x3A,0x01,"Medium not present - tray closed"}, + {0x3A,0x02,"Medium not present - tray open"}, + {0x3A,0x03,"Medium not present - loadable"}, + {0x3A,0x04,"Medium not present - medium auxiliary memory accessible"}, + {0x3B,0x00,"Sequential positioning error"}, + {0x3B,0x01,"Tape position error at beginning-of-medium"}, + {0x3B,0x02,"Tape position error at end-of-medium"}, + {0x3B,0x03,"Tape or electronic vertical forms unit not ready"}, + {0x3B,0x04,"Slew failure"}, + {0x3B,0x05,"Paper jam"}, + {0x3B,0x06,"Failed to sense top-of-form"}, + {0x3B,0x07,"Failed to sense bottom-of-form"}, + {0x3B,0x08,"Reposition error"}, + {0x3B,0x09,"Read past end of medium"}, + {0x3B,0x0A,"Read past beginning of medium"}, + {0x3B,0x0B,"Position past end of medium"}, + {0x3B,0x0C,"Position past beginning of medium"}, + {0x3B,0x0D,"Medium destination element full"}, + {0x3B,0x0E,"Medium source element empty"}, + {0x3B,0x0F,"End of medium reached"}, + {0x3B,0x11,"Medium magazine not accessible"}, + {0x3B,0x12,"Medium magazine removed"}, + {0x3B,0x13,"Medium magazine inserted"}, + {0x3B,0x14,"Medium magazine locked"}, + {0x3B,0x15,"Medium magazine unlocked"}, + {0x3B,0x16,"Mechanical positioning or changer error"}, + {0x3B,0x17,"Read past end of user object"}, + {0x3B,0x18,"Element disabled"}, + {0x3B,0x19,"Element enabled"}, + {0x3B,0x1a,"Data transfer device removed"}, + {0x3B,0x1b,"Data transfer device inserted"}, + {0x3B,0x1c,"Too many logical objects on partition to support operation"}, + {0x3D,0x00,"Invalid bits in identify message"}, + {0x3E,0x00,"Logical unit has not self-configured yet"}, + {0x3E,0x01,"Logical unit failure"}, + {0x3E,0x02,"Timeout on logical unit"}, + {0x3E,0x03,"Logical unit failed self-test"}, + {0x3E,0x04,"Logical unit unable to update self-test log"}, + {0x3F,0x00,"Target operating conditions have changed"}, + {0x3F,0x01,"Microcode has been changed"}, + {0x3F,0x02,"Changed operating definition"}, + {0x3F,0x03,"Inquiry data has changed"}, + {0x3F,0x04,"Component device attached"}, + {0x3F,0x05,"Device identifier changed"}, + {0x3F,0x06,"Redundancy group created or modified"}, + {0x3F,0x07,"Redundancy group deleted"}, + {0x3F,0x08,"Spare created or modified"}, + {0x3F,0x09,"Spare deleted"}, + {0x3F,0x0A,"Volume set created or modified"}, + {0x3F,0x0B,"Volume set deleted"}, + {0x3F,0x0C,"Volume set deassigned"}, + {0x3F,0x0D,"Volume set reassigned"}, + {0x3F,0x0E,"Reported luns data has changed"}, + {0x3F,0x0F,"Echo buffer overwritten"}, + {0x3F,0x10,"Medium loadable"}, + {0x3F,0x11,"Medium auxiliary memory accessible"}, + {0x3F,0x12,"iSCSI IP address added"}, + {0x3F,0x13,"iSCSI IP address removed"}, + {0x3F,0x14,"iSCSI IP address changed"}, + {0x3F,0x15,"Inspect referrals sense descriptors"}, + {0x3F,0x16,"Microcode has been changed without reset"}, + {0x3F,0x17,"Zone transition to full"}, + {0x3F,0x18,"Bind completed"}, + {0x3F,0x19,"Bind redirected"}, + {0x3F,0x1A,"Subsidiary binding changed"}, + + /* + * ASC 0x40, 0x41 and 0x42 overridden by "additional2" array entries + * for ascq > 1. Preferred error message for this group is + * "Diagnostic failure on component nn (80h-ffh)". + */ + {0x40,0x00,"Ram failure (should use 40 nn)"}, + {0x41,0x00,"Data path failure (should use 40 nn)"}, + {0x42,0x00,"Power-on or self-test failure (should use 40 nn)"}, + + {0x43,0x00,"Message error"}, + {0x44,0x00,"Internal target failure"}, + {0x44,0x01,"Persistent reservation information lost"}, + {0x44,0x71,"ATA device failed Set Features"}, + {0x45,0x00,"Select or reselect failure"}, + {0x46,0x00,"Unsuccessful soft reset"}, + {0x47,0x00,"SCSI parity error"}, + {0x47,0x01,"Data phase CRC error detected"}, + {0x47,0x02,"SCSI parity error detected during st data phase"}, + {0x47,0x03,"Information unit iuCRC error detected"}, + {0x47,0x04,"Asynchronous information protection error detected"}, + {0x47,0x05,"Protocol service CRC error"}, + {0x47,0x06,"Phy test function in progress"}, + {0x47,0x7F,"Some commands cleared by iSCSI protocol event"}, + {0x48,0x00,"Initiator detected error message received"}, + {0x49,0x00,"Invalid message error"}, + {0x4A,0x00,"Command phase error"}, + {0x4B,0x00,"Data phase error"}, + {0x4B,0x01,"Invalid target port transfer tag received"}, + {0x4B,0x02,"Too much write data"}, + {0x4B,0x03,"Ack/nak timeout"}, + {0x4B,0x04,"Nak received"}, + {0x4B,0x05,"Data offset error"}, + {0x4B,0x06,"Initiator response timeout"}, + {0x4B,0x07,"Connection lost"}, + {0x4B,0x08,"Data-in buffer overflow - data buffer size"}, + {0x4B,0x09,"Data-in buffer overflow - data buffer descriptor area"}, + {0x4B,0x0A,"Data-in buffer error"}, + {0x4B,0x0B,"Data-out buffer overflow - data buffer size"}, + {0x4B,0x0C,"Data-out buffer overflow - data buffer descriptor area"}, + {0x4B,0x0D,"Data-out buffer error"}, + {0x4B,0x0E,"PCIe fabric error"}, + {0x4B,0x0f,"PCIe completion timeout"}, + {0x4B,0x10,"PCIe completer abort"}, + {0x4B,0x11,"PCIe poisoned tlp received"}, + {0x4B,0x12,"PCIe ecrc check failed"}, + {0x4B,0x13,"PCIe unsupported request"}, + {0x4B,0x14,"PCIe acs violation"}, + {0x4B,0x15,"PCIe tlp prefix blocked"}, + {0x4C,0x00,"Logical unit failed self-configuration"}, + /* + * ASC 0x4D overridden by an "additional2" array entry + * so there is no need to have them here. + */ + /* {0x4D,0x00,"Tagged overlapped commands (nn = queue tag)"}, */ + + {0x4E,0x00,"Overlapped commands attempted"}, + {0x50,0x00,"Write append error"}, + {0x50,0x01,"Write append position error"}, + {0x50,0x02,"Position error related to timing"}, + {0x51,0x00,"Erase failure"}, + {0x51,0x01,"Erase failure - incomplete erase operation detected"}, + {0x52,0x00,"Cartridge fault"}, + {0x53,0x00,"Media load or eject failed"}, + {0x53,0x01,"Unload tape failure"}, + {0x53,0x02,"Medium removal prevented"}, + {0x53,0x03,"Medium removal prevented by data transfer element"}, + {0x53,0x04,"Medium thread or unthread failure"}, + {0x53,0x05,"Volume identifier invalid"}, + {0x53,0x06,"Volume identifier missing"}, + {0x53,0x07,"Duplicate volume identifier"}, + {0x53,0x08,"Element status unknown"}, + {0x53,0x09,"Data transfer device error - load failed"}, + {0x53,0x0A,"Data transfer device error - unload failed"}, + {0x53,0x0B,"Data transfer device error - unload missing"}, + {0x53,0x0C,"Data transfer device error - eject failed"}, + {0x53,0x0D,"Data transfer device error - library communication failed"}, + {0x54,0x00,"SCSI to host system interface failure"}, + {0x55,0x00,"System resource failure"}, + {0x55,0x01,"System buffer full"}, + {0x55,0x02,"Insufficient reservation resources"}, + {0x55,0x03,"Insufficient resources"}, + {0x55,0x04,"Insufficient registration resources"}, + {0x55,0x05,"Insufficient access control resources"}, + {0x55,0x06,"Auxiliary memory out of space"}, + {0x55,0x07,"Quota error"}, + {0x55,0x08,"Maximum number of supplemental decryption keys exceeded"}, + {0x55,0x09,"Medium auxiliary memory not accessible"}, + {0x55,0x0a,"Data currently unavailable"}, + {0x55,0x0b,"Insufficient power for operation"}, + {0x55,0x0c,"Insufficient resources to create rod"}, + {0x55,0x0d,"Insufficient resources to create rod token"}, + {0x55,0x0e,"Insufficient zone resources"}, + {0x55,0x0f,"Insufficient zone resources to complete write"}, + {0x55,0x10,"Maximum number of streams open"}, + {0x55,0x11,"Insufficient resources to bind"}, + {0x57,0x00,"Unable to recover table-of-contents"}, + {0x58,0x00,"Generation does not exist"}, + {0x59,0x00,"Updated block read"}, + {0x5A,0x00,"Operator request or state change input"}, + {0x5A,0x01,"Operator medium removal request"}, + {0x5A,0x02,"Operator selected write protect"}, + {0x5A,0x03,"Operator selected write permit"}, + {0x5B,0x00,"Log exception"}, + {0x5B,0x01,"Threshold condition met"}, + {0x5B,0x02,"Log counter at maximum"}, + {0x5B,0x03,"Log list codes exhausted"}, + {0x5C,0x00,"Rpl status change"}, + {0x5C,0x01,"Spindles synchronized"}, + {0x5C,0x02,"Spindles not synchronized"}, + {0x5D,0x00,"Failure prediction threshold exceeded"}, + {0x5D,0x01,"Media failure prediction threshold exceeded"}, + {0x5D,0x02,"Logical unit failure prediction threshold exceeded"}, + {0x5D,0x03,"spare area exhaustion prediction threshold exceeded"}, + {0x5D,0x10,"Hardware impending failure general hard drive failure"}, + {0x5D,0x11,"Hardware impending failure drive error rate too high" }, + {0x5D,0x12,"Hardware impending failure data error rate too high" }, + {0x5D,0x13,"Hardware impending failure seek error rate too high" }, + {0x5D,0x14,"Hardware impending failure too many block reassigns"}, + {0x5D,0x15,"Hardware impending failure access times too high" }, + {0x5D,0x16,"Hardware impending failure start unit times too high" }, + {0x5D,0x17,"Hardware impending failure channel parametrics"}, + {0x5D,0x18,"Hardware impending failure controller detected"}, + {0x5D,0x19,"Hardware impending failure throughput performance"}, + {0x5D,0x1A,"Hardware impending failure seek time performance"}, + {0x5D,0x1B,"Hardware impending failure spin-up retry count"}, + {0x5D,0x1C,"Hardware impending failure drive calibration retry count"}, + {0x5D,0x1D,"Hardware impending failure power loss protection circuit"}, + {0x5D,0x20,"Controller impending failure general hard drive failure"}, + {0x5D,0x21,"Controller impending failure drive error rate too high" }, + {0x5D,0x22,"Controller impending failure data error rate too high" }, + {0x5D,0x23,"Controller impending failure seek error rate too high" }, + {0x5D,0x24,"Controller impending failure too many block reassigns"}, + {0x5D,0x25,"Controller impending failure access times too high" }, + {0x5D,0x26,"Controller impending failure start unit times too high" }, + {0x5D,0x27,"Controller impending failure channel parametrics"}, + {0x5D,0x28,"Controller impending failure controller detected"}, + {0x5D,0x29,"Controller impending failure throughput performance"}, + {0x5D,0x2A,"Controller impending failure seek time performance"}, + {0x5D,0x2B,"Controller impending failure spin-up retry count"}, + {0x5D,0x2C,"Controller impending failure drive calibration retry count"}, + {0x5D,0x30,"Data channel impending failure general hard drive failure"}, + {0x5D,0x31,"Data channel impending failure drive error rate too high" }, + {0x5D,0x32,"Data channel impending failure data error rate too high" }, + {0x5D,0x33,"Data channel impending failure seek error rate too high" }, + {0x5D,0x34,"Data channel impending failure too many block reassigns"}, + {0x5D,0x35,"Data channel impending failure access times too high" }, + {0x5D,0x36,"Data channel impending failure start unit times too high" }, + {0x5D,0x37,"Data channel impending failure channel parametrics"}, + {0x5D,0x38,"Data channel impending failure controller detected"}, + {0x5D,0x39,"Data channel impending failure throughput performance"}, + {0x5D,0x3A,"Data channel impending failure seek time performance"}, + {0x5D,0x3B,"Data channel impending failure spin-up retry count"}, + {0x5D,0x3C,"Data channel impending failure drive calibration retry count"}, + {0x5D,0x40,"Servo impending failure general hard drive failure"}, + {0x5D,0x41,"Servo impending failure drive error rate too high" }, + {0x5D,0x42,"Servo impending failure data error rate too high" }, + {0x5D,0x43,"Servo impending failure seek error rate too high" }, + {0x5D,0x44,"Servo impending failure too many block reassigns"}, + {0x5D,0x45,"Servo impending failure access times too high" }, + {0x5D,0x46,"Servo impending failure start unit times too high" }, + {0x5D,0x47,"Servo impending failure channel parametrics"}, + {0x5D,0x48,"Servo impending failure controller detected"}, + {0x5D,0x49,"Servo impending failure throughput performance"}, + {0x5D,0x4A,"Servo impending failure seek time performance"}, + {0x5D,0x4B,"Servo impending failure spin-up retry count"}, + {0x5D,0x4C,"Servo impending failure drive calibration retry count"}, + {0x5D,0x50,"Spindle impending failure general hard drive failure"}, + {0x5D,0x51,"Spindle impending failure drive error rate too high" }, + {0x5D,0x52,"Spindle impending failure data error rate too high" }, + {0x5D,0x53,"Spindle impending failure seek error rate too high" }, + {0x5D,0x54,"Spindle impending failure too many block reassigns"}, + {0x5D,0x55,"Spindle impending failure access times too high" }, + {0x5D,0x56,"Spindle impending failure start unit times too high" }, + {0x5D,0x57,"Spindle impending failure channel parametrics"}, + {0x5D,0x58,"Spindle impending failure controller detected"}, + {0x5D,0x59,"Spindle impending failure throughput performance"}, + {0x5D,0x5A,"Spindle impending failure seek time performance"}, + {0x5D,0x5B,"Spindle impending failure spin-up retry count"}, + {0x5D,0x5C,"Spindle impending failure drive calibration retry count"}, + {0x5D,0x60,"Firmware impending failure general hard drive failure"}, + {0x5D,0x61,"Firmware impending failure drive error rate too high" }, + {0x5D,0x62,"Firmware impending failure data error rate too high" }, + {0x5D,0x63,"Firmware impending failure seek error rate too high" }, + {0x5D,0x64,"Firmware impending failure too many block reassigns"}, + {0x5D,0x65,"Firmware impending failure access times too high" }, + {0x5D,0x66,"Firmware impending failure start unit times too high" }, + {0x5D,0x67,"Firmware impending failure channel parametrics"}, + {0x5D,0x68,"Firmware impending failure controller detected"}, + {0x5D,0x69,"Firmware impending failure throughput performance"}, + {0x5D,0x6A,"Firmware impending failure seek time performance"}, + {0x5D,0x6B,"Firmware impending failure spin-up retry count"}, + {0x5D,0x6C,"Firmware impending failure drive calibration retry count"}, + {0x5D,0x73,"Media impending failure endurance limit met"}, + {0x5D,0xFF,"Failure prediction threshold exceeded (false)"}, + {0x5E,0x00,"Low power condition on"}, + {0x5E,0x01,"Idle condition activated by timer"}, + {0x5E,0x02,"Standby condition activated by timer"}, + {0x5E,0x03,"Idle condition activated by command"}, + {0x5E,0x04,"Standby condition activated by command"}, + {0x5E,0x05,"Idle_b condition activated by timer"}, + {0x5E,0x06,"Idle_b condition activated by command"}, + {0x5E,0x07,"Idle_c condition activated by timer"}, + {0x5E,0x08,"Idle_c condition activated by command"}, + {0x5E,0x09,"Standby_y condition activated by timer"}, + {0x5E,0x0a,"Standby_y condition activated by command"}, + {0x5E,0x41,"Power state change to active"}, + {0x5E,0x42,"Power state change to idle"}, + {0x5E,0x43,"Power state change to standby"}, + {0x5E,0x45,"Power state change to sleep"}, + {0x5E,0x47,"Power state change to device control"}, + {0x60,0x00,"Lamp failure"}, + {0x61,0x00,"Video acquisition error"}, + {0x61,0x01,"Unable to acquire video"}, + {0x61,0x02,"Out of focus"}, + {0x62,0x00,"Scan head positioning error"}, + {0x63,0x00,"End of user area encountered on this track"}, + {0x63,0x01,"Packet does not fit in available space"}, + {0x64,0x00,"Illegal mode for this track"}, + {0x64,0x01,"Invalid packet size"}, + {0x65,0x00,"Voltage fault"}, + {0x66,0x00,"Automatic document feeder cover up"}, + {0x66,0x01,"Automatic document feeder lift up"}, + {0x66,0x02,"Document jam in automatic document feeder"}, + {0x66,0x03,"Document miss feed automatic in document feeder"}, + {0x67,0x00,"Configuration failure"}, + {0x67,0x01,"Configuration of incapable logical units failed"}, + {0x67,0x02,"Add logical unit failed"}, + {0x67,0x03,"Modification of logical unit failed"}, + {0x67,0x04,"Exchange of logical unit failed"}, + {0x67,0x05,"Remove of logical unit failed"}, + {0x67,0x06,"Attachment of logical unit failed"}, + {0x67,0x07,"Creation of logical unit failed"}, + {0x67,0x08,"Assign failure occurred"}, + {0x67,0x09,"Multiply assigned logical unit"}, + {0x67,0x0A,"Set target port groups command failed"}, + {0x67,0x0B,"ATA device feature not enabled"}, + {0x67,0x0C,"Command rejected"}, + {0x67,0x0D,"Explicit bind not allowed"}, + {0x68,0x00,"Logical unit not configured"}, + {0x68,0x01,"Subsidiary logical unit not configured"}, + {0x69,0x00,"Data loss on logical unit"}, + {0x69,0x01,"Multiple logical unit failures"}, + {0x69,0x02,"Parity/data mismatch"}, + {0x6A,0x00,"Informational, refer to log"}, + {0x6B,0x00,"State change has occurred"}, + {0x6B,0x01,"Redundancy level got better"}, + {0x6B,0x02,"Redundancy level got worse"}, + {0x6C,0x00,"Rebuild failure occurred"}, + {0x6D,0x00,"Recalculate failure occurred"}, + {0x6E,0x00,"Command to logical unit failed"}, + {0x6F,0x00,"Copy protection key exchange failure - authentication " + "failure"}, + {0x6F,0x01,"Copy protection key exchange failure - key not present"}, + {0x6F,0x02,"Copy protection key exchange failure - key not established"}, + {0x6F,0x03,"Read of scrambled sector without authentication"}, + {0x6F,0x04,"Media region code is mismatched to logical unit region"}, + {0x6F,0x05,"Drive region must be permanent/region reset count error"}, + {0x6F,0x06,"Insufficient block count for binding nonce recording"}, + {0x6F,0x07,"Conflict in binding nonce recording"}, + {0x6F,0x08,"Insufficient permission"}, + {0x6F,0x09,"Invalid drive-host pairing server"}, + {0x6F,0x0A,"Drive-host pairing suspended"}, + /* + * ASC 0x70 overridden by an "additional2" array entry + * so there is no need to have them here. + */ + /* {0x70,0x00,"Decompression exception short algorithm id of nn"}, */ + + {0x71,0x00,"Decompression exception long algorithm id"}, + {0x72,0x00,"Session fixation error"}, + {0x72,0x01,"Session fixation error writing lead-in"}, + {0x72,0x02,"Session fixation error writing lead-out"}, + {0x72,0x03,"Session fixation error - incomplete track in session"}, + {0x72,0x04,"Empty or partially written reserved track"}, + {0x72,0x05,"No more track reservations allowed"}, + {0x72,0x06,"RMZ extension is not allowed"}, + {0x72,0x07,"No more test zone extensions are allowed"}, + {0x73,0x00,"CD control error"}, + {0x73,0x01,"Power calibration area almost full"}, + {0x73,0x02,"Power calibration area is full"}, + {0x73,0x03,"Power calibration area error"}, + {0x73,0x04,"Program memory area update failure"}, + {0x73,0x05,"Program memory area is full"}, + {0x73,0x06,"RMA/PMA is almost full"}, + {0x73,0x10,"Current power calibration area almost full"}, + {0x73,0x11,"Current power calibration area is full"}, + {0x73,0x17,"RDZ is full"}, + {0x74,0x00,"Security error"}, + {0x74,0x01,"Unable to decrypt data"}, + {0x74,0x02,"Unencrypted data encountered while decrypting"}, + {0x74,0x03,"Incorrect data encryption key"}, + {0x74,0x04,"Cryptographic integrity validation failed"}, + {0x74,0x05,"Error decrypting data"}, + {0x74,0x06,"Unknown signature verification key"}, + {0x74,0x07,"Encryption parameters not useable"}, + {0x74,0x08,"Digital signature validation failure"}, + {0x74,0x09,"Encryption mode mismatch on read"}, + {0x74,0x0a,"Encrypted block not raw read enabled"}, + {0x74,0x0b,"Incorrect Encryption parameters"}, + {0x74,0x0c,"Unable to decrypt parameter list"}, + {0x74,0x0d,"Encryption algorithm disabled"}, + {0x74,0x10,"SA creation parameter value invalid"}, + {0x74,0x11,"SA creation parameter value rejected"}, + {0x74,0x12,"Invalid SA usage"}, + {0x74,0x21,"Data encryption configuration prevented"}, + {0x74,0x30,"SA creation parameter not supported"}, + {0x74,0x40,"Authentication failed"}, + {0x74,0x61,"External data encryption key manager access error"}, + {0x74,0x62,"External data encryption key manager error"}, + {0x74,0x63,"External data encryption key not found"}, + {0x74,0x64,"External data encryption request not authorized"}, + {0x74,0x6e,"External data encryption control timeout"}, + {0x74,0x6f,"External data encryption control error"}, + {0x74,0x71,"Logical unit access not authorized"}, + {0x74,0x79,"Security conflict in translated device"}, + {0, 0, NULL} +}; + +#else /* SG_SCSI_STRINGS */ + +struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] = +{ + {0, 0, 0, NULL} +}; + +struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] = +{ + {0, 0, NULL} +}; +#endif /* SG_SCSI_STRINGS */ + +const char * sg_lib_sense_key_desc[] = { + "No Sense", /* Filemark, ILI and/or EOM; progress + indication (during FORMAT); power + condition sensing (REQUEST SENSE) */ + "Recovered Error", /* The last command completed successfully + but used error correction */ + "Not Ready", /* The addressed target is not ready */ + "Medium Error", /* Data error detected on the medium */ + "Hardware Error", /* Controller or device failure */ + "Illegal Request", + "Unit Attention", /* Removable medium was changed, or + the target has been reset */ + "Data Protect", /* Access to the data is blocked */ + "Blank Check", /* Reached unexpected written or unwritten + region of the medium */ + "Vendor specific(9)", /* Vendor specific */ + "Copy Aborted", /* COPY or COMPARE was aborted */ + "Aborted Command", /* The target aborted the command */ + "Equal", /* SEARCH DATA found data equal (obsolete) */ + "Volume Overflow", /* Medium full with data to be written */ + "Miscompare", /* Source data and data on the medium + do not agree */ + "Completed" /* may occur for successful cmd (spc4r23) */ +}; + +const char * sg_lib_pdt_strs[32] = { /* should have 2**5 elements */ + /* 0 */ "disk", + "tape", + "printer", /* obsolete, spc5r01 */ + "processor", /* often SAF-TE device, copy manager */ + "write once optical disk", /* obsolete, spc5r01 */ + /* 5 */ "cd/dvd", + "scanner", /* obsolete */ + "optical memory device", + "medium changer", + "communications", /* obsolete */ + /* 0xa */ "graphics [0xa]", /* obsolete */ + "graphics [0xb]", /* obsolete */ + "storage array controller", + "enclosure services device", + "simplified direct access device", + "optical card reader/writer device", + /* 0x10 */ "bridge controller commands", + "object based storage", + "automation/driver interface", + "security manager device", /* obsolete, spc5r01 */ + "zoned block commands", + "0x15", "0x16", "0x17", "0x18", + "0x19", "0x1a", "0x1b", "0x1c", "0x1d", + "well known logical unit", + "no physical device on this lu", +}; + +const char * sg_lib_transport_proto_strs[] = +{ + "Fibre Channel Protocol for SCSI (FCP-4)", + "SCSI Parallel Interface (SPI-5)", /* obsolete in spc5r01 */ + "Serial Storage Architecture SCSI-3 Protocol (SSA-S3P)", + "Serial Bus Protocol for IEEE 1394 (SBP-3)", + "SCSI RDMA Protocol (SRP)", + "Internet SCSI (iSCSI)", + "Serial Attached SCSI Protocol (SPL-4)", + "Automation/Drive Interface Transport (ADT-2)", + "AT Attachment Interface (ACS-2)", /* 0x8 */ + "USB Attached SCSI (UAS-2)", + "SCSI over PCI Express (SOP)", + "PCIe", /* added in spc5r02 */ + "Oxc", "Oxd", "Oxe", + "No specific protocol" +}; + +/* SCSI Feature Sets array. code->value, pdt->peri_dev_type (-1 for SPC) */ +struct sg_lib_value_name_t sg_lib_scsi_feature_sets[] = +{ + {SCSI_FS_SPC_DISCOVERY_2016, -1, "Discovery 2016"}, + {SCSI_FS_SBC_BASE_2010, PDT_DISK, "SBC Base 2010"}, + {SCSI_FS_SBC_BASE_2016, PDT_DISK, "SBC Base 2016"}, + {SCSI_FS_SBC_BASIC_PROV_2016, PDT_DISK, "Basic provisioning 2016"}, + {SCSI_FS_SBC_DRIVE_MAINT_2016, PDT_DISK, "Drive maintenance 2016"}, + {0x0, 0, NULL}, /* 0x0 is reserved sfs; trailing sentinel */ +}; + +#if (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) + +/* .value is completion queue's DW3 as follows: ((DW3 >> 17) & 0x3ff) + * .peri_dev_type is an index for the sg_lib_scsi_status_sense_arr[] + * .name is taken from NVMe 1.3a document, section 4.6.1.2.1 with less + * capitalization. + * NVMe term bits 31:17 of DW3 in the completion field as the "Status + * Field" (SF). Bit 31 is "Do not retry" (DNR) and bit 30 is "More" (M). + * Bits 29:28 are reserved, bit 27:25 are the "Status Code Type" (SCT) + * and bits 24:17 are the Status Code (SC). This table is in ascending + * order of its .value field so a binary search could be done on it. */ +struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] = +{ + /* Generic command status values, Status Code Type (SCT): 0h + * Lowest 8 bits are the Status Code (SC), in this case: + * 00h - 7Fh: Applicable to Admin Command Set, or across multiple + * command sets + * 80h - BFh: I/O Command Set Specific status codes + * c0h - FFh: I/O Vendor Specific status codes */ + {0x0, 0, "Successful completion"}, + {0x1, 1, "Invalid command opcode"}, + {0x2, 2, "Invalid field in command"}, + {0x3, 2, "Command id conflict"}, + {0x4, 3, "Data transfer error"}, + {0x5, 4, "Command aborted due to power loss notication"}, + {0x6, 5, "Internal error"}, + {0x7, 6, "Command abort requested"}, + {0x8, 6, "Command aborted due to SQ deletion"}, + {0x9, 6, "Command aborted due to failed fused command"}, + {0xa, 6, "Command aborted due to missing fused command"}, + {0xb, 7, "Invalid namespace or format"}, + {0xc, 5, "Command sequence error"}, + {0xd, 5, "Invalid SGL segment descriptor"}, + {0xe, 5, "Invalid number of SGL descriptors"}, + {0xf, 5, "Data SGL length invalid"}, + {0x10, 5, "Matadata SGL length invalid"}, + {0x11, 5, "SGL descriptor type invalid"}, + {0x12, 5, "Invalid use of controller memory buffer"}, + {0x13, 5, "PRP offset invalid"}, + {0x14, 2, "Atomic write unit exceeded"}, + {0x15, 8, "Operation denied"}, + {0x16, 5, "SGL offset invalid"}, + {0x17, 5, "Reserved [0x17]"}, + {0x18, 5, "Host identifier inconsistent format"}, + {0x19, 5, "Keep alive timeout expired"}, + {0x1a, 5, "Keep alive timeout invalid"}, + {0x1b, 6, "Command aborted due to Preempt and Abort"}, + {0x1c, 10, "Sanitize failed"}, + {0x1d, 11, "Sanitize in progress"}, + {0x1e, 5, "SGL data block granularity invalid"}, + {0x1f, 5, "Command not supported for queue in CMB"}, + + /* Generic command status values, NVM (I/O) Command Set */ + {0x80, 12, "LBA out of range"}, + {0x81, 3, "Capacity exceeded"}, + {0x82, 13, "Namespace not ready"}, + {0x83, 14, "Reservation conflict"}, + {0x84, 15, "Format in progress"}, + /* 0xc0 - 0xff: vendor specific */ + + /* Command specific status values, Status Code Type (SCT): 1h */ + {0x100, 5, "Completion queue invalid"}, + {0x101, 5, "Invalid queue identifier"}, + {0x102, 5, "Invalid queue size"}, + {0x103, 5, "Abort command limit exceeded"}, + {0x104, 5, "Reserved [0x104]"}, + {0x105, 5, "Asynchronous event request limit exceeded"}, + {0x106, 5, "Invalid firmware slot"}, + {0x107, 5, "Invalid firmware image"}, + {0x108, 5, "Invalid interrupt vector"}, + {0x109, 5, "Invalid log page"}, + {0x10a,16, "Invalid format"}, + {0x10b, 5, "Firmware activation requires conventional reset"}, + {0x10c, 5, "Invalid queue deletion"}, + {0x10d, 5, "Feature identifier not saveable"}, + {0x10e, 5, "Feature not changeable"}, + {0x10f, 5, "Feature not namespace specific"}, + {0x110, 5, "Firmware activation requires NVM subsystem reset"}, + {0x111, 5, "Firmware activation requires reset"}, + {0x112, 5, "Firmware activation requires maximum time violation"}, + {0x113, 5, "Firmware activation prohibited"}, + {0x114, 5, "Overlapping range"}, + {0x115, 5, "Namespace insufficient capacity"}, + {0x116, 5, "Namespace identifier unavailable"}, + {0x117, 5, "Reserved [0x107]"}, + {0x118, 5, "Namespace already attached"}, + {0x119, 5, "Namespace is private"}, + {0x11a, 5, "Namespace not attached"}, + {0x11b, 3, "Thin provisioning not supported"}, + {0x11c, 3, "Controller list invalid"}, + {0x11d,17, "Device self-test in progress"}, + {0x11e,18, "Boot partition write prohibited"}, + {0x11f, 5, "Invalid controller identifier"}, + {0x120, 5, "Invalid secondary controller state"}, + {0x121, 5, "Invalid number of controller resorces"}, + {0x122, 5, "Invalid resorce identifier"}, + + /* Command specific status values, Status Code Type (SCT): 1h + * for NVM (I/O) Command Set */ + {0x180, 2, "Conflicting attributes"}, + {0x181,19, "Invalid protection information"}, + {0x182,18, "Attempted write to read only range"}, + /* 0x1c0 - 0x1ff: vendor specific */ + + /* Media and Data Integrity error values, Status Code Type (SCT): 2h */ + {0x280,20, "Write fault"}, + {0x281,21, "Unrecovered read error"}, + {0x282,22, "End-to-end guard check error"}, + {0x283,23, "End-to-end application tag check error"}, + {0x284,24, "End-to-end reference tag check error"}, + {0x285,25, "Compare failure"}, + {0x286, 8, "Access denied"}, + {0x287,26, "Deallocated or unwritten logical block"}, + /* 0x2c0 - 0x2ff: vendor specific */ + + /* Leave this Sentinel value at end of this array */ + {0x3ff, 0, NULL}, +}; + +/* The sg_lib_nvme_cmd_status_arr[n].peri_dev_type field is an index + * to this array. It allows an NVMe status (error) value to be mapped + * to this SCSI tuple: status, sense_key, additional sense code (asc) and + * asc qualifier (ascq). For brevity SAM_STAT_CHECK_CONDITION is written + * as 0x2. */ +struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] = +{ + {SAM_STAT_GOOD, SPC_SK_NO_SENSE, 0, 0}, /* it's all good */ /* 0 */ + {SAM_STAT_CHECK_CONDITION, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x0},/* opcode */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x24, 0x0}, /* field in cdb */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x0, 0x0}, + {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0xb, 0x8}, + {0x2, SPC_SK_HARDWARE_ERROR, 0x44, 0x0}, /* internal error */ /* 5 */ + {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0x0, 0x0}, + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x9}, /* invalid LU */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x2}, /* access denied */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x2c, 0x0}, /* cmd sequence error */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x31, 0x3}, /* sanitize failed */ /* 10 */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x1b}, /* sanitize in progress */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x21, 0x0}, /* LBA out of range */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x0}, /* not reportable; 0x1: becoming */ + {SAM_STAT_RESERVATION_CONFLICT, 0x0, 0x0, 0x0}, + {0x2, SPC_SK_NOT_READY, 0x4, 0x4}, /* format in progress */ /* 15 */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x31, 0x1}, /* format failed */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x9}, /* self-test in progress */ + {0x2, SPC_SK_DATA_PROTECT, 0x27, 0x0}, /* write prohibited */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x10, 0x5}, /* protection info */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x3, 0x0}, /* periph dev w fault */ /* 20 */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x11, 0x0}, /* unrecoc rd */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x1}, /* PI guard */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2}, /* PI app tag */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2}, /* PI app tag */ + {0x2, SPC_SK_MISCOMPARE, 0x1d, 0x0}, /* during verify */ /* 25 */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x21, 0x6}, /* read invalid data */ + + /* Leave this Sentinel value at end of this array */ + {0xff, 0xff, 0xff, 0xff}, +}; + + +#else /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */ +struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] = +{ + + /* Leave this Sentinel value at end of this array */ + {0x3ff, 0, NULL}, +}; + +struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] = +{ + + /* Leave this Sentinel value at end of this array */ + {0xff, 0xff, 0xff, 0xff}, +}; + +#endif /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */ diff --git a/tools/sg_write_buffer/sg_pt_common.c b/tools/sg_write_buffer/sg_pt_common.c new file mode 100644 index 0000000..85bc191 --- /dev/null +++ b/tools/sg_write_buffer/sg_pt_common.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2009-2018 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_pt.h" +#include "sg_pt_nvme.h" + + +static const char * scsi_pt_version_str = "3.03 20180115"; + +static const char * nvme_scsi_vendor_str = "NVMe "; + + +const char * +scsi_pt_version() +{ + return scsi_pt_version_str; +} + +/* Given the NVMe Identify controller response and optionally the NVMe + * Identify namespace response (NULL otherwise), generate the SCSI VPD + * page 0x83 (device identification) descriptor(s) in dop. Return the + * number of bytes written which will not exceed max_do_len. Probably use + * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport + * protocol (tproto) should be -1 if not known, else SCSI value. + * N.B. Does not write total VPD page length into dop[2:3] . */ +int +sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p, + const uint8_t * nvme_id_ns_p, int pdt, + int tproto, uint8_t * dop, int max_do_len) +{ + bool have_nguid, have_eui64; + int k, n; + char b[4]; + + if ((NULL == nvme_id_ctl_p) || (NULL == dop) || (max_do_len < 56)) + return 0; + + memset(dop, 0, max_do_len); + dop[0] = 0x1f & pdt; /* (PQ=0)<<5 | (PDT=pdt); 0 or 0xd (SES) */ + dop[1] = 0x83; /* Device Identification VPD page number */ + /* Build a T10 Vendor ID based designator (desig_id=1) for controller */ + if (tproto >= 0) { + dop[4] = ((0xf & tproto) << 4) | 0x2; + dop[5] = 0xa1; /* PIV=1, ASSOC=2 (target device), desig_id=1 */ + } else { + dop[4] = 0x2; /* Prococol id=0, code_set=2 (ASCII) */ + dop[5] = 0x21; /* PIV=0, ASSOC=2 (target device), desig_id=1 */ + } + memcpy(dop + 8, nvme_scsi_vendor_str, 8); /* N.B. this is "NVMe " */ + memcpy(dop + 16, nvme_id_ctl_p + 24, 40); /* MN */ + for (k = 40; k > 0; --k) { + if (' ' == dop[15 + k]) + dop[15 + k] = '_'; /* convert trailing spaces */ + else + break; + } + if (40 == k) + --k; + n = 16 + 1 + k; + if (max_do_len < (n + 20)) + return 0; + memcpy(dop + n, nvme_id_ctl_p + 4, 20); /* SN */ + for (k = 20; k > 0; --k) { /* trim trailing spaces */ + if (' ' == dop[n + k - 1]) + dop[n + k - 1] = '\0'; + else + break; + } + n += k; + if (0 != (n % 4)) + n = ((n / 4) + 1) * 4; /* round up to next modulo 4 */ + dop[7] = n - 8; + if (NULL == nvme_id_ns_p) + return n; + + /* Look for NGUID (16 byte identifier) or EUI64 (8 byte) fields in + * NVME Identify for namespace. If found form a EUI and a SCSI string + * descriptor for non-zero NGUID or EUI64 (prefer NGUID if both). */ + have_nguid = ! sg_all_zeros(nvme_id_ns_p + 104, 16); + have_eui64 = ! sg_all_zeros(nvme_id_ns_p + 120, 8); + if ((! have_nguid) && (! have_eui64)) + return n; + if (have_nguid) { + if (max_do_len < (n + 20)) + return n; + dop[n + 0] = 0x1; /* Prococol id=0, code_set=1 (binary) */ + dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */ + dop[n + 3] = 16; + memcpy(dop + n + 4, nvme_id_ns_p + 104, 16); + n += 20; + if (max_do_len < (n + 40)) + return n; + dop[n + 0] = 0x3; /* Prococol id=0, code_set=3 (utf8) */ + dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */ + dop[n + 3] = 36; + memcpy(dop + n + 4, "eui.", 4); + for (k = 0; k < 16; ++k) { + snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[104 + k]); + memcpy(dop + n + 8 + (2 * k), b, 2); + } + return n + 40; + } else { /* have_eui64 is true, 8 byte identifier */ + if (max_do_len < (n + 12)) + return n; + dop[n + 0] = 0x1; /* Prococol id=0, code_set=1 (binary) */ + dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */ + dop[n + 3] = 8; + memcpy(dop + n + 4, nvme_id_ns_p + 120, 8); + n += 12; + if (max_do_len < (n + 24)) + return n; + dop[n + 0] = 0x3; /* Prococol id=0, code_set=3 (utf8) */ + dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */ + dop[n + 3] = 20; + memcpy(dop + n + 4, "eui.", 4); + for (k = 0; k < 8; ++k) { + snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[120 + k]); + memcpy(dop + n + 8 + (2 * k), b, 2); + } + return n + 24; + } +} diff --git a/tools/sg_write_buffer/sg_pt_linux.c b/tools/sg_write_buffer/sg_pt_linux.c new file mode 100644 index 0000000..22ea068 --- /dev/null +++ b/tools/sg_write_buffer/sg_pt_linux.c @@ -0,0 +1,964 @@ +/* + * Copyright (c) 2005-2018 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.37 20180126 */ + + +#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" + + +#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) */ + +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 + +bool sg_bsg_nvme_char_major_checked = false; +int sg_bsg_major = 0; +volatile int sg_nvme_char_major = 0; + +long sg_lin_page_size = 4096; /* default, overridden with correct value */ + + +#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). */ +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 + + +/* 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 (! 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_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_nvme_id_ctlp) { + free(ptp->free_nvme_id_ctlp); + ptp->free_nvme_id_ctlp = NULL; + ptp->nvme_id_ctlp = NULL; + } + 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; + uint32_t 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_direct = false; + 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 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); + else { + ptp->is_sg = false; + ptp->is_bsg = false; + ptp->is_nvme = false; + ptp->nvme_direct = 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_uintptr_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_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, 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_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 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_uintptr_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_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; + } +} + +/* 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->nvme_direct ? 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->nvme_direct ? 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->nvme_direct ? 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; +} + +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 >= 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 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 (! 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) { + 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 sg_do_nvme_pt(vp, -1, time_secs, verbose); + else if (sg_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; +} diff --git a/tools/sg_write_buffer/sg_pt_linux_nvme.c b/tools/sg_write_buffer/sg_pt_linux_nvme.c new file mode 100644 index 0000000..5b08f6d --- /dev/null +++ b/tools/sg_write_buffer/sg_pt_linux_nvme.c @@ -0,0 +1,1185 @@ +/* + * Copyright (c) 2017-2018 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. + * + * The code to use the NVMe Management Interface (MI) SES pass-through + * was provided by WDC in November 2017. + */ + +/* + * Copyright 2017, Western Digital Corporation + * + * Written by Berck Nash + * + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + * + * Based on the NVM-Express command line utility, which bore the following + * notice: + * + * Copyright (c) 2014-2015, Intel Corporation. + * + * Written by Keith Busch <keith.busch@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +/* sg_pt_linux_nvme version 1.04 20180115 */ + + +#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> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.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_unaligned.h" + +#define SCSI_INQUIRY_OPC 0x12 +#define SCSI_REPORT_LUNS_OPC 0xa0 +#define SCSI_TEST_UNIT_READY_OPC 0x0 +#define SCSI_REQUEST_SENSE_OPC 0x3 +#define SCSI_SEND_DIAGNOSTIC_OPC 0x1d +#define SCSI_RECEIVE_DIAGNOSTIC_OPC 0x1c +#define SCSI_MAINT_IN_OPC 0xa3 +#define SCSI_REP_SUP_OPCS_OPC 0xc +#define SCSI_REP_SUP_TMFS_OPC 0xd + +/* Additional Sense Code (ASC) */ +#define NO_ADDITIONAL_SENSE 0x0 +#define LOGICAL_UNIT_NOT_READY 0x4 +#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8 +#define UNRECOVERED_READ_ERR 0x11 +#define PARAMETER_LIST_LENGTH_ERR 0x1a +#define INVALID_OPCODE 0x20 +#define LBA_OUT_OF_RANGE 0x21 +#define INVALID_FIELD_IN_CDB 0x24 +#define INVALID_FIELD_IN_PARAM_LIST 0x26 +#define UA_RESET_ASC 0x29 +#define UA_CHANGED_ASC 0x2a +#define TARGET_CHANGED_ASC 0x3f +#define LUNS_CHANGED_ASCQ 0x0e +#define INSUFF_RES_ASC 0x55 +#define INSUFF_RES_ASCQ 0x3 +#define LOW_POWER_COND_ON_ASC 0x5e /* ASCQ=0 */ +#define POWER_ON_RESET_ASCQ 0x0 +#define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */ +#define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */ +#define CAPACITY_CHANGED_ASCQ 0x9 +#define SAVING_PARAMS_UNSUP 0x39 +#define TRANSPORT_PROBLEM 0x4b +#define THRESHOLD_EXCEEDED 0x5d +#define LOW_POWER_COND_ON 0x5e +#define MISCOMPARE_VERIFY_ASC 0x1d +#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */ +#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16 + + +static inline bool is_aligned(const void * pointer, size_t byte_count) +{ + return ((sg_uintptr_t)pointer % byte_count) == 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; +} + +#if (HAVE_NVME && (! IGNORE_NVME)) + +/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5) + * to the name of its associated char device (e.g. /dev/nvme0). If this + * occurs true is returned and the char device name is placed in 'b' (as + * long as b_len is sufficient). Otherwise false is returned. */ +bool +sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len, + char * b) +{ + uint32_t n, tlen; + const char * cp; + char buff[8]; + + if ((NULL == b) || (b_len < 5)) + return false; /* degenerate cases */ + cp = strstr(nvme_block_devname, "nvme"); + if (NULL == cp) + return false; /* expected to find "nvme" in given name */ + if (1 != sscanf(cp, "nvme%u", &n)) + return false; /* didn't find valid "nvme<number>" */ + snprintf(buff, sizeof(buff), "%u", n); + tlen = (cp - nvme_block_devname) + 4 + strlen(buff); + if ((tlen + 1) > b_len) + return false; /* b isn't long enough to fit output */ + memcpy(b, nvme_block_devname, tlen); + b[tlen] = '\0'; + return true; +} + +static void +build_sense_buffer(bool desc, uint8_t *buf, uint8_t skey, uint8_t asc, + uint8_t ascq) +{ + if (desc) { + buf[0] = 0x72; /* descriptor, current */ + buf[1] = skey; + buf[2] = asc; + buf[3] = ascq; + buf[7] = 0; + } else { + buf[0] = 0x70; /* fixed, current */ + buf[2] = skey; + buf[7] = 0xa; /* Assumes length is 18 bytes */ + buf[12] = asc; + buf[13] = ascq; + } +} + +/* Set in_bit to -1 to indicate no bit position of invalid field */ +static void +mk_sense_asc_ascq(struct sg_pt_linux_scsi * ptp, int sk, int asc, int ascq, + int vb) +{ + bool dsense = ptp->scsi_dsense; + int n; + uint8_t * sbp = (uint8_t *)ptp->io_hdr.response; + + ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + if (vb) + pr2ws("%s: max_response_len=%d too short, want 14 or more\n", + __func__, n); + return; + } else + ptp->io_hdr.response_len = dsense ? 8 : ((n < 18) ? n : 18); + memset(sbp, 0, n); + build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, sk, + asc, ascq); +} + +static void +mk_sense_from_nvme_status(struct sg_pt_linux_scsi * ptp, int vb) +{ + bool ok; + bool dsense = ptp->scsi_dsense; + int n; + uint8_t sstatus, sk, asc, ascq; + uint8_t * sbp = (uint8_t *)ptp->io_hdr.response; + + ok = sg_nvme_status2scsi(ptp->nvme_status, &sstatus, &sk, &asc, &ascq); + if (! ok) { /* can't find a mapping to a SCSI error, so ... */ + sstatus = SAM_STAT_CHECK_CONDITION; + sk = SPC_SK_ILLEGAL_REQUEST; + asc = 0xb; + ascq = 0x0; /* asc: "WARNING" purposely vague */ + } + + ptp->io_hdr.device_status = sstatus; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__, n); + return; + } else + ptp->io_hdr.response_len = (dsense ? 8 : ((n < 18) ? n : 18)); + memset(sbp, 0, n); + build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (vb > 3) + pr2ws("%s: [status, sense_key,asc,ascq]: [0x%x, 0x%x,0x%x,0x%x]\n", + __func__, sstatus, sk, asc, ascq); +} + +/* Set in_bit to -1 to indicate no bit position of invalid field */ +static void +mk_sense_invalid_fld(struct sg_pt_linux_scsi * ptp, bool in_cdb, int in_byte, + int in_bit, int vb) +{ + bool dsense = ptp->scsi_dsense; + int sl, asc, n; + uint8_t * sbp = (uint8_t *)ptp->io_hdr.response; + uint8_t sks[4]; + + ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION; + asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + if (vb) + pr2ws("%s: max_response_len=%d too short, want 14 or more\n", + __func__, n); + return; + } else + ptp->io_hdr.response_len = dsense ? 8 : ((n < 18) ? n : 18); + memset(sbp, 0, n); + build_sense_buffer(dsense, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0); + memset(sks, 0, sizeof(sks)); + sks[0] = 0x80; + if (in_cdb) + sks[0] |= 0x40; + if (in_bit >= 0) { + sks[0] |= 0x8; + sks[0] |= (0x7 & in_bit); + } + sg_put_unaligned_be16(in_byte, sks + 1); + if (dsense) { + sl = sbp[7] + 8; + sbp[7] = sl; + sbp[sl] = 0x2; + sbp[sl + 1] = 0x6; + memcpy(sbp + sl + 4, sks, 3); + } else + memcpy(sbp + 15, sks, 3); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n", + __func__, asc, in_cdb ? 'C' : 'D', in_byte, in_bit); +} + +/* Returns 0 for success. Returns SG_LIB_NVME_STATUS if there is non-zero + * NVMe status (from the completion queue) with the value placed in + * ptp->nvme_status. If Unix error from ioctl then return negated value + * (equivalent -errno from basic Unix system functions like open()). + * CDW0 from the completion queue is placed in ptp->nvme_result in the + * absence of a Unix error. If time_secs is negative it is treated as + * a timeout in milliseconds (of abs(time_secs) ). */ +static int +do_nvme_admin_cmd(struct sg_pt_linux_scsi * ptp, + struct sg_nvme_passthru_cmd *cmdp, void * dp, bool is_read, + int time_secs, int vb) +{ + const uint32_t cmd_len = sizeof(struct sg_nvme_passthru_cmd); + int res; + uint32_t n; + uint16_t sct_sc; + const uint8_t * up = ((const uint8_t *)cmdp) + SG_NVME_PT_OPCODE; + + cmdp->timeout_ms = (time_secs < 0) ? (-time_secs) : (1000 * time_secs); + ptp->os_err = 0; + if (vb > 2) { + pr2ws("NVMe command:\n"); + hex2stderr((const uint8_t *)cmdp, cmd_len, 1); + if ((vb > 3) && (! is_read) && dp) { + uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN); + + if (len > 0) { + n = len; + if ((len < 512) || (vb > 5)) + pr2ws("\nData-out buffer (%u bytes):\n", n); + else { + pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n); + n = 512; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + } + res = ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, cmdp); + if (res < 0) { /* OS error (errno negated) */ + ptp->os_err = -res; + if (vb > 1) { + pr2ws("%s: ioctl opcode=0x%x failed: %s " + "(errno=%d)\n", __func__, *up, strerror(-res), -res); + } + return res; + } + + /* Now res contains NVMe completion queue CDW3 31:17 (15 bits) */ + ptp->nvme_result = cmdp->result; + if (ptp->nvme_direct && ptp->io_hdr.response && + (ptp->io_hdr.max_response_len > 3)) { + /* build 16 byte "sense" buffer */ + uint8_t * sbp = (uint8_t *)ptp->io_hdr.response; + uint16_t st = (uint16_t)res; + + n = ptp->io_hdr.max_response_len; + n = (n < 16) ? n : 16; + memset(sbp, 0 , n); + ptp->io_hdr.response_len = n; + sg_put_unaligned_le32(cmdp->result, + sbp + SG_NVME_PT_CQ_RESULT); + if (n > 15) /* LSBit will be 0 (Phase bit) after (st << 1) */ + sg_put_unaligned_le16(st << 1, sbp + SG_NVME_PT_CQ_STATUS_P); + } + /* clear upper bits (DNR and More) leaving ((SCT << 8) | SC) */ + sct_sc = 0x3ff & res; + ptp->nvme_status = sct_sc; + if (sct_sc) { /* when non-zero, treat as command error */ + if (vb > 1) { + char b[80]; + + pr2ws("%s: ioctl opcode=0x%x failed: NVMe status: %s [0x%x]\n", + __func__, *up, + sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b), sct_sc); + } + return SG_LIB_NVME_STATUS; /* == SCSI_PT_DO_NVME_STATUS */ + } + if ((vb > 3) && is_read && dp) { + uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN); + + if (len > 0) { + n = len; + if ((len < 1024) || (vb > 5)) + pr2ws("\nData-in buffer (%u bytes):\n", n); + else { + pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n); + n = 1024; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + return 0; +} + +/* Returns 0 on success; otherwise a positive value is returned */ +static int +sntl_cache_identity(struct sg_pt_linux_scsi * ptp, int time_secs, int vb) +{ + struct sg_nvme_passthru_cmd cmd; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * up; + + up = sg_memalign(pg_sz, pg_sz, &ptp->free_nvme_id_ctlp, vb > 3); + ptp->nvme_id_ctlp = up; + if (NULL == up) { + pr2ws("%s: sg_memalign() failed to get memory\n", __func__); + return -ENOMEM; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x6; /* Identify */ + cmd.cdw10 = 0x1; /* CNS=0x1 Identify controller */ + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->nvme_id_ctlp; + cmd.data_len = pg_sz; + return do_nvme_admin_cmd(ptp, &cmd, up, true, time_secs, vb); +} + +static const char * nvme_scsi_vendor_str = "NVMe "; +static const uint16_t inq_resp_len = 36; + +static int +sntl_inq(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs, + int vb) +{ + bool evpd; + bool cp_id_ctl = false; + int res; + uint16_t n, alloc_len, pg_cd; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * nvme_id_ns = NULL; + uint8_t * free_nvme_id_ns = NULL; + uint8_t inq_dout[256]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + + if (0x2 & cdbp[1]) { /* Reject CmdDt=1 */ + mk_sense_invalid_fld(ptp, true, 1, 1, vb); + return 0; + } + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) /* should be negative errno */ + return res; + } + memset(inq_dout, 0, sizeof(inq_dout)); + alloc_len = sg_get_unaligned_be16(cdbp + 3); + evpd = !!(0x1 & cdbp[1]); + pg_cd = cdbp[2]; + if (evpd) { /* VPD page responses */ + switch (pg_cd) { + case 0: + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + n = 8; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[4] = 0x0; + inq_dout[5] = 0x80; + inq_dout[6] = 0x83; + inq_dout[n - 1] = 0xde; /* last VPD number */ + break; + case 0x80: + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + sg_put_unaligned_be16(20, inq_dout + 2); + memcpy(inq_dout + 4, ptp->nvme_id_ctlp + 4, 20); /* SN */ + n = 24; + break; + case 0x83: + if ((ptp->nvme_nsid > 0) && + (ptp->nvme_nsid < SG_NVME_BROADCAST_NSID)) { + nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns, + vb > 3); + if (nvme_id_ns) { + struct sg_nvme_passthru_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x6; /* Identify */ + cmd.nsid = ptp->nvme_nsid; + cmd.cdw10 = 0x0; /* CNS=0x0 Identify namespace */ + cmd.addr = (uint64_t)(sg_uintptr_t)nvme_id_ns; + cmd.data_len = pg_sz; + res = do_nvme_admin_cmd(ptp, &cmd, nvme_id_ns, true, + time_secs, vb > 3); + if (res) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + } + } + n = sg_make_vpd_devid_for_nvme(ptp->nvme_id_ctlp, nvme_id_ns, + 0 /* pdt */, -1 /*tproto */, + inq_dout, sizeof(inq_dout)); + if (n > 3) + sg_put_unaligned_be16(n - 4, inq_dout + 2); + if (free_nvme_id_ns) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + break; + case 0xde: + inq_dout[1] = pg_cd; + sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2); + n = 16 + 4096; + cp_id_ctl = true; + break; + default: /* Point to page_code field in cdb */ + mk_sense_invalid_fld(ptp, true, 2, 7, vb); + return 0; + } + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) { + if (cp_id_ctl) { + memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, + (n < 16 ? n : 16)); + if (n > 16) + memcpy((uint8_t *)ptp->io_hdr.din_xferp + 16, + ptp->nvme_id_ctlp, n - 16); + } else + memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, n); + } + } + } else { /* Standard INQUIRY response */ + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); pdt=0 --> SBC; 0xd --> SES */ + inq_dout[2] = 6; /* version: SPC-4 */ + inq_dout[3] = 2; /* NORMACA=0, HISUP=0, response data format: 2 */ + inq_dout[4] = 31; /* so response length is (or could be) 36 bytes */ + inq_dout[6] = 0x40; /* ENCSERV=1 */ + inq_dout[7] = 0x2; /* CMDQUE=1 */ + memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8); /* NVMe not Intel */ + memcpy(inq_dout + 16, ptp->nvme_id_ctlp + 24, 16); /* Prod <-- MN */ + memcpy(inq_dout + 32, ptp->nvme_id_ctlp + 64, 4); /* Rev <-- FR */ + if (alloc_len > 0) { + n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, n); + } + } + return 0; +} + +static int +sntl_rluns(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs, + int vb) +{ + int res; + uint16_t sel_report; + uint32_t alloc_len, k, n, num, max_nsid; + uint8_t * rl_doutp; + uint8_t * up; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + + sel_report = cdbp[2]; + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + max_nsid = sg_get_unaligned_le32(ptp->nvme_id_ctlp + 516); + switch (sel_report) { + case 0: + case 2: + num = max_nsid; + break; + case 1: + case 0x10: + case 0x12: + num = 0; + break; + case 0x11: + num = (1 == ptp->nvme_nsid) ? max_nsid : 0; + break; + default: + if (vb > 1) + pr2ws("%s: bad select_report value: 0x%x\n", __func__, + sel_report); + mk_sense_invalid_fld(ptp, true, 2, 7, vb); + return 0; + } + rl_doutp = (uint8_t *)calloc(num + 1, 8); + if (NULL == rl_doutp) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8) + sg_put_unaligned_be16(k, up); + n = num * 8; + sg_put_unaligned_be32(n, rl_doutp); + n+= 8; + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, rl_doutp, n); + } + res = 0; + free(rl_doutp); + return res; +} + +static int +sntl_tur(struct sg_pt_linux_scsi * ptp, int time_secs, int vb) +{ + int res; + uint32_t pow_state; + struct sg_nvme_passthru_cmd cmd; + + if (vb > 4) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0xa; /* Get feature */ + cmd.nsid = SG_NVME_BROADCAST_NSID; + cmd.cdw10 = 0x2; /* SEL=0 (current), Feature=2 Power Management */ + cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } else { + ptp->os_err = 0; + ptp->nvme_status = 0; + } + pow_state = (0x1f & ptp->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); +#if 0 /* pow_state bounces around too much on laptop */ + if (pow_state) + mk_sense_asc_ascq(ptp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0, + vb); +#endif + return 0; +} + +static int +sntl_req_sense(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool desc; + int res; + uint32_t pow_state, alloc_len, n; + struct sg_nvme_passthru_cmd cmd; + uint8_t rs_dout[64]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + desc = !!(0x1 & cdbp[1]); + alloc_len = cdbp[4]; + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0xa; /* Get feature */ + cmd.nsid = SG_NVME_BROADCAST_NSID; + cmd.cdw10 = 0x2; /* SEL=0 (current), Feature=2 Power Management */ + cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } else { + ptp->os_err = 0; + ptp->nvme_status = 0; + } + ptp->io_hdr.response_len = 0; + pow_state = (0x1f & ptp->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); + memset(rs_dout, 0, sizeof(rs_dout)); + if (pow_state) + build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + LOW_POWER_COND_ON_ASC, 0); + else + build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + NO_ADDITIONAL_SENSE, 0); + n = desc ? 8 : 18; + n = (n < alloc_len) ? n : alloc_len; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, rs_dout, n); + return 0; +} + +/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI + * has a special command (SES Send) to tunnel through pages to an + * enclosure. The NVMe enclosure is meant to understand the SES + * (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_senddiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool pf, self_test; + int res; + uint8_t st_cd, dpg_cd; + uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * dop; + struct sg_nvme_passthru_cmd cmd; + uint8_t * cmd_up = (uint8_t *)&cmd; + + st_cd = 0x7 & (cdbp[1] >> 5); + self_test = !! (0x4 & cdbp[1]); + pf = !! (0x10 & cdbp[1]); + if (vb > 3) + pr2ws("%s: pf=%d, self_test=%d (st_code=%d)\n", __func__, (int)pf, + (int)self_test, (int)st_cd); + if (self_test || st_cd) { + memset(cmd_up, 0, sizeof(cmd)); + cmd_up[SG_NVME_PT_OPCODE] = 0x14; /* Device self-test */ + /* just this namespace (if there is one) and controller */ + sg_put_unaligned_le32(ptp->nvme_nsid, cmd_up + SG_NVME_PT_NSID); + switch (st_cd) { + case 0: /* Here if self_test is set, do short self-test */ + case 1: /* Background short */ + case 5: /* Foreground short */ + nvme_dst = 1; + break; + case 2: /* Background extended */ + case 6: /* Foreground extended */ + nvme_dst = 2; + break; + case 4: /* Abort self-test */ + nvme_dst = 0xf; + break; + default: + pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd); + mk_sense_invalid_fld(ptp, true, 1, 7, vb); + return 0; + } + sg_put_unaligned_le32(nvme_dst, cmd_up + SG_NVME_PT_CDW10); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } + } + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + dout_len = ptp->io_hdr.dout_xfer_len; + if (pf) { + if (0 == alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: PF bit set bit param_list_len=0\n", __func__); + return 0; + } + } else { /* PF bit clear */ + if (alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: param_list_len>0 but PF clear\n", __func__); + return 0; + } else + return 0; /* nothing to do */ + if (dout_len > 0) { + if (vb) + pr2ws("%s: dout given but PF clear\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } + if (dout_len < 4) { + if (vb) + pr2ws("%s: dout length (%u bytes) too short\n", __func__, + dout_len); + return SCSI_PT_DO_BAD_PARAMS; + } + n = dout_len; + n = (n < alloc_len) ? n : alloc_len; + dop = (uint8_t *)ptp->io_hdr.dout_xferp; + if (! is_aligned(dop, pg_sz)) { /* caller best use sg_memalign(,pg_sz) */ + if (vb) + pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)ptp->io_hdr.dout_xferp); + return SCSI_PT_DO_BAD_PARAMS; + } + dpg_cd = dop[0]; + dpg_len = sg_get_unaligned_be16(dop + 2) + 4; + /* should we allow for more than one D_PG is dout ?? */ + n = (n < dpg_len) ? n : dpg_len; /* not yet ... */ + + if (vb) + pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n", + __func__, dpg_cd, dpg_len); + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x1d; /* MI send; hmmm same opcode as SEND DIAG */ + cmd.addr = (uint64_t)(sg_uintptr_t)dop; + cmd.data_len = 0x1000; /* NVMe 4k page size. Maybe determine this? */ + /* dout_len > 0x1000, is this a problem?? */ + cmd.cdw10 = 0x0804; /* NVMe Message Header */ + cmd.cdw11 = 0x9; /* nvme_mi_ses_send; (0x8 -> mi_ses_recv) */ + cmd.cdw13 = n; + res = do_nvme_admin_cmd(ptp, &cmd, dop, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } + } + return res; +} + +/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1) + * NVMe-MI has a special command (SES Receive) to read pages through a + * tunnel from an enclosure. The NVMe enclosure is meant to understand the + * SES (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_recvdiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool pcv; + int res; + uint8_t dpg_cd; + uint32_t alloc_len, n, din_len; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * dip; + struct sg_nvme_passthru_cmd cmd; + + pcv = !! (0x1 & cdbp[1]); + dpg_cd = cdbp[2]; + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + if (vb > 3) + pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__, + dpg_cd, (int)pcv, alloc_len); + din_len = ptp->io_hdr.din_xfer_len; + n = din_len; + n = (n < alloc_len) ? n : alloc_len; + dip = (uint8_t *)ptp->io_hdr.din_xferp; + if (! is_aligned(dip, pg_sz)) { /* caller best use sg_memalign(,pg_sz) */ + if (vb) + pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)ptp->io_hdr.din_xferp); + return SCSI_PT_DO_BAD_PARAMS; + } + + if (vb) + pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__, + dpg_cd); + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x1e; /* MI receive */ + cmd.addr = (uint64_t)(sg_uintptr_t)dip; + cmd.data_len = 0x1000; /* NVMe 4k page size. Maybe determine this? */ + /* din_len > 0x1000, is this a problem?? */ + cmd.cdw10 = 0x0804; /* NVMe Message Header */ + cmd.cdw11 = 0x8; /* nvme_mi_ses_receive */ + cmd.cdw12 = dpg_cd; + cmd.cdw13 = n; + res = do_nvme_admin_cmd(ptp, &cmd, dip, true, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } + ptp->io_hdr.din_resid = din_len - n; + return res; +} + +#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */ +#define F_SA_HIGH 0x100 /* as used by variable length cdbs */ +#define FF_SA (F_SA_HIGH | F_SA_LOW) +#define F_INV_OP 0x200 + +static struct opcode_info_t { + uint8_t opcode; + uint16_t sa; /* service action, 0 for none */ + uint32_t flags; /* OR-ed set of F_* flags */ + uint8_t len_mask[16]; /* len=len_mask[0], then mask for cdb[1]... */ + /* ignore cdb bytes after position 15 */ + } opcode_info_arr[] = { + {0x0, 0, 0, {6, /* TEST UNIT READY */ + 0, 0, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x3, 0, 0, {6, /* REQUEST SENSE */ + 0xe1, 0, 0, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x12, 0, 0, {6, /* INQUIRY */ + 0xe3, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x1c, 0, 0, {6, /* RECEIVE DIAGNOSTIC RESULTS */ + 0x1, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x1d, 0, 0, {6, /* SEND DIAGNOSTIC */ + 0xf7, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0xa0, 0, 0, {12, /* REPORT LUNS */ + 0xe3, 0xff, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} }, + {0xa3, 0xc, F_SA_LOW, {12, /* REPORT SUPPORTED OPERATION CODES */ + 0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, + 0} }, + {0xa3, 0xd, F_SA_LOW, {12, /* REPORT SUPPORTED TASK MAN. FUNCTIONS */ + 0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} }, + + {0xff, 0xffff, 0xffff, {0, /* Sentinel, keep as last element */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, +}; + +static int +sntl_rep_opcodes(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool rctd; + uint8_t reporting_opts, req_opcode, supp; + uint16_t req_sa, u; + uint32_t alloc_len, offset, a_len; + uint32_t pg_sz = sg_get_page_size(); + int k, len, count, bump; + const struct opcode_info_t *oip; + uint8_t *arr; + uint8_t *free_arr; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + rctd = !!(cdbp[2] & 0x80); /* report command timeout desc. */ + reporting_opts = cdbp[2] & 0x7; + req_opcode = cdbp[3]; + req_sa = sg_get_unaligned_be16(cdbp + 4); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4 || alloc_len > 0xffff) { + mk_sense_invalid_fld(ptp, true, 6, -1, vb); + return 0; + } + a_len = pg_sz - 72; + arr = sg_memalign(pg_sz, pg_sz, &free_arr, vb > 3); + if (NULL == arr) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + switch (reporting_opts) { + case 0: /* all commands */ + count = 0; + bump = rctd ? 20 : 8; + for (offset = 4, oip = opcode_info_arr; + (oip->flags != 0xffff) && (offset < a_len); ++oip) { + if (F_INV_OP & oip->flags) + continue; + ++count; + arr[offset] = oip->opcode; + sg_put_unaligned_be16(oip->sa, arr + offset + 2); + if (rctd) + arr[offset + 5] |= 0x2; + if (FF_SA & oip->flags) + arr[offset + 5] |= 0x1; + sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6); + if (rctd) + sg_put_unaligned_be16(0xa, arr + offset + 8); + offset += bump; + } + sg_put_unaligned_be32(count * bump, arr + 0); + break; + case 1: /* one command: opcode only */ + case 2: /* one command: opcode plus service action */ + case 3: /* one command: if sa==0 then opcode only else opcode+sa */ + for (oip = opcode_info_arr; oip->flags != 0xffff; ++oip) { + if ((req_opcode == oip->opcode) && (req_sa == oip->sa)) + break; + } + if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) { + supp = 1; + offset = 4; + } else { + if (1 == reporting_opts) { + if (FF_SA & oip->flags) { + mk_sense_invalid_fld(ptp, true, 2, 2, vb); + free(free_arr); + return 0; + } + req_sa = 0; + } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) { + mk_sense_invalid_fld(ptp, true, 4, -1, vb); + free(free_arr); + return 0; + } + if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode)) + supp = 3; + else if (0 == (FF_SA & oip->flags)) + supp = 1; + else if (req_sa != oip->sa) + supp = 1; + else + supp = 3; + if (3 == supp) { + u = oip->len_mask[0]; + sg_put_unaligned_be16(u, arr + 2); + arr[4] = oip->opcode; + for (k = 1; k < u; ++k) + arr[4 + k] = (k < 16) ? + oip->len_mask[k] : 0xff; + offset = 4 + u; + } else + offset = 4; + } + arr[1] = (rctd ? 0x80 : 0) | supp; + if (rctd) { + sg_put_unaligned_be16(0xa, arr + offset); + offset += 12; + } + break; + default: + mk_sense_invalid_fld(ptp, true, 2, 2, vb); + free(free_arr); + return 0; + } + offset = (offset < a_len) ? offset : a_len; + len = (offset < alloc_len) ? offset : alloc_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len; + if (len > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, arr, len); + free(free_arr); + return 0; +} + +static int +sntl_rep_tmfs(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool repd; + uint32_t alloc_len, len; + uint8_t arr[16]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + memset(arr, 0, sizeof(arr)); + repd = !!(cdbp[2] & 0x80); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4) { + mk_sense_invalid_fld(ptp, true, 6, -1, vb); + return 0; + } + arr[0] = 0xc8; /* ATS | ATSS | LURS */ + arr[1] = 0x1; /* ITNRS */ + if (repd) { + arr[3] = 0xc; + len = 16; + } else + len = 4; + + len = (len < alloc_len) ? len : alloc_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len; + if (len > 0) + memcpy((uint8_t *)ptp->io_hdr.din_xferp, arr, len); + 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. */ +int +sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb) +{ + bool scsi_cdb; + bool is_read = false; + int n, len; + uint16_t sa; + struct sg_pt_linux_scsi * ptp = &vp->impl; + struct sg_nvme_passthru_cmd cmd; + const uint8_t * cdbp; + void * dp = NULL; + + if (! ptp->io_hdr.request) { + if (vb) + pr2ws("No NVMe command given (set_scsi_pt_cdb())\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + if (fd >= 0) { + if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) { + if (vb) + 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 (vb) + pr2ws("%s: invalid file descriptors\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + n = ptp->io_hdr.request_len; + cdbp = (const uint8_t *)ptp->io_hdr.request; + if (vb > 3) + pr2ws("%s: opcode=0x%x, fd=%d, time_secs=%d\n", __func__, cdbp[0], + fd, time_secs); + scsi_cdb = sg_is_scsi_cdb(cdbp, n); + /* direct NVMe command (i.e. 64 bytes long) or SNTL */ + ptp->nvme_direct = ! scsi_cdb; + if (scsi_cdb) { + switch (cdbp[0]) { + case SCSI_INQUIRY_OPC: + return sntl_inq(ptp, cdbp, time_secs, vb); + case SCSI_REPORT_LUNS_OPC: + return sntl_rluns(ptp, cdbp, time_secs, vb); + case SCSI_TEST_UNIT_READY_OPC: + return sntl_tur(ptp, time_secs, vb); + case SCSI_REQUEST_SENSE_OPC: + return sntl_req_sense(ptp, cdbp, time_secs, vb); + case SCSI_SEND_DIAGNOSTIC_OPC: + return sntl_senddiag(ptp, cdbp, time_secs, vb); + case SCSI_RECEIVE_DIAGNOSTIC_OPC: + return sntl_recvdiag(ptp, cdbp, time_secs, vb); + case SCSI_MAINT_IN_OPC: + sa = 0x1f & cdbp[1]; /* service action */ + if (SCSI_REP_SUP_OPCS_OPC == sa) + return sntl_rep_opcodes(ptp, cdbp, time_secs, vb); + else if (SCSI_REP_SUP_TMFS_OPC == sa) + return sntl_rep_tmfs(ptp, cdbp, time_secs, vb); + /* fall through */ + default: + if (vb > 2) { + char b[64]; + + sg_get_command_name(cdbp, -1, sizeof(b), b); + pr2ws("%s: no translation to NVMe for SCSI %s command\n", + __func__, b); + } + mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE, + 0, vb); + return 0; + } + } + 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, (const uint8_t *)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; + dp = (void *)ptp->io_hdr.din_xferp; + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.din_xferp; + is_read = true; + } else if (ptp->io_hdr.dout_xfer_len > 0) { + cmd.data_len = ptp->io_hdr.dout_xfer_len; + dp = (void *)ptp->io_hdr.dout_xferp; + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.dout_xferp; + is_read = false; + } + return do_nvme_admin_cmd(ptp, &cmd, dp, is_read, time_secs, vb); +} + +#else /* (HAVE_NVME && (! IGNORE_NVME)) */ + +int +sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb) +{ + if (vb) + pr2ws("%s: not supported\n", __func__); + if (vp) { ; } /* suppress warning */ + if (fd) { ; } /* suppress warning */ + if (time_secs) { ; } /* suppress warning */ + return -ENOTTY; /* inappropriate ioctl error */ +} + +#endif /* (HAVE_NVME && (! IGNORE_NVME)) */ diff --git a/tools/sg_write_buffer/sg_write_buffer.c b/tools/sg_write_buffer/sg_write_buffer.c new file mode 100644 index 0000000..18d8f6f --- /dev/null +++ b/tools/sg_write_buffer/sg_write_buffer.c @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2006-2018 Luben Tuikov and 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. + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <ctype.h> +#include <string.h> +#include <getopt.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +#ifdef SG_LIB_WIN32 +#ifdef SG_LIB_WIN32_DIRECT +#include "sg_pt.h" /* needed for scsi_pt_win32_direct() */ +#endif +#endif + +/* + * This utility issues the SCSI WRITE BUFFER command to the given device. + */ + +static const char * version_str = "1.24 20180111"; /* spc5r18 */ + +#define ME "sg_write_buffer: " +#define DEF_XFER_LEN (8 * 1024 * 1024) +#define EBUFF_SZ 256 + +#define WRITE_BUFFER_CMD 0x3b +#define WRITE_BUFFER_CMDLEN 10 +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define DEF_PT_TIMEOUT 300 /* 300 seconds, 5 minutes */ + +static struct option long_options[] = { + {"bpw", required_argument, 0, 'b'}, + {"dry-run", no_argument, 0, 'd'}, + {"dry_run", no_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"id", required_argument, 0, 'i'}, + {"in", required_argument, 0, 'I'}, + {"length", required_argument, 0, 'l'}, + {"mode", required_argument, 0, 'm'}, + {"offset", required_argument, 0, 'o'}, + {"read-stdin", no_argument, 0, 'r'}, + {"read_stdin", no_argument, 0, 'r'}, + {"raw", no_argument, 0, 'r'}, + {"skip", required_argument, 0, 's'}, + {"specific", required_argument, 0, 'S'}, + {"timeout", required_argument, 0, 't' }, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + + +static void +usage() +{ + pr2serr("Usage: " + "sg_write_buffer [--bpw=CS] [--dry-run] [--help] [--id=ID] " + "[--in=FILE]\n" + " [--length=LEN] [--mode=MO] " + "[--offset=OFF]\n" + " [--read-stdin] [--skip=SKIP] " + "[--specific=MS]\n" + " [--timeout=TO] [--verbose] [--version] " + "DEVICE\n" + " where:\n" + " --bpw=CS|-b CS CS is chunk size: bytes per write " + "buffer\n" + " command (def: 0 -> as many as " + "possible)\n" + " --dry-run|-d skip WRITE BUFFER commands, do " + "everything else\n" + " --help|-h print out usage message then exit\n" + " --id=ID|-i ID buffer identifier (0 (default) to " + "255)\n" + " --in=FILE|-I FILE read from FILE ('-I -' read " + "from stdin)\n" + " --length=LEN|-l LEN length in bytes to write; may be " + "deduced from\n" + " FILE\n" + " --mode=MO|-m MO write buffer mode, MO is number or " + "acronym\n" + " (def: 0 -> 'combined header and " + "data' (obs))\n" + " --offset=OFF|-o OFF buffer offset (unit: bytes, def: 0)\n" + " --read-stdin|-r read from stdin (same as '-I -')\n" + " --skip=SKIP|-s SKIP bytes in file FILE to skip before " + "reading\n" + " --specific=MS|-S MS mode specific value; 3 bit field " + "(0 to 7)\n" + " --timeout=TO|-t TO command timeout in seconds (def: " + "300)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Performs one or more SCSI WRITE BUFFER commands. Use '-m xxx' " + "to list\navailable modes. A chunk size of 4 KB ('--bpw=4k') " + "seems to work well.\nExample: sg_write_buffer -b 4k -I xxx.lod " + "-m 7 /dev/sg3\n" + ); + +} + +#define MODE_HEADER_DATA 0 +#define MODE_VENDOR 1 +#define MODE_DATA 2 +#define MODE_DNLD_MC 4 +#define MODE_DNLD_MC_SAVE 5 +#define MODE_DNLD_MC_OFFS 6 +#define MODE_DNLD_MC_OFFS_SAVE 7 +#define MODE_ECHO_BUFFER 0x0A +#define MODE_DNLD_MC_EV_OFFS_DEFER 0x0D +#define MODE_DNLD_MC_OFFS_DEFER 0x0E +#define MODE_ACTIVATE_MC 0x0F +#define MODE_EN_EX_ECHO 0x1A +#define MODE_DIS_EX 0x1B +#define MODE_DNLD_ERR_HISTORY 0x1C + + +struct mode_s { + const char *mode_string; + int mode; + const char *comment; +}; + +static struct mode_s mode_arr[] = { + {"hd", MODE_HEADER_DATA, "combined header and data " + "(obsolete)"}, + {"vendor", MODE_VENDOR, "vendor specific"}, + {"data", MODE_DATA, "data"}, + {"dmc", MODE_DNLD_MC, "download microcode and activate"}, + {"dmc_save", MODE_DNLD_MC_SAVE, "download microcode, save and " + "activate"}, + {"dmc_offs", MODE_DNLD_MC_OFFS, "download microcode with offsets " + "and activate"}, + {"dmc_offs_save", MODE_DNLD_MC_OFFS_SAVE, "download microcode with " + "offsets, save and\n\t\t\t\tactivate"}, + {"echo", MODE_ECHO_BUFFER, "write data to echo buffer"}, + {"dmc_offs_ev_defer", MODE_DNLD_MC_EV_OFFS_DEFER, "download " + "microcode with offsets, select\n\t\t\t\tactivation event, " + "save and defer activation"}, + {"dmc_offs_defer", MODE_DNLD_MC_OFFS_DEFER, "download microcode " + "with offsets, save and\n\t\t\t\tdefer activation"}, + {"activate_mc", MODE_ACTIVATE_MC, "activate deferred microcode"}, + {"en_ex", MODE_EN_EX_ECHO, "enable expander communications " + "protocol and\n\t\t\t\techo buffer (obsolete)"}, + {"dis_ex", MODE_DIS_EX, "disable expander communications " + "protocol\n\t\t\t\t(obsolete)"}, + {"deh", MODE_DNLD_ERR_HISTORY, "download application client " + "error history "}, + {NULL, 0, NULL}, +}; + +static void +print_modes(void) +{ + const struct mode_s * mp; + + pr2serr("The modes parameter argument can be numeric (hex or decimal)\n" + "or symbolic:\n"); + for (mp = mode_arr; mp->mode_string; ++mp) { + pr2serr(" %2d (0x%02x) %-18s%s\n", mp->mode, mp->mode, + mp->mode_string, mp->comment); + } + pr2serr("\nAdditionally '--bpw=<val>,act' does a activate deferred " + "microcode after\nsuccessful dmc_offs_defer and " + "dmc_offs_ev_defer mode downloads.\n"); +} + + +int +main(int argc, char * argv[]) +{ + bool bpw_then_activate = false; + bool dry_run = false; + bool got_stdin = false; + bool wb_len_given = false; + int sg_fd, infd, res, c, len, k, n; + int bpw = 0; + int do_help = 0; + int ret = 0; + int verbose = 0; + int wb_id = 0; + int wb_len = 0; + int wb_mode = 0; + int wb_offset = 0; + int wb_skip = 0; + int wb_timeout = DEF_PT_TIMEOUT; + int wb_mspec = 0; + const char * device_name = NULL; + const char * file_name = NULL; + unsigned char * dop = NULL; + char * cp; + const struct mode_s * mp; + char ebuff[EBUFF_SZ]; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "b:dhi:I:l:m:o:rs:S:t:vV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'b': + bpw = sg_get_num(optarg); + if (bpw < 0) { + pr2serr("argument to '--bpw' should be in a positive " + "number\n"); + return SG_LIB_SYNTAX_ERROR; + } + if ((cp = strchr(optarg, ','))) { + if (0 == strncmp("act", cp + 1, 3)) + bpw_then_activate = true; + } + break; + case 'd': + dry_run = true; + break; + case 'h': + case '?': + ++do_help; + break; + case 'i': + wb_id = sg_get_num(optarg); + if ((wb_id < 0) || (wb_id > 255)) { + pr2serr("argument to '--id' should be in the range 0 to " + "255\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'I': + file_name = optarg; + break; + case 'l': + wb_len = sg_get_num(optarg); + if (wb_len < 0) { + pr2serr("bad argument to '--length'\n"); + return SG_LIB_SYNTAX_ERROR; + } + wb_len_given = true; + break; + case 'm': + if (isdigit(*optarg)) { + wb_mode = sg_get_num(optarg); + if ((wb_mode < 0) || (wb_mode > 31)) { + pr2serr("argument to '--mode' should be in the range 0 " + "to 31\n"); + return SG_LIB_SYNTAX_ERROR; + } + } else { + len = strlen(optarg); + for (mp = mode_arr; mp->mode_string; ++mp) { + if (0 == strncmp(mp->mode_string, optarg, len)) { + wb_mode = mp->mode; + break; + } + } + if (! mp->mode_string) { + print_modes(); + return SG_LIB_SYNTAX_ERROR; + } + } + break; + case 'o': + wb_offset = sg_get_num(optarg); + if (wb_offset < 0) { + pr2serr("bad argument to '--offset'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'r': /* --read-stdin and --raw (previous name) */ + file_name = "-"; + break; + case 's': + wb_skip = sg_get_num(optarg); + if (wb_skip < 0) { + pr2serr("bad argument to '--skip'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'S': + wb_mspec = sg_get_num(optarg); + if ((wb_mspec < 0) || (wb_mspec > 7)) { + pr2serr("expected argument to '--specific' to be 0 to 7\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 't': + wb_timeout = sg_get_num(optarg); + if (wb_timeout < 0) { + pr2serr("Invalid argument to '--timeout'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'v': + ++verbose; + break; + case 'V': + pr2serr(ME "version: %s\n", version_str); + return 0; + default: + pr2serr("unrecognised option code 0x%x ??\n", c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (do_help) { + if (do_help > 1) { + usage(); + pr2serr("\n"); + print_modes(); + } else + usage(); + return 0; + } + if (optind < argc) { + if (NULL == device_name) { + device_name = argv[optind]; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + pr2serr("Unexpected extra argument: %s\n", argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + + if (NULL == device_name) { + pr2serr("missing device name!\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + + if ((wb_len > 0) && (bpw > wb_len)) { + pr2serr("trim chunk size (CS) to be the same as LEN\n"); + bpw = wb_len; + } + +#ifdef SG_LIB_WIN32 +#ifdef SG_LIB_WIN32_DIRECT + if (verbose > 4) + pr2serr("Initial win32 SPT interface state: %s\n", + scsi_pt_win32_spt_state() ? "direct" : "indirect"); + scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */); +#endif +#endif + + sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose); + if (sg_fd < 0) { + pr2serr(ME "open error: %s: %s\n", device_name, + safe_strerror(-sg_fd)); + return SG_LIB_FILE_ERROR; + } + if (file_name || (wb_len > 0)) { + if (0 == wb_len) + wb_len = DEF_XFER_LEN; + if (NULL == (dop = (unsigned char *)malloc(wb_len))) { + pr2serr(ME "out of memory\n"); + ret = SG_LIB_SYNTAX_ERROR; + goto err_out; + } + memset(dop, 0xff, wb_len); + if (file_name) { + got_stdin = (0 == strcmp(file_name, "-")); + if (got_stdin) { + if (wb_skip > 0) { + pr2serr("Can't skip on stdin\n"); + ret = SG_LIB_FILE_ERROR; + goto err_out; + } + infd = STDIN_FILENO; + } else { + if ((infd = open(file_name, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + ME "could not open %s for reading", file_name); + perror(ebuff); + ret = SG_LIB_FILE_ERROR; + goto err_out; + } else if (sg_set_binary_mode(infd) < 0) + perror("sg_set_binary_mode"); + if (wb_skip > 0) { + if (lseek(infd, wb_skip, SEEK_SET) < 0) { + snprintf(ebuff, EBUFF_SZ, ME "couldn't skip to " + "required position on %s", file_name); + perror(ebuff); + close(infd); + ret = SG_LIB_FILE_ERROR; + goto err_out; + } + } + } + res = read(infd, dop, wb_len); + if (res < 0) { + snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s", + file_name); + perror(ebuff); + if (! got_stdin) + close(infd); + ret = SG_LIB_FILE_ERROR; + goto err_out; + } + if (res < wb_len) { + if (wb_len_given) { + pr2serr("tried to read %d bytes from %s, got %d bytes\n", + wb_len, file_name, res); + pr2serr("pad with 0xff bytes and continue\n"); + } else { + if (verbose) { + pr2serr("tried to read %d bytes from %s, got %d " + "bytes\n", wb_len, file_name, res); + pr2serr("will write %d bytes", res); + if ((bpw > 0) && (bpw < wb_len)) + pr2serr(", %d bytes per WRITE BUFFER command\n", + bpw); + else + pr2serr("\n"); + } + wb_len = res; + } + } + if (! got_stdin) + close(infd); + } + } + + res = 0; + if (bpw > 0) { + for (k = 0; k < wb_len; k += n) { + n = wb_len - k; + if (n > bpw) + n = bpw; + if (verbose) + pr2serr("sending write buffer, mode=0x%x, mspec=%d, id=%d, " + " offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id, + wb_offset + k, n); + if (dry_run) { + if (verbose) + pr2serr("skipping WRITE BUFFER command due to " + "--dry-run\n"); + res = 0; + } else + res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id, + wb_offset + k, dop + k, n, + wb_timeout, true, verbose); + if (res) + break; + } + if (bpw_then_activate) { + if (verbose) + pr2serr("sending Activate deferred microcode [0xf]\n"); + if (dry_run) { + if (verbose) + pr2serr("skipping WRITE BUFFER(ACTIVATE) command due to " + "--dry-run\n"); + res = 0; + } else + res = sg_ll_write_buffer_v2(sg_fd, MODE_ACTIVATE_MC, + 0 /* buffer_id */, + 0 /* buffer_offset */, 0, + NULL, 0, wb_timeout, true, + verbose); + } + } else { + if (verbose) + pr2serr("sending single write buffer, mode=0x%x, mpsec=%d, " + "id=%d, offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id, + wb_offset, wb_len); + if (dry_run) { + if (verbose) + pr2serr("skipping WRITE BUFFER(all in one) command due to " + "--dry-run\n"); + res = 0; + } else + res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id, + wb_offset, dop, wb_len, wb_timeout, + true, verbose); + } + if (0 != res) { + char b[80]; + + ret = res; + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("Write buffer failed: %s\n", b); + } + +err_out: + if (dop) + free(dop); + res = sg_cmds_close_device(sg_fd); + if (res < 0) { + pr2serr("close error: %s\n", safe_strerror(-res)); + if (0 == ret) + return SG_LIB_FILE_ERROR; + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} -- 2.15.0.531.g2ccb3012c9-goog ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU 2018-03-14 0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim 2018-03-14 2:10 ` Junling Zheng @ 2018-03-16 8:29 ` Chao Yu 2018-03-17 4:59 ` Jaegeuk Kim 1 sibling, 1 reply; 8+ messages in thread From: Chao Yu @ 2018-03-16 8:29 UTC (permalink / raw) To: Jaegeuk Kim, linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim Hi Jaegeuk, On 2018/3/14 8:08, Jaegeuk Kim wrote: > From: Hyojun Kim <hyojun@google.com> > > sg_write_buffer sources are added for FFU. Could you please explain more details about why we need to add those source codes? ;) Thanks, ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU 2018-03-16 8:29 ` [PATCH 2/2] " Chao Yu @ 2018-03-17 4:59 ` Jaegeuk Kim 2018-03-19 2:40 ` Chao Yu 0 siblings, 1 reply; 8+ messages in thread From: Jaegeuk Kim @ 2018-03-17 4:59 UTC (permalink / raw) To: Chao Yu; +Cc: Hyojun Kim, Jaegeuk Kim, linux-f2fs-devel On 03/16, Chao Yu wrote: > Hi Jaegeuk, > > On 2018/3/14 8:08, Jaegeuk Kim wrote: > > From: Hyojun Kim <hyojun@google.com> > > > > sg_write_buffer sources are added for FFU. > > Could you please explain more details about why we need to add those source > codes? ;) We need this for FFU. What else do we need? > > Thanks, ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU 2018-03-17 4:59 ` Jaegeuk Kim @ 2018-03-19 2:40 ` Chao Yu 0 siblings, 0 replies; 8+ messages in thread From: Chao Yu @ 2018-03-19 2:40 UTC (permalink / raw) To: Jaegeuk Kim; +Cc: Hyojun Kim, Jaegeuk Kim, linux-f2fs-devel On 2018/3/17 12:59, Jaegeuk Kim wrote: > On 03/16, Chao Yu wrote: >> Hi Jaegeuk, >> >> On 2018/3/14 8:08, Jaegeuk Kim wrote: >>> From: Hyojun Kim <hyojun@google.com> >>> >>> sg_write_buffer sources are added for FFU. >> >> Could you please explain more details about why we need to add those source >> codes? ;) > > We need this for FFU. What else do we need? You mean Field Firmware Update? Oh, I don't know, just be curious to know how f2fs using these drive to do FFU. Thanks, > >> >> Thanks, > > . > ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) 2018-03-14 0:08 [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Jaegeuk Kim 2018-03-14 0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim @ 2018-03-16 8:26 ` Chao Yu 1 sibling, 0 replies; 8+ messages in thread From: Chao Yu @ 2018-03-16 8:26 UTC (permalink / raw) To: Jaegeuk Kim, linux-f2fs-devel; +Cc: Hyojun Kim On 2018/3/14 8:08, Jaegeuk Kim wrote: > From: Hyojun Kim <hyojun@google.com> > > It was reported that #pragma pack(1) could create unwanted > influences. pack(push, 1) and pack(pop) are used instead. > > Signed-off-by: Hyojun Kim <hyojun@google.com> > Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org> Reviewed-by: Chao Yu <yuchao0@huawei.com> Thanks, ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2018-03-19 2:40 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2018-03-14 0:08 [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Jaegeuk Kim 2018-03-14 0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim 2018-03-14 2:10 ` Junling Zheng 2018-03-14 4:27 ` [PATCH 2/2 v2] " Jaegeuk Kim 2018-03-16 8:29 ` [PATCH 2/2] " Chao Yu 2018-03-17 4:59 ` Jaegeuk Kim 2018-03-19 2:40 ` Chao Yu 2018-03-16 8:26 ` [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Chao Yu
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).