| /* |
| * Copyright (c) 2005-2015 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.25 20151217 */ |
| |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "sg_pt.h" |
| #include "sg_lib.h" |
| #include "sg_linux_inc.h" |
| |
| #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 |
| |
| |
| #ifdef __GNUC__ |
| 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(IGNORE_LINUX_BSG) || ! defined(HAVE_LINUX_BSG_H) |
| /* |
| * sg(v3) via SG_IO ioctl on a sg node or other node that accepts that ioctl. |
| * Decision has been made at compile time because either: |
| * a) no /usr/include/linux/bsg.h header file was found, or |
| * b) the builder gave the '--enable-no-linux-bsg' option to ./configure |
| */ |
| |
| |
| struct sg_pt_linux_scsi { |
| struct sg_io_hdr io_hdr; |
| int in_err; |
| int os_err; |
| }; |
| |
| struct sg_pt_base { |
| struct sg_pt_linux_scsi impl; |
| }; |
| |
| |
| /* Returns >= 0 if successful. If error in Unix returns negated errno. */ |
| int |
| scsi_pt_open_device(const char * device_name, int read_only, int verbose) |
| { |
| int oflags = O_NONBLOCK; |
| |
| oflags |= (read_only ? O_RDONLY : O_RDWR); |
| return scsi_pt_open_flags(device_name, oflags, verbose); |
| } |
| |
| /* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed */ |
| /* together. The 'flags' argument is advisory and may be ignored. */ |
| /* Returns >= 0 if successful, otherwise returns negated errno. */ |
| int |
| scsi_pt_open_flags(const char * device_name, int flags, int verbose) |
| { |
| int fd; |
| |
| if (verbose > 1) { |
| pr2ws("open %s with flags=0x%x\n", device_name, flags); |
| } |
| fd = open(device_name, flags); |
| if (fd < 0) |
| fd = -errno; |
| return fd; |
| } |
| |
| /* Returns 0 if successful. If error in Unix returns negated errno. */ |
| int |
| scsi_pt_close_device(int device_fd) |
| { |
| int res; |
| |
| res = close(device_fd); |
| if (res < 0) |
| res = -errno; |
| return res; |
| } |
| |
| |
| struct sg_pt_base * |
| construct_scsi_pt_obj() |
| { |
| 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) { |
| ptp->io_hdr.interface_id = 'S'; |
| ptp->io_hdr.dxfer_direction = SG_DXFER_NONE; |
| } |
| return (struct sg_pt_base *)ptp; |
| } |
| |
| void |
| destruct_scsi_pt_obj(struct sg_pt_base * vp) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp) |
| free(ptp); |
| } |
| |
| void |
| clear_scsi_pt_obj(struct sg_pt_base * vp) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp) { |
| memset(ptp, 0, sizeof(struct sg_pt_linux_scsi)); |
| ptp->io_hdr.interface_id = 'S'; |
| ptp->io_hdr.dxfer_direction = SG_DXFER_NONE; |
| } |
| } |
| |
| void |
| set_scsi_pt_cdb(struct sg_pt_base * vp, const unsigned char * cdb, |
| int cdb_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.cmdp) |
| ++ptp->in_err; |
| ptp->io_hdr.cmdp = (unsigned char *)cdb; |
| ptp->io_hdr.cmd_len = cdb_len; |
| } |
| |
| void |
| set_scsi_pt_sense(struct sg_pt_base * vp, unsigned char * sense, |
| int max_sense_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.sbp) |
| ++ptp->in_err; |
| memset(sense, 0, max_sense_len); |
| ptp->io_hdr.sbp = sense; |
| ptp->io_hdr.mx_sb_len = max_sense_len; |
| } |
| |
| /* Setup for data transfer from device */ |
| void |
| set_scsi_pt_data_in(struct sg_pt_base * vp, unsigned char * dxferp, |
| int dxfer_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.dxferp) |
| ++ptp->in_err; |
| if (dxfer_len > 0) { |
| ptp->io_hdr.dxferp = dxferp; |
| ptp->io_hdr.dxfer_len = dxfer_len; |
| ptp->io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; |
| } |
| } |
| |
| /* Setup for data transfer toward device */ |
| void |
| set_scsi_pt_data_out(struct sg_pt_base * vp, const unsigned char * dxferp, |
| int dxfer_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.dxferp) |
| ++ptp->in_err; |
| if (dxfer_len > 0) { |
| ptp->io_hdr.dxferp = (unsigned char *)dxferp; |
| ptp->io_hdr.dxfer_len = dxfer_len; |
| ptp->io_hdr.dxfer_direction = SG_DXFER_TO_DEV; |
| } |
| } |
| |
| void |
| set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| ptp->io_hdr.pack_id = pack_id; |
| } |
| |
| void |
| set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| ++ptp->in_err; |
| if (tag) { ; } /* unused, suppress warning */ |
| } |
| |
| /* Note that task management function codes are transport specific */ |
| void |
| set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| ++ptp->in_err; |
| if (tmf_code) { ; } /* unused, suppress warning */ |
| } |
| |
| void |
| set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| ++ptp->in_err; |
| if (attribute) { ; } /* unused, suppress warning */ |
| if (priority) { ; } /* unused, suppress warning */ |
| } |
| |
| #ifndef SG_FLAG_Q_AT_TAIL |
| #define SG_FLAG_Q_AT_TAIL 0x10 |
| #endif |
| #ifndef SG_FLAG_Q_AT_HEAD |
| #define SG_FLAG_Q_AT_HEAD 0x20 |
| #endif |
| |
| void |
| set_scsi_pt_flags(struct sg_pt_base * vp, int flags) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| /* default action of sg driver [sg v3 interface] is QUEUE_AT_HEAD */ |
| /* default action of block layer SG_IO ioctl is QUEUE_AT_TAIL */ |
| if (SCSI_PT_FLAGS_QUEUE_AT_HEAD & flags) { /* favour AT_HEAD */ |
| ptp->io_hdr.flags |= SG_FLAG_Q_AT_HEAD; |
| ptp->io_hdr.flags &= ~SG_FLAG_Q_AT_TAIL; |
| } else if (SCSI_PT_FLAGS_QUEUE_AT_TAIL & flags) { |
| ptp->io_hdr.flags |= SG_FLAG_Q_AT_TAIL; |
| ptp->io_hdr.flags &= ~SG_FLAG_Q_AT_HEAD; |
| } |
| } |
| |
| /* Executes SCSI command (or at least forwards it to lower layers). |
| * Clears os_err field prior to active call (whose result may set it |
| * again). */ |
| int |
| do_scsi_pt(struct sg_pt_base * vp, int fd, int time_secs, int verbose) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| ptp->os_err = 0; |
| if (ptp->in_err) { |
| if (verbose) |
| pr2ws("Replicated or unused set_scsi_pt... functions\n"); |
| return SCSI_PT_DO_BAD_PARAMS; |
| } |
| if (NULL == ptp->io_hdr.cmdp) { |
| if (verbose) |
| pr2ws("No SCSI command (cdb) given\n"); |
| return SCSI_PT_DO_BAD_PARAMS; |
| } |
| /* io_hdr.timeout is in milliseconds */ |
| ptp->io_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : |
| DEF_TIMEOUT); |
| if (ptp->io_hdr.sbp && (ptp->io_hdr.mx_sb_len > 0)) |
| memset(ptp->io_hdr.sbp, 0, ptp->io_hdr.mx_sb_len); |
| if (ioctl(fd, SG_IO, &ptp->io_hdr) < 0) { |
| ptp->os_err = errno; |
| if (verbose > 1) |
| pr2ws("ioctl(SG_IO) failed: %s (errno=%d)\n", |
| strerror(ptp->os_err), ptp->os_err); |
| return -ptp->os_err; |
| } |
| return 0; |
| } |
| |
| int |
| get_scsi_pt_result_category(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| int dr_st = ptp->io_hdr.driver_status & SG_LIB_DRIVER_MASK; |
| int scsi_st = ptp->io_hdr.status & 0x7e; |
| |
| if (ptp->os_err) |
| return SCSI_PT_RESULT_OS_ERR; |
| else if (ptp->io_hdr.host_status) |
| return SCSI_PT_RESULT_TRANSPORT_ERR; |
| else if (dr_st && (SG_LIB_DRIVER_SENSE != dr_st)) |
| return SCSI_PT_RESULT_TRANSPORT_ERR; |
| else if ((SG_LIB_DRIVER_SENSE == dr_st) || |
| (SAM_STAT_CHECK_CONDITION == scsi_st) || |
| (SAM_STAT_COMMAND_TERMINATED == scsi_st)) |
| return SCSI_PT_RESULT_SENSE; |
| else if (scsi_st) |
| return SCSI_PT_RESULT_STATUS; |
| else |
| return SCSI_PT_RESULT_GOOD; |
| } |
| |
| int |
| get_scsi_pt_resid(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.resid; |
| } |
| |
| int |
| get_scsi_pt_status_response(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.status; |
| } |
| |
| int |
| get_scsi_pt_sense_len(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.sb_len_wr; |
| } |
| |
| int |
| get_scsi_pt_duration_ms(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.duration; |
| } |
| |
| int |
| get_scsi_pt_transport_err(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return (ptp->io_hdr.host_status << 8) + ptp->io_hdr.driver_status; |
| } |
| |
| int |
| get_scsi_pt_os_err(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->os_err; |
| } |
| |
| /* Returns b which will contain a null char terminated string (if |
| * max_b_len > 0). That string should decode Linux driver and host |
| * status values. */ |
| char * |
| get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len, |
| char * b) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| int ds = ptp->io_hdr.driver_status; |
| int hs = ptp->io_hdr.host_status; |
| int n, m; |
| char * cp = b; |
| int driv; |
| const char * driv_cp = "unknown"; |
| |
| if (max_b_len < 1) |
| return b; |
| m = max_b_len; |
| n = 0; |
| if (hs) { |
| if ((hs < 0) || (hs >= LINUX_HOST_BYTES_SZ)) |
| n = snprintf(cp, m, "Host_status=0x%02x is unknown\n", hs); |
| else |
| n = snprintf(cp, m, "Host_status=0x%02x [%s]\n", hs, |
| linux_host_bytes[hs]); |
| } |
| m -= n; |
| if (m < 1) { |
| b[max_b_len - 1] = '\0'; |
| return b; |
| } |
| cp += n; |
| driv = ds & SG_LIB_DRIVER_MASK; |
| if (driv < LINUX_DRIVER_BYTES_SZ) |
| driv_cp = linux_driver_bytes[driv]; |
| #if 0 |
| sugg = (ds & SG_LIB_SUGGEST_MASK) >> 4; |
| if (sugg < LINUX_DRIVER_SUGGESTS_SZ) |
| sugg_cp = linux_driver_suggests[sugg]; |
| #endif |
| n = snprintf(cp, m, "Driver_status=0x%02x [%s]\n", ds, driv_cp); |
| m -= n; |
| if (m < 1) |
| b[max_b_len - 1] = '\0'; |
| return b; |
| } |
| |
| char * |
| get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| const char * cp; |
| |
| cp = safe_strerror(ptp->os_err); |
| strncpy(b, cp, max_b_len); |
| if ((int)strlen(cp) >= max_b_len) |
| b[max_b_len - 1] = '\0'; |
| return b; |
| } |
| |
| |
| // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| #else /* allow for runtime selection of sg v3 or v4 (via bsg) */ |
| // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| /* |
| * So bsg is an option. Thus we make a runtime decision. If all the following |
| * are true we use sg v4 which is only currently supported on bsg device |
| * nodes: |
| * a) there is a bsg entry in the /proc/devices file |
| * b) the device node given to scsi_pt_open() is a char device |
| * c) the char major number of the device node given to scsi_pt_open() |
| * matches the char major number of the bsg entry in /proc/devices |
| * Otherwise the sg v3 interface is used. |
| * |
| * Note that in either case we prepare the data in a sg v4 structure. If |
| * the runtime tests indicate that the v3 interface is needed then |
| * do_scsi_pt_v3() transfers the input data into a v3 structure and |
| * then the output data is transferred back into a sg v4 structure. |
| * That implementation detail could change in the future. |
| * |
| * [20120806] Only use MAJOR() macro in kdev_t.h if that header file is |
| * available and major() macro [N.B. lower case] is not available. |
| */ |
| |
| |
| #include <linux/types.h> |
| #include <linux/bsg.h> |
| |
| #ifdef major |
| #define SG_DEV_MAJOR major |
| #else |
| #ifdef HAVE_LINUX_KDEV_T_H |
| #include <linux/kdev_t.h> |
| #endif |
| #define SG_DEV_MAJOR MAJOR /* MAJOR() macro faulty if > 255 minors */ |
| #endif |
| |
| |
| struct sg_pt_linux_scsi { |
| struct sg_io_v4 io_hdr; /* use v4 header as it is more general */ |
| int in_err; |
| int os_err; |
| unsigned char tmf_request[4]; |
| }; |
| |
| struct sg_pt_base { |
| struct sg_pt_linux_scsi impl; |
| }; |
| |
| static int bsg_major_checked = 0; |
| static int bsg_major = 0; |
| |
| |
| |
| static void |
| find_bsg_major(int verbose) |
| { |
| const char * proc_devices = "/proc/devices"; |
| FILE *fp; |
| char a[128]; |
| char b[128]; |
| char * cp; |
| int n; |
| |
| if (NULL == (fp = fopen(proc_devices, "r"))) { |
| if (verbose) |
| pr2ws("fopen %s failed: %s\n", proc_devices, strerror(errno)); |
| return; |
| } |
| while ((cp = fgets(b, sizeof(b), fp))) { |
| if ((1 == sscanf(b, "%126s", a)) && |
| (0 == memcmp(a, "Character", 9))) |
| break; |
| } |
| while (cp && (cp = fgets(b, sizeof(b), fp))) { |
| if (2 == sscanf(b, "%d %126s", &n, a)) { |
| if (0 == strcmp("bsg", a)) { |
| bsg_major = n; |
| break; |
| } |
| } else |
| break; |
| } |
| if (verbose > 3) { |
| if (cp) |
| pr2ws("found bsg_major=%d\n", bsg_major); |
| else |
| pr2ws("found no bsg char device in %s\n", proc_devices); |
| } |
| fclose(fp); |
| } |
| |
| |
| /* Returns >= 0 if successful. If error in Unix returns negated errno. */ |
| int |
| scsi_pt_open_device(const char * device_name, int read_only, int verbose) |
| { |
| int oflags = O_NONBLOCK; |
| |
| oflags |= (read_only ? O_RDONLY : O_RDWR); |
| return scsi_pt_open_flags(device_name, oflags, verbose); |
| } |
| |
| /* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed */ |
| /* together. The 'flags' argument is advisory and may be ignored. */ |
| /* Returns >= 0 if successful, otherwise returns negated errno. */ |
| int |
| scsi_pt_open_flags(const char * device_name, int flags, int verbose) |
| { |
| int fd; |
| |
| if (! bsg_major_checked) { |
| bsg_major_checked = 1; |
| find_bsg_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; |
| 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; |
| } |
| |
| |
| struct sg_pt_base * |
| construct_scsi_pt_obj() |
| { |
| struct sg_pt_linux_scsi * ptp; |
| |
| ptp = (struct sg_pt_linux_scsi *) |
| calloc(1, sizeof(struct sg_pt_linux_scsi)); |
| if (ptp) { |
| 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 |
| } |
| return (struct sg_pt_base *)ptp; |
| } |
| |
| void |
| destruct_scsi_pt_obj(struct sg_pt_base * vp) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp) |
| free(ptp); |
| } |
| |
| void |
| clear_scsi_pt_obj(struct sg_pt_base * vp) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp) { |
| 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 |
| } |
| } |
| |
| 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; |
| /* C99 has intptr_t instead of long */ |
| ptp->io_hdr.request = (__u64)(long)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)(long)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_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.din_xferp) |
| ++ptp->in_err; |
| if (dxfer_len > 0) { |
| ptp->io_hdr.din_xferp = (__u64)(long)dxferp; |
| ptp->io_hdr.din_xfer_len = dxfer_len; |
| } |
| } |
| |
| /* Setup for data transfer toward device */ |
| void |
| set_scsi_pt_data_out(struct sg_pt_base * vp, const unsigned char * dxferp, |
| int dxfer_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.dout_xferp) |
| ++ptp->in_err; |
| if (dxfer_len > 0) { |
| ptp->io_hdr.dout_xferp = (__u64)(long)dxferp; |
| ptp->io_hdr.dout_xfer_len = dxfer_len; |
| } |
| } |
| |
| 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)(long)(&(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; |
| |
| return 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; |
| |
| return ptp->io_hdr.device_status; |
| } |
| |
| int |
| get_scsi_pt_sense_len(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.response_len; |
| } |
| |
| int |
| get_scsi_pt_duration_ms(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.duration; |
| } |
| |
| int |
| get_scsi_pt_transport_err(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.transport_status; |
| } |
| |
| /* Returns b which will contain a null char terminated string (if |
| * max_b_len > 0). Combined driver and transport (called "host" in Linux |
| * kernel) statuses */ |
| char * |
| get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len, |
| char * b) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| int ds = ptp->io_hdr.driver_status; |
| int hs = ptp->io_hdr.transport_status; |
| int n, m; |
| char * cp = b; |
| int driv; |
| const char * driv_cp = "invalid"; |
| |
| if (max_b_len < 1) |
| return b; |
| m = max_b_len; |
| n = 0; |
| if (hs) { |
| if ((hs < 0) || (hs >= LINUX_HOST_BYTES_SZ)) |
| n = snprintf(cp, m, "Host_status=0x%02x is invalid\n", hs); |
| else |
| n = snprintf(cp, m, "Host_status=0x%02x [%s]\n", hs, |
| linux_host_bytes[hs]); |
| } |
| m -= n; |
| if (m < 1) { |
| b[max_b_len - 1] = '\0'; |
| return b; |
| } |
| cp += n; |
| driv = ds & SG_LIB_DRIVER_MASK; |
| if (driv < LINUX_DRIVER_BYTES_SZ) |
| driv_cp = linux_driver_bytes[driv]; |
| #if 0 |
| sugg = (ds & SG_LIB_SUGGEST_MASK) >> 4; |
| if (sugg < LINUX_DRIVER_SUGGESTS_SZ) |
| sugg_cp = linux_driver_suggests[sugg]; |
| #endif |
| n = snprintf(cp, m, "Driver_status=0x%02x [%s]\n", ds, driv_cp); |
| m -= n; |
| if (m < 1) |
| b[max_b_len - 1] = '\0'; |
| return b; |
| } |
| |
| int |
| get_scsi_pt_result_category(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| int dr_st = ptp->io_hdr.driver_status & SG_LIB_DRIVER_MASK; |
| int scsi_st = ptp->io_hdr.device_status & 0x7e; |
| |
| if (ptp->os_err) |
| return SCSI_PT_RESULT_OS_ERR; |
| else if (ptp->io_hdr.transport_status) |
| return SCSI_PT_RESULT_TRANSPORT_ERR; |
| else if (dr_st && (SG_LIB_DRIVER_SENSE != dr_st)) |
| return SCSI_PT_RESULT_TRANSPORT_ERR; |
| else if ((SG_LIB_DRIVER_SENSE == dr_st) || |
| (SAM_STAT_CHECK_CONDITION == scsi_st) || |
| (SAM_STAT_COMMAND_TERMINATED == scsi_st)) |
| return SCSI_PT_RESULT_SENSE; |
| else if (scsi_st) |
| return SCSI_PT_RESULT_STATUS; |
| else |
| return SCSI_PT_RESULT_GOOD; |
| } |
| |
| int |
| get_scsi_pt_os_err(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->os_err; |
| } |
| |
| char * |
| get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| const char * cp; |
| |
| cp = safe_strerror(ptp->os_err); |
| strncpy(b, cp, max_b_len); |
| if ((int)strlen(cp) >= max_b_len) |
| b[max_b_len - 1] = '\0'; |
| return b; |
| } |
| |
| /* 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", |
| 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). |
| * Clears os_err field prior to active call (whose result may set it |
| * again). */ |
| int |
| do_scsi_pt(struct sg_pt_base * vp, int fd, int time_secs, int verbose) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (! bsg_major_checked) { |
| bsg_major_checked = 1; |
| find_bsg_major(verbose); |
| } |
| ptp->os_err = 0; |
| if (ptp->in_err) { |
| if (verbose) |
| pr2ws("Replicated or unused set_scsi_pt... functions\n"); |
| return SCSI_PT_DO_BAD_PARAMS; |
| } |
| if (bsg_major <= 0) |
| return do_scsi_pt_v3(ptp, fd, time_secs, verbose); |
| else { |
| struct stat a_stat; |
| |
| if (fstat(fd, &a_stat) < 0) { |
| ptp->os_err = errno; |
| if (verbose > 1) |
| pr2ws("fstat() failed: %s (errno=%d)\n", |
| strerror(ptp->os_err), ptp->os_err); |
| return -ptp->os_err; |
| } |
| if (! S_ISCHR(a_stat.st_mode) || |
| (bsg_major != (int)SG_DEV_MAJOR(a_stat.st_rdev))) |
| 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", |
| strerror(ptp->os_err), ptp->os_err); |
| return -ptp->os_err; |
| } |
| return 0; |
| } |
| |
| #endif |
| // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |