/* * (C) Copyright IBM Corp. 2004, 2005 All Rights Reserved. * * rtpg.c * * Tool to make use of a SCSI-feature called Asymmetric Logical Unit Access. * It determines the ALUA state of a device and prints a priority value to * stdout. * * Author(s): Jan Kunigk * S. Bader * * This file is released under the GPL. */ #include #include #include #include #include #include #include #include #define __user #include #include "../structs.h" #include "../prio.h" #include "../discovery.h" #include "../unaligned.h" #include "../debug.h" #include "alua_rtpg.h" #define SENSE_BUFF_LEN 32 #define SGIO_TIMEOUT 60000 #define PRINT_DEBUG(f, a...) \ condlog(4, "alua: " f, ##a) /* * Optionally print the commands sent and the data received a hex dump. */ #if DEBUG > 0 #if DEBUG_DUMPHEX > 0 #define PRINT_HEX(p, l) print_hex(p, l) void print_hex(unsigned char *p, unsigned long len) { int i; for(i = 0; i < len; i++) { if (i % 16 == 0) printf("%04x: ", i); printf("%02x%s", p[i], (((i + 1) % 16) == 0) ? "\n" : " "); } printf("\n"); } #else #define PRINT_HEX(p, l) #endif #else #define PRINT_HEX(p, l) #endif /* * Returns 0 if the SCSI command either was successful or if the an error was * recovered, otherwise 1. (definitions taken from sg_err.h) */ #define SCSI_CHECK_CONDITION 0x2 #define SCSI_COMMAND_TERMINATED 0x22 #define SG_ERR_DRIVER_SENSE 0x08 #define RECOVERED_ERROR 0x01 #define NOT_READY 0x2 #define UNIT_ATTENTION 0x6 enum scsi_disposition { SCSI_GOOD = 0, SCSI_ERROR, SCSI_RETRY, }; static int scsi_error(struct sg_io_hdr *hdr, int opcode) { int sense_key, asc, ascq; /* Treat SG_ERR here to get rid of sg_err.[ch] */ hdr->status &= 0x7e; if ( (hdr->status == 0) && (hdr->host_status == 0) && (hdr->driver_status == 0) ) { return SCSI_GOOD; } sense_key = asc = ascq = -1; if ( (hdr->status == SCSI_CHECK_CONDITION) || (hdr->status == SCSI_COMMAND_TERMINATED) || ((hdr->driver_status & 0xf) == SG_ERR_DRIVER_SENSE) ) { if (hdr->sbp && (hdr->sb_len_wr > 2)) { unsigned char * sense_buffer = hdr->sbp; if (sense_buffer[0] & 0x2) { sense_key = sense_buffer[1] & 0xf; if (hdr->sb_len_wr > 3) asc = sense_buffer[2]; if (hdr->sb_len_wr > 4) ascq = sense_buffer[3]; } else { sense_key = sense_buffer[2] & 0xf; if (hdr->sb_len_wr > 13) asc = sense_buffer[12]; if (hdr->sb_len_wr > 14) ascq = sense_buffer[13]; } if (sense_key == RECOVERED_ERROR) return SCSI_GOOD; } } PRINT_DEBUG("alua: SCSI error for command %02x: status %02x, sense %02x/%02x/%02x", opcode, hdr->status, sense_key, asc, ascq); if (sense_key == UNIT_ATTENTION || sense_key == NOT_READY) return SCSI_RETRY; else return SCSI_ERROR; } /* * Helper function to setup and run a SCSI inquiry command. */ static int do_inquiry_sg(int fd, int evpd, unsigned int codepage, void *resp, int resplen, unsigned int timeout) { struct inquiry_command cmd; struct sg_io_hdr hdr; unsigned char sense[SENSE_BUFF_LEN]; int rc, retry_count = 3; retry: memset(&cmd, 0, sizeof(cmd)); cmd.op = OPERATION_CODE_INQUIRY; if (evpd) { inquiry_command_set_evpd(&cmd); cmd.page = codepage; } put_unaligned_be16(resplen, cmd.length); PRINT_HEX((unsigned char *) &cmd, sizeof(cmd)); memset(&hdr, 0, sizeof(hdr)); hdr.interface_id = 'S'; hdr.cmdp = (unsigned char *) &cmd; hdr.cmd_len = sizeof(cmd); hdr.dxfer_direction = SG_DXFER_FROM_DEV; hdr.dxferp = resp; hdr.dxfer_len = resplen; hdr.sbp = sense; hdr.mx_sb_len = sizeof(sense); hdr.timeout = get_prio_timeout(timeout, SGIO_TIMEOUT); if (ioctl(fd, SG_IO, &hdr) < 0) { PRINT_DEBUG("do_inquiry: IOCTL failed!"); return -RTPG_INQUIRY_FAILED; } rc = scsi_error(&hdr, OPERATION_CODE_INQUIRY); if (rc == SCSI_ERROR) { PRINT_DEBUG("do_inquiry: SCSI error!"); return -RTPG_INQUIRY_FAILED; } else if (rc == SCSI_RETRY) { if (--retry_count >= 0) goto retry; PRINT_DEBUG("do_inquiry: retries exhausted!"); return -RTPG_INQUIRY_FAILED; } PRINT_HEX((unsigned char *) resp, resplen); return 0; } int do_inquiry(const struct path *pp, int evpd, unsigned int codepage, void *resp, int resplen, unsigned int timeout) { struct udev_device *ud; ud = udev_device_get_parent_with_subsystem_devtype(pp->udev, "scsi", "scsi_device"); if (ud != NULL) { int rc; if (!evpd) rc = sysfs_get_inquiry(ud, resp, resplen); else rc = sysfs_get_vpd(ud, codepage, resp, resplen); if (rc >= 0) { PRINT_HEX((unsigned char *) resp, resplen); return 0; } } return do_inquiry_sg(pp->fd, evpd, codepage, resp, resplen, timeout); } /* * This function returns the support for target port groups by evaluating the * data returned by the standard inquiry command. */ int get_target_port_group_support(const struct path *pp, unsigned int timeout) { struct inquiry_data inq; int rc; memset((unsigned char *)&inq, 0, sizeof(inq)); rc = do_inquiry(pp, 0, 0x00, &inq, sizeof(inq), timeout); if (!rc) { rc = inquiry_data_get_tpgs(&inq); } return rc; } static int get_sysfs_pg83(const struct path *pp, unsigned char *buff, int buflen) { struct udev_device *parent = pp->udev; while (parent) { const char *subsys = udev_device_get_subsystem(parent); if (subsys && !strncmp(subsys, "scsi", 4)) break; parent = udev_device_get_parent(parent); } if (!parent || sysfs_get_vpd(parent, 0x83, buff, buflen) <= 0) { PRINT_DEBUG("failed to read sysfs vpd pg83"); return -1; } return 0; } int get_target_port_group(const struct path * pp, unsigned int timeout) { unsigned char *buf; struct vpd83_data * vpd83; struct vpd83_dscr * dscr; int rc; int buflen, scsi_buflen; buflen = 4096; buf = (unsigned char *)malloc(buflen); if (!buf) { PRINT_DEBUG("malloc failed: could not allocate" "%u bytes", buflen); return -RTPG_RTPG_FAILED; } memset(buf, 0, buflen); rc = get_sysfs_pg83(pp, buf, buflen); if (rc < 0) { rc = do_inquiry(pp, 1, 0x83, buf, buflen, timeout); if (rc < 0) goto out; scsi_buflen = get_unaligned_be16(&buf[2]) + 4; /* Paranoia */ if (scsi_buflen >= USHRT_MAX) scsi_buflen = USHRT_MAX; if (buflen < scsi_buflen) { free(buf); buf = (unsigned char *)malloc(scsi_buflen); if (!buf) { PRINT_DEBUG("malloc failed: could not allocate" "%u bytes", scsi_buflen); return -RTPG_RTPG_FAILED; } buflen = scsi_buflen; memset(buf, 0, buflen); rc = do_inquiry(pp, 1, 0x83, buf, buflen, timeout); if (rc < 0) goto out; } } vpd83 = (struct vpd83_data *) buf; rc = -RTPG_NO_TPG_IDENTIFIER; FOR_EACH_VPD83_DSCR(vpd83, dscr) { if (vpd83_dscr_istype(dscr, IDTYPE_TARGET_PORT_GROUP)) { struct vpd83_tpg_dscr *p; if (rc != -RTPG_NO_TPG_IDENTIFIER) { PRINT_DEBUG("get_target_port_group: more " "than one TPG identifier found!"); continue; } p = (struct vpd83_tpg_dscr *)dscr->data; rc = get_unaligned_be16(p->tpg); } } if (rc == -RTPG_NO_TPG_IDENTIFIER) { PRINT_DEBUG("get_target_port_group: " "no TPG identifier found!"); } out: free(buf); return rc; } int do_rtpg(int fd, void* resp, long resplen, unsigned int timeout) { struct rtpg_command cmd; struct sg_io_hdr hdr; unsigned char sense[SENSE_BUFF_LEN]; int retry_count = 3, rc; retry: memset(&cmd, 0, sizeof(cmd)); cmd.op = OPERATION_CODE_RTPG; rtpg_command_set_service_action(&cmd); put_unaligned_be32(resplen, cmd.length); PRINT_HEX((unsigned char *) &cmd, sizeof(cmd)); memset(&hdr, 0, sizeof(hdr)); hdr.interface_id = 'S'; hdr.cmdp = (unsigned char *) &cmd; hdr.cmd_len = sizeof(cmd); hdr.dxfer_direction = SG_DXFER_FROM_DEV; hdr.dxferp = resp; hdr.dxfer_len = resplen; hdr.mx_sb_len = sizeof(sense); hdr.sbp = sense; hdr.timeout = get_prio_timeout(timeout, SGIO_TIMEOUT); if (ioctl(fd, SG_IO, &hdr) < 0) { condlog(2, "%s: sg ioctl failed: %s", __func__, strerror(errno)); return -RTPG_RTPG_FAILED; } rc = scsi_error(&hdr, OPERATION_CODE_RTPG); if (rc == SCSI_ERROR) { PRINT_DEBUG("do_rtpg: SCSI error!"); return -RTPG_RTPG_FAILED; } else if (rc == SCSI_RETRY) { if (--retry_count >= 0) goto retry; PRINT_DEBUG("do_rtpg: retries exhausted!"); return -RTPG_RTPG_FAILED; } PRINT_HEX(resp, resplen); return 0; } int get_asymmetric_access_state(const struct path *pp, unsigned int tpg, unsigned int timeout) { unsigned char *buf; struct rtpg_data * tpgd; struct rtpg_tpg_dscr * dscr; int rc; unsigned int buflen; uint64_t scsi_buflen; int fd = pp->fd; buflen = 4096; buf = (unsigned char *)malloc(buflen); if (!buf) { PRINT_DEBUG ("malloc failed: could not allocate" "%u bytes", buflen); return -RTPG_RTPG_FAILED; } memset(buf, 0, buflen); rc = do_rtpg(fd, buf, buflen, timeout); if (rc < 0) { PRINT_DEBUG("%s: do_rtpg returned %d", __func__, rc); goto out; } scsi_buflen = get_unaligned_be32(&buf[0]) + 4; if (scsi_buflen > UINT_MAX) scsi_buflen = UINT_MAX; if (buflen < scsi_buflen) { free(buf); buf = (unsigned char *)malloc(scsi_buflen); if (!buf) { PRINT_DEBUG("malloc failed: could not allocate %" PRIu64 " bytes", scsi_buflen); return -RTPG_RTPG_FAILED; } buflen = scsi_buflen; memset(buf, 0, buflen); rc = do_rtpg(fd, buf, buflen, timeout); if (rc < 0) goto out; } tpgd = (struct rtpg_data *) buf; rc = -RTPG_TPG_NOT_FOUND; RTPG_FOR_EACH_PORT_GROUP(tpgd, dscr) { if (get_unaligned_be16(dscr->tpg) == tpg) { if (rc != -RTPG_TPG_NOT_FOUND) { PRINT_DEBUG("get_asymmetric_access_state: " "more than one entry with same port " "group."); } else { condlog(5, "pref=%i", dscr->b0); rc = rtpg_tpg_dscr_get_aas(dscr); } } } if (rc == -RTPG_TPG_NOT_FOUND) condlog(2, "%s: port group %d not found", __func__, tpg); out: free(buf); return rc; }