/* * Copyright (c) 2005 Christophe Varoqui */ #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "debug.h" #include "../libmultipath/sg_include.h" #define INQUIRY_CMDLEN 6 #define INQUIRY_CMD 0x12 #define MODE_SENSE_CMD 0x5a #define MODE_SELECT_CMD 0x55 #define MODE_SEN_SEL_CMDLEN 10 #define SENSE_BUFF_LEN 32 #define SCSI_CHECK_CONDITION 0x2 #define SCSI_COMMAND_TERMINATED 0x22 #define SG_ERR_DRIVER_SENSE 0x08 #define RECOVERED_ERROR 0x01 #define ILLEGAL_REQUEST 0x05 #define CURRENT_PAGE_CODE_VALUES 0 #define CHANGEABLE_PAGE_CODE_VALUES 1 #define MSG_RDAC_DOWN " reports path is down" #define MSG_RDAC_DOWN_TYPE(STR) MSG_RDAC_DOWN": "STR #define RTPG_UNAVAILABLE 0x3 #define RTPG_OFFLINE 0xE #define RTPG_TRANSITIONING 0xF #define RTPG_UNAVAIL_NON_RESPONSIVE 0x2 #define RTPG_UNAVAIL_IN_RESET 0x3 #define RTPG_UNAVAIL_CFW_DL1 0x4 #define RTPG_UNAVAIL_CFW_DL2 0x5 #define RTPG_UNAVAIL_QUIESCED 0x6 #define RTPG_UNAVAIL_SERVICE_MODE 0x7 struct control_mode_page { unsigned char header[8]; unsigned char page_code; unsigned char page_len; unsigned char dontcare0[3]; unsigned char tas_bit; unsigned char dontcare1[6]; }; struct rdac_checker_context { void * dummy; }; int libcheck_init (struct checker * c) { unsigned char cmd[MODE_SEN_SEL_CMDLEN]; unsigned char sense_b[SENSE_BUFF_LEN]; struct sg_io_hdr io_hdr; struct control_mode_page current, changeable; int set = 0; memset(cmd, 0, MODE_SEN_SEL_CMDLEN); cmd[0] = MODE_SENSE_CMD; cmd[1] = 0x08; /* DBD bit on */ cmd[2] = 0xA + (CURRENT_PAGE_CODE_VALUES << 6); cmd[8] = (sizeof(struct control_mode_page) & 0xff); memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); memset(sense_b, 0, SENSE_BUFF_LEN); memset(¤t, 0, sizeof(struct control_mode_page)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = MODE_SEN_SEL_CMDLEN; io_hdr.mx_sb_len = sizeof(sense_b); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = (sizeof(struct control_mode_page) & 0xff); io_hdr.dxferp = ¤t; io_hdr.cmdp = cmd; io_hdr.sbp = sense_b; io_hdr.timeout = c->timeout * 1000; if (ioctl(c->fd, SG_IO, &io_hdr) < 0) goto out; /* check the TAS bit to see if it is already set */ if ((current.tas_bit >> 6) & 0x1) { set = 1; goto out; } /* get the changeble values */ cmd[2] = 0xA + (CHANGEABLE_PAGE_CODE_VALUES << 6); io_hdr.dxferp = &changeable; memset(&changeable, 0, sizeof(struct control_mode_page)); if (ioctl(c->fd, SG_IO, &io_hdr) < 0) goto out; /* if TAS bit is not settable exit */ if (((changeable.tas_bit >> 6) & 0x1) == 0) goto out; /* Now go ahead and set it */ memset(cmd, 0, MODE_SEN_SEL_CMDLEN); cmd[0] = MODE_SELECT_CMD; cmd[1] = 0x1; /* set SP bit on */ cmd[8] = (sizeof(struct control_mode_page) & 0xff); /* use the same buffer as current, only set the tas bit */ current.page_code = 0xA; current.page_len = 0xA; current.tas_bit |= (1 << 6); io_hdr.dxfer_direction = SG_DXFER_TO_DEV; io_hdr.dxferp = ¤t; if (ioctl(c->fd, SG_IO, &io_hdr) < 0) goto out; /* Success */ set = 1; out: if (set == 0) condlog(3, "rdac checker failed to set TAS bit"); return 0; } void libcheck_free(__attribute__((unused)) struct checker *c) { return; } static int do_inq(int sg_fd, unsigned int pg_op, void *resp, int mx_resp_len, unsigned int timeout) { unsigned char inqCmdBlk[INQUIRY_CMDLEN] = { INQUIRY_CMD, 1, 0, 0, 0, 0 }; unsigned char sense_b[SENSE_BUFF_LEN]; struct sg_io_hdr io_hdr; int retry_rdac = 5; retry: inqCmdBlk[2] = (unsigned char) pg_op; inqCmdBlk[4] = (unsigned char) (mx_resp_len & 0xff); memset(&io_hdr, 0, sizeof (struct sg_io_hdr)); memset(sense_b, 0, SENSE_BUFF_LEN); io_hdr.interface_id = 'S'; io_hdr.cmd_len = sizeof (inqCmdBlk); io_hdr.mx_sb_len = sizeof (sense_b); io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = mx_resp_len; io_hdr.dxferp = resp; io_hdr.cmdp = inqCmdBlk; io_hdr.sbp = sense_b; io_hdr.timeout = timeout * 1000; if (ioctl(sg_fd, SG_IO, &io_hdr) < 0 && errno == ENOTTY) return PATH_WILD; /* treat SG_ERR here to get rid of sg_err.[ch] */ io_hdr.status &= 0x7e; if ((0 == io_hdr.status) && (0 == io_hdr.host_status) && (0 == io_hdr.driver_status)) return PATH_UP; /* check if we need to retry this error */ if (io_hdr.info & SG_INFO_OK_MASK) { switch (io_hdr.host_status) { case DID_BUS_BUSY: case DID_ERROR: case DID_SOFT_ERROR: case DID_TRANSPORT_DISRUPTED: /* Transport error, retry */ if (--retry_rdac) goto retry; break; default: break; } } if ((SCSI_CHECK_CONDITION == io_hdr.status) || (SCSI_COMMAND_TERMINATED == io_hdr.status) || (SG_ERR_DRIVER_SENSE == (0xf & io_hdr.driver_status))) { if (io_hdr.sbp && (io_hdr.sb_len_wr > 2)) { int sense_key; unsigned char * sense_buffer = io_hdr.sbp; if (sense_buffer[0] & 0x2) sense_key = sense_buffer[1] & 0xf; else sense_key = sense_buffer[2] & 0xf; if (RECOVERED_ERROR == sense_key) return PATH_UP; else if (ILLEGAL_REQUEST == sense_key) return PATH_WILD; condlog(1, "rdac checker: INQUIRY failed with sense key %02x", sense_key); } } return PATH_DOWN; } struct volume_access_inq { char PQ_PDT; char dontcare0[7]; char avtcvp; char vol_ppp; char aas_cur; char vendor_specific_cur; char aas_alt; char vendor_specific_alt; char dontcare1[34]; }; enum { RDAC_MSGID_NOT_CONN = CHECKER_FIRST_MSGID, RDAC_MSGID_IN_STARTUP, RDAC_MSGID_NON_RESPONSIVE, RDAC_MSGID_IN_RESET, RDAC_MSGID_FW_DOWNLOADING, RDAC_MSGID_QUIESCED, RDAC_MSGID_SERVICE_MODE, RDAC_MSGID_UNAVAILABLE, RDAC_MSGID_INQUIRY_FAILED, }; #define _IDX(x) (RDAC_MSGID_##x - CHECKER_FIRST_MSGID) const char *libcheck_msgtable[] = { [_IDX(NOT_CONN)] = MSG_RDAC_DOWN_TYPE("lun not connected"), [_IDX(IN_STARTUP)] = MSG_RDAC_DOWN_TYPE("ctlr is in startup sequence"), [_IDX(NON_RESPONSIVE)] = MSG_RDAC_DOWN_TYPE("non-responsive to queries"), [_IDX(IN_RESET)] = MSG_RDAC_DOWN_TYPE("ctlr held in reset"), [_IDX(FW_DOWNLOADING)] = MSG_RDAC_DOWN_TYPE("ctlr firmware downloading"), [_IDX(QUIESCED)] = MSG_RDAC_DOWN_TYPE("ctlr quiesced by admin request"), [_IDX(SERVICE_MODE)] = MSG_RDAC_DOWN_TYPE("ctlr is in service mode"), [_IDX(UNAVAILABLE)] = MSG_RDAC_DOWN_TYPE("ctlr is unavailable"), [_IDX(INQUIRY_FAILED)] = MSG_RDAC_DOWN_TYPE("inquiry failed"), NULL, }; static int checker_msg_string(const struct volume_access_inq *inq) { /* lun not connected */ if (((inq->PQ_PDT & 0xE0) == 0x20) || (inq->PQ_PDT & 0x7f)) return RDAC_MSGID_NOT_CONN; /* if no tpg data is available, give the generic path down message */ if (!(inq->avtcvp & 0x10)) return CHECKER_MSGID_DOWN; /* controller is booting up */ if (((inq->aas_cur & 0x0F) == RTPG_TRANSITIONING) && (inq->aas_alt & 0x0F) != RTPG_TRANSITIONING) return RDAC_MSGID_IN_STARTUP; /* if not unavailable, give generic message */ if ((inq->aas_cur & 0x0F) != RTPG_UNAVAILABLE) return CHECKER_MSGID_DOWN; /* target port group unavailable */ switch (inq->vendor_specific_cur) { case RTPG_UNAVAIL_NON_RESPONSIVE: return RDAC_MSGID_NON_RESPONSIVE; case RTPG_UNAVAIL_IN_RESET: return RDAC_MSGID_IN_RESET; case RTPG_UNAVAIL_CFW_DL1: case RTPG_UNAVAIL_CFW_DL2: return RDAC_MSGID_FW_DOWNLOADING; case RTPG_UNAVAIL_QUIESCED: return RDAC_MSGID_QUIESCED; case RTPG_UNAVAIL_SERVICE_MODE: return RDAC_MSGID_SERVICE_MODE; default: return RDAC_MSGID_UNAVAILABLE; } } int libcheck_check(struct checker * c) { struct volume_access_inq inq; int ret, inqfail; inqfail = 0; memset(&inq, 0, sizeof(struct volume_access_inq)); ret = do_inq(c->fd, 0xC9, &inq, sizeof(struct volume_access_inq), c->timeout); if (ret != PATH_UP) { inqfail = 1; goto done; } if (((inq.PQ_PDT & 0xE0) == 0x20) || (inq.PQ_PDT & 0x7f)) { /* LUN not connected*/ ret = PATH_DOWN; goto done; } /* If TPGDE bit set, evaluate TPG information */ if ((inq.avtcvp & 0x10)) { switch (inq.aas_cur & 0x0F) { /* Never use the path if it reports unavailable */ case RTPG_UNAVAILABLE: ret = PATH_DOWN; goto done; /* * If both controllers report transitioning, it * means mode select or STPG is being processed. * * If this controller alone is transitioning, it's * booting and we shouldn't use it yet. */ case RTPG_TRANSITIONING: if ((inq.aas_alt & 0xF) != RTPG_TRANSITIONING) { ret = PATH_DOWN; goto done; } break; } } /* If owner set or ioship mode is enabled return PATH_UP always */ if ((inq.avtcvp & 0x1) || ((inq.avtcvp >> 5) & 0x1)) ret = PATH_UP; else ret = PATH_GHOST; done: switch (ret) { case PATH_WILD: c->msgid = CHECKER_MSGID_UNSUPPORTED; break; case PATH_DOWN: c->msgid = (inqfail ? RDAC_MSGID_INQUIRY_FAILED : checker_msg_string(&inq)); break; case PATH_UP: c->msgid = CHECKER_MSGID_UP; break; case PATH_GHOST: c->msgid = CHECKER_MSGID_GHOST; break; } return ret; }