/* * Copyright (c) 2004-2022 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. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #define __STDC_FORMAT_MACROS 1 #include #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_pt.h" #include "sg_pr2serr.h" /* * This program issues SCSI SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS * commands tailored for SES (enclosure) devices. */ static const char * version_str = "2.58 20220813"; /* ses4r04 */ #define MX_ALLOC_LEN ((64 * 1024) - 4) /* max allowable for big enclosures */ #define MX_ELEM_HDR 1024 #define REQUEST_SENSE_RESP_SZ 252 #define DATA_IN_OFF 4 #define MIN_MAXLEN 16 #define MIN_DATA_IN_SZ 8192 /* use max(MIN_DATA_IN_SZ, op->maxlen) for * the size of data_arr */ #define MX_DATA_IN_LINES (16 * 1024) #define MX_JOIN_ROWS 520 /* element index fields in dpages are only 8 * bit, and index 0xff (255) is sometimes used * for 'not applicable'. However this limit * can bypassed with sub-enclosure numbers. * So try higher figure. */ #define MX_DATA_IN_DESCS 32 #define NUM_ACTIVE_ET_AESP_ARR 32 #define TEMPERAT_OFF 20 /* 8 bits represents -19 C to +235 C */ /* value of 0 (would imply -20 C) reserved */ /* Send Diagnostic and Receive Diagnostic Results page codes */ /* Sometimes referred to as "dpage"s in code comments */ #define SUPPORTED_DPC 0x0 #define CONFIGURATION_DPC 0x1 #define ENC_CONTROL_DPC 0x2 #define ENC_STATUS_DPC 0x2 #define HELP_TEXT_DPC 0x3 #define STRING_DPC 0x4 #define THRESHOLD_DPC 0x5 #define ARRAY_CONTROL_DPC 0x6 /* obsolete, last seen ses-r08b.pdf */ #define ARRAY_STATUS_DPC 0x6 /* obsolete */ #define ELEM_DESC_DPC 0x7 #define SHORT_ENC_STATUS_DPC 0x8 #define ENC_BUSY_DPC 0x9 #define ADD_ELEM_STATUS_DPC 0xa /* Additional Element Status dpage code */ #define SUBENC_HELP_TEXT_DPC 0xb #define SUBENC_STRING_DPC 0xc #define SUPPORTED_SES_DPC 0xd /* should be 0x1 <= dpc <= 0x2f */ #define DOWNLOAD_MICROCODE_DPC 0xe #define SUBENC_NICKNAME_DPC 0xf #define ALL_DPC 0xff /* Element Type codes */ #define UNSPECIFIED_ETC 0x0 #define DEVICE_ETC 0x1 #define POWER_SUPPLY_ETC 0x2 #define COOLING_ETC 0x3 #define TEMPERATURE_ETC 0x4 #define DOOR_ETC 0x5 /* prior to ses3r05 was DOOR_LOCK_ETC */ #define AUD_ALARM_ETC 0x6 #define ENC_SCELECTR_ETC 0x7 /* Enclosure services controller electronics */ #define SCC_CELECTR_ETC 0x8 /* SCC: SCSI Controller Commands (e.g. RAID * controller). SCC Controller Elecronics */ #define NV_CACHE_ETC 0x9 #define INV_OP_REASON_ETC 0xa #define UI_POWER_SUPPLY_ETC 0xb #define DISPLAY_ETC 0xc #define KEY_PAD_ETC 0xd #define ENCLOSURE_ETC 0xe #define SCSI_PORT_TRAN_ETC 0xf #define LANGUAGE_ETC 0x10 #define COMM_PORT_ETC 0x11 #define VOLT_SENSOR_ETC 0x12 #define CURR_SENSOR_ETC 0x13 #define SCSI_TPORT_ETC 0x14 #define SCSI_IPORT_ETC 0x15 #define SIMPLE_SUBENC_ETC 0x16 #define ARRAY_DEV_ETC 0x17 #define SAS_EXPANDER_ETC 0x18 #define SAS_CONNECTOR_ETC 0x19 #define LAST_ETC SAS_CONNECTOR_ETC /* adjust as necessary */ #define TPROTO_PCIE_PS_NVME 1 /* NVMe regarded as subset of PCIe */ #define NUM_ETC (LAST_ETC + 1) #define DEF_CLEAR_VAL 0 #define DEF_SET_VAL 1 struct element_type_t { int elem_type_code; const char * abbrev; const char * desc; }; #define CGS_CL_ARR_MAX_SZ 8 #define CGS_STR_MAX_SZ 80 enum cgs_select_t {CLEAR_OPT, GET_OPT, SET_OPT}; struct cgs_cl_t { enum cgs_select_t cgs_sel; bool last_cs; /* true only for last --clear= or --set= */ char cgs_str[CGS_STR_MAX_SZ]; }; struct opts_t { bool byte1_given; /* true if -b B1 or --byte1=B1 given */ bool do_control; /* want to write to DEVICE */ bool do_data; /* flag if --data= option has been used */ bool do_list; bool do_status; /* want to read from DEVICE (or user data) */ bool eiioe_auto; /* Element Index Includes Overall (status) Element */ bool eiioe_force; bool ind_given; /* '--index=...' or '-I ...' */ bool inner_hex; bool many_dpages; /* user supplied data has more than one dpage */ bool mask_ign; /* element read-mask-modify-write actions */ bool o_readonly; bool page_code_given; /* or suitable abbreviation */ bool quiet; /* exit status unaltered by --quiet */ bool seid_given; bool verbose_given; bool version_given; bool warn; int byte1; /* (origin 0 so second byte) in Control dpage */ int dev_slot_num; int do_filter; int do_help; int do_hex; int do_join; /* relational join of Enclosure status, Element descriptor and Additional element status dpages. Use twice to add Threshold in dpage to join. */ int do_raw; int enumerate; int ind_th; /* type header index, set by build_type_desc_hdr_arr() */ int ind_indiv; /* individual element index; -1 for overall */ int ind_indiv_last; /* if > ind_indiv then [ind_indiv..ind_indiv_last] */ int ind_et_inst; /* ETs can have multiple type header instances */ int maxlen; int seid; int page_code; /* recognised abbreviations converted to dpage num */ int verbose; int num_cgs; /* number of --clear-, --get= and --set= options */ int mx_arr_len; /* allocated size of data_arr */ int arr_len; /* valid bytes in data_arr */ uint8_t * data_arr; uint8_t * free_data_arr; const char * desc_name; const char * dev_name; const struct element_type_t * ind_etp; const char * index_str; const char * nickname_str; struct cgs_cl_t cgs_cl_arr[CGS_CL_ARR_MAX_SZ]; uint8_t sas_addr[8]; /* Big endian byte sequence */ }; struct diag_page_code { int page_code; const char * desc; }; struct diag_page_abbrev { const char * abbrev; int page_code; }; /* The Configuration diagnostic page contains one or more of these. The * elements of the Enclosure Control/Status and Threshold In/ Out page follow * this format. The additional element status page is closely related to * this format (with some element types and all overall elements excluded). */ struct type_desc_hdr_t { uint8_t etype; /* element type code (0: unspecified) */ uint8_t num_elements; /* number of possible elements, excluding * overall element */ uint8_t se_id; /* subenclosure id (0 for primary enclosure) */ uint8_t txt_len; /* type descriptor text length; (unused) */ }; /* A SQL-like join of the Enclosure Status, Threshold In and Additional * Element Status pages based of the format indicated in the Configuration * page. Note that the array of these struct instances is built such that * the array index is equal to the 'ei_ioe' (element index that includes * overall elements). */ struct join_row_t { /* this struct is 72 bytes long on Intel "64" bit arch */ int th_i; /* type header index (origin 0) */ int indiv_i; /* individual (element) index, -1 for overall * instance, otherwise origin 0 */ uint8_t etype; /* element type */ uint8_t se_id; /* subenclosure id (0 for primary enclosure) */ int ei_eoe; /* element index referring to Enclosure status dpage * descriptors, origin 0 and excludes overall * elements, -1 for not applicable. As defined by * SES-2 standard for the AES descriptor, EIP=1 */ int ei_aess; /* subset of ei_eoe that only includes elements of * these types: excludes DEVICE_ETC, ARRAY_DEV_ETC, * SAS_EXPANDER_ETC, SCSI_IPORT_ETC, SCSI_TPORT_ETC * and ENC_SCELECTR_ETC. -1 for not applicable */ /* following point into Element Descriptor, Enclosure Status, Threshold * In and Additional element status diagnostic pages. enc_statp only * NULL beyond last, other pointers can be NULL . */ const uint8_t * elem_descp; uint8_t * enc_statp; /* NULL indicates past last */ uint8_t * thresh_inp; const uint8_t * ae_statp; int dev_slot_num; /* if not available, set to -1 */ uint8_t sas_addr[8]; /* big endian, if not available, set to 0 */ }; enum fj_select_t {FJ_IOE, FJ_EOE, FJ_AESS, FJ_SAS_CON}; /* Instance ('tes' in main() ) holds a type_desc_hdr_t array potentially with the matching join array if present. */ struct th_es_t { const struct type_desc_hdr_t * th_base; int num_ths; /* items in array pointed to by th_base */ struct join_row_t * j_base; int num_j_rows; int num_j_eoe; }; /* Representation of [=] or * :[:][=]. Associated with * --clear=, --get= or --set= option. */ struct tuple_acronym_val { const char * acron; const char * val_str; enum cgs_select_t cgs_sel; /* indicates --clear=, --get= or --set= */ int start_byte; /* -1 indicates no start_byte */ int start_bit; int num_bits; int64_t val; }; /* Mapping from to :: for a * given element type. Table of known acronyms made from these elements. */ struct acronym2tuple { const char * acron; /* element name or acronym, NULL for past end */ int etype; /* -1 for all element types */ int start_byte; /* origin 0, normally 0 to 3 */ int start_bit; /* 7 (MSbit or leftmost in SES drafts) to 0 (LSbit) */ int num_bits; /* usually 1, maximum is 64 */ const char * info; /* optional, set to NULL if not used */ }; /* Structure for holding (sub-)enclosure information found in the * Configuration diagnostic page. */ struct enclosure_info { int have_info; int rel_esp_id; /* relative enclosure services process id (origin 1) */ int num_esp; /* number of enclosure services processes */ uint8_t enc_log_id[8]; /* 8 byte NAA */ uint8_t enc_vendor_id[8]; /* may differ from INQUIRY response */ uint8_t product_id[16]; /* may differ from INQUIRY response */ uint8_t product_rev_level[4]; /* may differ from INQUIRY response */ }; /* When --status is given with --data= the file contents may contain more * than one dpage to be decoded. */ struct data_in_desc_t { bool in_use; int page_code; int offset; /* byte offset from op->data_arr + DATA_IN_OFF */ int dp_len; /* byte length of this diagnostic page */ }; /* Join array has four "element index"ing stategies: * [1] based on all descriptors in the Enclosure Status (ES) dpage * [2] based on the non-overall descriptors in the ES dpage * [3] based on the non-overall descriptors of these element types * in the ES dpage: DEVICE_ETC, ARRAY_DEV_ETC, SAS_EXPANDER_ETC, * SCSI_IPORT_ETC, SCSI_TPORT_ETC and ENC_SCELECTR_ETC. * [4] based on the non-overall descriptors of the SAS_CONNECTOR_ETC * element type * * The indexes are all origin 0 with the maximum index being one less then * the number of status descriptors in the ES dpage. Table of supported * permutations follows: * * ==========|=============================================================== * Algorithm | Indexes | Notes * |Element|Connector element|Other element| * ==========|=======|=================|=============|======================= * [A] | [2] | [4] | [3] | SES-2, OR * [A] | [2] | [4] | [3] | SES-3,EIIOE=0 * ----------|-------|-----------------|-------------|----------------------- * [B] | [1] | [1] | [1] | SES-3, EIIOE=1 * ----------|-------|-----------------|-------------|----------------------- * [C] | [2] | [2] | [2] | SES-3, EIIOE=2 * ----------|-------|-----------------|-------------|----------------------- * [D] | [2] | [1] | [1] | SES-3, EIIOE=3 * ----------|-------|-----------------|-------------|----------------------- * [E] | [1] | [4] | [3] | EIIOE=0 and * | | | | --eiioe=force, OR * [E] | [1] | [4] | [3] | {HP JBOD} EIIOE=0 and * | | | | --eiioe=auto and * | | | | AES[desc_0].ei==1 . * ----------|-------|-----------------|-------------|----------------------- * [F] | [2->3]| [4] | [3] | "broken_ei" when any * | | | | of AES[*].ei invalid * | | | | using strategy [2] * ----------|-------|-----------------|-------------|----------------------- * [Z] | - | [4] | [3] | EIP=0, implicit * | | | | element index of [3] * ========================================================================== * * */ static struct join_row_t join_arr[MX_JOIN_ROWS]; static struct join_row_t * join_arr_lastp = join_arr + MX_JOIN_ROWS - 1; static bool join_done = false; static struct type_desc_hdr_t type_desc_hdr_arr[MX_ELEM_HDR]; static int type_desc_hdr_count = 0; static uint8_t * config_dp_resp = NULL; static uint8_t * free_config_dp_resp = NULL; static int config_dp_resp_len; static struct data_in_desc_t data_in_desc_arr[MX_DATA_IN_DESCS]; /* Large buffers on heap, aligned to page size and zeroed */ static uint8_t * enc_stat_rsp; static uint8_t * elem_desc_rsp; static uint8_t * add_elem_rsp; static uint8_t * threshold_rsp; static unsigned enc_stat_rsp_sz; static unsigned elem_desc_rsp_sz; static unsigned add_elem_rsp_sz; static unsigned threshold_rsp_sz; static int enc_stat_rsp_len; static int elem_desc_rsp_len; static int add_elem_rsp_len; static int threshold_rsp_len; /* Diagnostic page names, control and/or status (in and/or out) */ static struct diag_page_code dpc_arr[] = { {SUPPORTED_DPC, "Supported Diagnostic Pages"}, /* 0 */ {CONFIGURATION_DPC, "Configuration (SES)"}, {ENC_STATUS_DPC, "Enclosure Status/Control (SES)"}, {HELP_TEXT_DPC, "Help Text (SES)"}, {STRING_DPC, "String In/Out (SES)"}, {THRESHOLD_DPC, "Threshold In/Out (SES)"}, {ARRAY_STATUS_DPC, "Array Status/Control (SES, obsolete)"}, {ELEM_DESC_DPC, "Element Descriptor (SES)"}, {SHORT_ENC_STATUS_DPC, "Short Enclosure Status (SES)"}, /* 8 */ {ENC_BUSY_DPC, "Enclosure Busy (SES-2)"}, {ADD_ELEM_STATUS_DPC, "Additional Element Status (SES-2)"}, {SUBENC_HELP_TEXT_DPC, "Subenclosure Help Text (SES-2)"}, {SUBENC_STRING_DPC, "Subenclosure String In/Out (SES-2)"}, {SUPPORTED_SES_DPC, "Supported SES Diagnostic Pages (SES-2)"}, {DOWNLOAD_MICROCODE_DPC, "Download Microcode (SES-2)"}, {SUBENC_NICKNAME_DPC, "Subenclosure Nickname (SES-2)"}, {0x3f, "Protocol Specific (SAS transport)"}, {0x40, "Translate Address (SBC)"}, {0x41, "Device Status (SBC)"}, {0x42, "Rebuild Assist (SBC)"}, /* sbc3r31 */ {ALL_DPC, "All SES diagnostic pages output (sg_ses)"}, {-1, NULL}, }; /* Diagnostic page names, for status (or in) pages */ static struct diag_page_code in_dpc_arr[] = { {SUPPORTED_DPC, "Supported Diagnostic Pages"}, /* 0 */ {CONFIGURATION_DPC, "Configuration (SES)"}, {ENC_STATUS_DPC, "Enclosure Status (SES)"}, {HELP_TEXT_DPC, "Help Text (SES)"}, {STRING_DPC, "String In (SES)"}, {THRESHOLD_DPC, "Threshold In (SES)"}, {ARRAY_STATUS_DPC, "Array Status (SES, obsolete)"}, {ELEM_DESC_DPC, "Element Descriptor (SES)"}, {SHORT_ENC_STATUS_DPC, "Short Enclosure Status (SES)"}, /* 8 */ {ENC_BUSY_DPC, "Enclosure Busy (SES-2)"}, {ADD_ELEM_STATUS_DPC, "Additional Element Status (SES-2)"}, {SUBENC_HELP_TEXT_DPC, "Subenclosure Help Text (SES-2)"}, {SUBENC_STRING_DPC, "Subenclosure String In (SES-2)"}, {SUPPORTED_SES_DPC, "Supported SES Diagnostic Pages (SES-2)"}, {DOWNLOAD_MICROCODE_DPC, "Download Microcode (SES-2)"}, {SUBENC_NICKNAME_DPC, "Subenclosure Nickname (SES-2)"}, {0x3f, "Protocol Specific (SAS transport)"}, {0x40, "Translate Address (SBC)"}, {0x41, "Device Status (SBC)"}, {0x42, "Rebuild Assist Input (SBC)"}, {-1, NULL}, }; /* Diagnostic page names, for control (or out) pages */ static struct diag_page_code out_dpc_arr[] = { {SUPPORTED_DPC, "?? [Supported Diagnostic Pages]"}, /* 0 */ {CONFIGURATION_DPC, "?? [Configuration (SES)]"}, {ENC_CONTROL_DPC, "Enclosure Control (SES)"}, {HELP_TEXT_DPC, "Help Text (SES)"}, {STRING_DPC, "String Out (SES)"}, {THRESHOLD_DPC, "Threshold Out (SES)"}, {ARRAY_CONTROL_DPC, "Array Control (SES, obsolete)"}, {ELEM_DESC_DPC, "?? [Element Descriptor (SES)]"}, {SHORT_ENC_STATUS_DPC, "?? [Short Enclosure Status (SES)]"}, /* 8 */ {ENC_BUSY_DPC, "?? [Enclosure Busy (SES-2)]"}, {ADD_ELEM_STATUS_DPC, "?? [Additional Element Status (SES-2)]"}, {SUBENC_HELP_TEXT_DPC, "?? [Subenclosure Help Text (SES-2)]"}, {SUBENC_STRING_DPC, "Subenclosure String Out (SES-2)"}, {SUPPORTED_SES_DPC, "?? [Supported SES Diagnostic Pages (SES-2)]"}, {DOWNLOAD_MICROCODE_DPC, "Download Microcode (SES-2)"}, {SUBENC_NICKNAME_DPC, "Subenclosure Nickname (SES-2)"}, {0x3f, "Protocol Specific (SAS transport)"}, {0x40, "Translate Address (SBC)"}, {0x41, "Device Status (SBC)"}, {0x42, "Rebuild Assist Output (SBC)"}, {-1, NULL}, }; static struct diag_page_abbrev dp_abbrev[] = { {"ac", ARRAY_CONTROL_DPC}, {"aes", ADD_ELEM_STATUS_DPC}, {"all", ALL_DPC}, {"as", ARRAY_STATUS_DPC}, {"cf", CONFIGURATION_DPC}, {"dm", DOWNLOAD_MICROCODE_DPC}, {"eb", ENC_BUSY_DPC}, {"ec", ENC_CONTROL_DPC}, {"ed", ELEM_DESC_DPC}, {"es", ENC_STATUS_DPC}, {"ht", HELP_TEXT_DPC}, {"sdp", SUPPORTED_DPC}, {"ses", SHORT_ENC_STATUS_DPC}, {"sht", SUBENC_HELP_TEXT_DPC}, {"snic", SUBENC_NICKNAME_DPC}, {"ssp", SUPPORTED_SES_DPC}, {"sstr", SUBENC_STRING_DPC}, {"str", STRING_DPC}, {"th", THRESHOLD_DPC}, {NULL, -999}, }; /* Names of element types used by the Enclosure Control/Status diagnostic * page. */ static struct element_type_t element_type_arr[] = { {UNSPECIFIED_ETC, "un", "Unspecified"}, {DEVICE_ETC, "dev", "Device slot"}, {POWER_SUPPLY_ETC, "ps", "Power supply"}, {COOLING_ETC, "coo", "Cooling"}, {TEMPERATURE_ETC, "ts", "Temperature sensor"}, {DOOR_ETC, "do", "Door"}, /* prior to ses3r05 was 'dl' (for Door Lock) but the "Lock" has been dropped */ {AUD_ALARM_ETC, "aa", "Audible alarm"}, {ENC_SCELECTR_ETC, "esc", "Enclosure services controller electronics"}, {SCC_CELECTR_ETC, "sce", "SCC controller electronics"}, {NV_CACHE_ETC, "nc", "Nonvolatile cache"}, {INV_OP_REASON_ETC, "ior", "Invalid operation reason"}, {UI_POWER_SUPPLY_ETC, "ups", "Uninterruptible power supply"}, {DISPLAY_ETC, "dis", "Display"}, {KEY_PAD_ETC, "kpe", "Key pad entry"}, {ENCLOSURE_ETC, "enc", "Enclosure"}, {SCSI_PORT_TRAN_ETC, "sp", "SCSI port/transceiver"}, {LANGUAGE_ETC, "lan", "Language"}, {COMM_PORT_ETC, "cp", "Communication port"}, {VOLT_SENSOR_ETC, "vs", "Voltage sensor"}, {CURR_SENSOR_ETC, "cs", "Current sensor"}, {SCSI_TPORT_ETC, "stp", "SCSI target port"}, {SCSI_IPORT_ETC, "sip", "SCSI initiator port"}, {SIMPLE_SUBENC_ETC, "ss", "Simple subenclosure"}, {ARRAY_DEV_ETC, "arr", "Array device slot"}, {SAS_EXPANDER_ETC, "sse", "SAS expander"}, {SAS_CONNECTOR_ETC, "ssc", "SAS connector"}, {-1, NULL, NULL}, }; static struct element_type_t element_type_by_code = {0, NULL, "element type code form"}; /* Many control element names below have "RQST" in front in drafts. These are for the Enclosure Control/Status diagnostic page */ static struct acronym2tuple ecs_a2t_arr[] = { /* acron element_type start_byte start_bit num_bits */ {"ac_fail", UI_POWER_SUPPLY_ETC, 2, 4, 1, NULL}, {"ac_hi", UI_POWER_SUPPLY_ETC, 2, 6, 1, NULL}, {"ac_lo", UI_POWER_SUPPLY_ETC, 2, 7, 1, NULL}, {"ac_qual", UI_POWER_SUPPLY_ETC, 2, 5, 1, NULL}, {"active", DEVICE_ETC, 2, 7, 1, NULL}, /* for control only */ {"active", ARRAY_DEV_ETC, 2, 7, 1, NULL}, /* for control only */ {"batt_fail", UI_POWER_SUPPLY_ETC, 3, 1, 1, NULL}, {"bpf", UI_POWER_SUPPLY_ETC, 3, 0, 1, NULL}, {"bypa", DEVICE_ETC, 3, 3, 1, "bypass port A"}, {"bypa", ARRAY_DEV_ETC, 3, 3, 1, "bypass port A"}, {"bypb", DEVICE_ETC, 3, 2, 1, "bypass port B"}, {"bypb", ARRAY_DEV_ETC, 3, 2, 1, "bypass port B"}, {"conscheck", ARRAY_DEV_ETC, 1, 4, 1, "consistency check"}, {"ctr_link", SAS_CONNECTOR_ETC, 2, 7, 8, "connector physical link"}, {"ctr_type", SAS_CONNECTOR_ETC, 1, 6, 7, "connector type"}, {"current", CURR_SENSOR_ETC, 2, 7, 16, "current in centiamps"}, {"dc_fail", UI_POWER_SUPPLY_ETC, 2, 3, 1, NULL}, {"disable", -1, 0, 5, 1, NULL}, /* -1 is for all element types */ {"disable_elm", SCSI_PORT_TRAN_ETC, 3, 4, 1, "disable port/transceiver"}, {"disable_elm", COMM_PORT_ETC, 3, 0, 1, "disable communication port"}, {"devoff", DEVICE_ETC, 3, 4, 1, NULL}, /* device off */ {"devoff", ARRAY_DEV_ETC, 3, 4, 1, NULL}, {"disp_mode", DISPLAY_ETC, 1, 1, 2, NULL}, {"disp_char", DISPLAY_ETC, 2, 7, 16, NULL}, {"dnr", ARRAY_DEV_ETC, 2, 6, 1, "do not remove"}, {"dnr", COOLING_ETC, 1, 6, 1, "do not remove"}, {"dnr", DEVICE_ETC, 2, 6, 1, "do not remove"}, {"dnr", ENC_SCELECTR_ETC, 1, 5, 1, "do not remove"}, {"dnr", POWER_SUPPLY_ETC, 1, 6, 1, "do not remove"}, {"dnr", UI_POWER_SUPPLY_ETC, 3, 3, 1, "do not remove"}, {"enable", SCSI_IPORT_ETC, 3, 0, 1, NULL}, {"enable", SCSI_TPORT_ETC, 3, 0, 1, NULL}, {"fail", AUD_ALARM_ETC, 1, 6, 1, NULL}, {"fail", COMM_PORT_ETC, 1, 7, 1, NULL}, {"fail", COOLING_ETC, 3, 6, 1, NULL}, {"fail", CURR_SENSOR_ETC, 3, 6, 1, NULL}, {"fail", DISPLAY_ETC, 1, 6, 1, NULL}, {"fail", DOOR_ETC, 1, 6, 1, NULL}, {"fail", ENC_SCELECTR_ETC, 1, 6, 1, NULL}, {"fail", KEY_PAD_ETC, 1, 6, 1, NULL}, {"fail", NV_CACHE_ETC, 3, 6, 1, NULL}, {"fail", POWER_SUPPLY_ETC, 3, 6, 1, NULL}, {"fail", SAS_CONNECTOR_ETC, 3, 6, 1, NULL}, {"fail", SAS_EXPANDER_ETC, 1, 6, 1, NULL}, {"fail", SCC_CELECTR_ETC, 3, 6, 1, NULL}, {"fail", SCSI_IPORT_ETC, 1, 6, 1, NULL}, {"fail", SCSI_PORT_TRAN_ETC, 1, 6, 1, NULL}, {"fail", SCSI_TPORT_ETC, 1, 6, 1, NULL}, {"fail", SIMPLE_SUBENC_ETC, 1, 6, 1, NULL}, {"fail", TEMPERATURE_ETC, 3, 6, 1, NULL}, {"fail", UI_POWER_SUPPLY_ETC, 3, 6, 1, NULL}, {"fail", VOLT_SENSOR_ETC, 1, 6, 1, NULL}, {"failure_ind", ENCLOSURE_ETC, 2, 1, 1, NULL}, {"failure", ENCLOSURE_ETC, 3, 1, 1, NULL}, {"fault", DEVICE_ETC, 3, 5, 1, NULL}, {"fault", ARRAY_DEV_ETC, 3, 5, 1, NULL}, {"hotspare", ARRAY_DEV_ETC, 1, 5, 1, NULL}, {"hotswap", COOLING_ETC, 3, 7, 1, NULL}, {"hotswap", ENC_SCELECTR_ETC, 3, 7, 1, NULL}, /* status only */ {"hw_reset", ENC_SCELECTR_ETC, 1, 2, 1, "hardware reset"}, /* 18-047r1 */ {"ident", DEVICE_ETC, 2, 1, 1, "flash LED"}, {"ident", ARRAY_DEV_ETC, 2, 1, 1, "flash LED"}, {"ident", POWER_SUPPLY_ETC, 1, 7, 1, "flash LED"}, {"ident", COMM_PORT_ETC, 1, 7, 1, "flash LED"}, {"ident", COOLING_ETC, 1, 7, 1, "flash LED"}, {"ident", CURR_SENSOR_ETC, 1, 7, 1, "flash LED"}, {"ident", DISPLAY_ETC, 1, 7, 1, "flash LED"}, {"ident", DOOR_ETC, 1, 7, 1, "flash LED"}, {"ident", ENC_SCELECTR_ETC, 1, 7, 1, "flash LED"}, {"ident", ENCLOSURE_ETC, 1, 7, 1, "flash LED"}, {"ident", KEY_PAD_ETC, 1, 7, 1, "flash LED"}, {"ident", LANGUAGE_ETC, 1, 7, 1, "flash LED"}, {"ident", AUD_ALARM_ETC, 1, 7, 1, NULL}, {"ident", NV_CACHE_ETC, 1, 7, 1, "flash LED"}, {"ident", SAS_CONNECTOR_ETC, 1, 7, 1, "flash LED"}, {"ident", SAS_EXPANDER_ETC, 1, 7, 1, "flash LED"}, {"ident", SCC_CELECTR_ETC, 1, 7, 1, "flash LED"}, {"ident", SCSI_IPORT_ETC, 1, 7, 1, "flash LED"}, {"ident", SCSI_PORT_TRAN_ETC, 1, 7, 1, "flash LED"}, {"ident", SCSI_TPORT_ETC, 1, 7, 1, "flash LED"}, {"ident", SIMPLE_SUBENC_ETC, 1, 7, 1, "flash LED"}, {"ident", TEMPERATURE_ETC, 1, 7, 1, "flash LED"}, {"ident", UI_POWER_SUPPLY_ETC, 3, 7, 1, "flash LED"}, {"ident", VOLT_SENSOR_ETC, 1, 7, 1, "flash LED"}, {"incritarray", ARRAY_DEV_ETC, 1, 3, 1, NULL}, {"infailedarray", ARRAY_DEV_ETC, 1, 2, 1, NULL}, {"info", AUD_ALARM_ETC, 3, 3, 1, "emits warning tone when set"}, {"insert", DEVICE_ETC, 2, 3, 1, NULL}, {"insert", ARRAY_DEV_ETC, 2, 3, 1, NULL}, {"intf_fail", UI_POWER_SUPPLY_ETC, 2, 0, 1, NULL}, {"language", LANGUAGE_ETC, 2, 7, 16, "language code"}, {"locate", DEVICE_ETC, 2, 1, 1, "flash LED"}, {"locate", ARRAY_DEV_ETC, 2, 1, 1, "flash LED"}, {"locate", POWER_SUPPLY_ETC, 1, 7, 1, "flash LED"}, {"locate", COMM_PORT_ETC, 1, 7, 1, "flash LED"}, {"locate", COOLING_ETC, 1, 7, 1, "flash LED"}, {"locate", CURR_SENSOR_ETC, 1, 7, 1, "flash LED"}, {"locate", DISPLAY_ETC, 1, 7, 1, "flash LED"}, {"locate", DOOR_ETC, 1, 7, 1, "flash LED"}, {"locate", ENC_SCELECTR_ETC, 1, 7, 1, "flash LED"}, {"locate", ENCLOSURE_ETC, 1, 7, 1, "flash LED"}, {"locate", KEY_PAD_ETC, 1, 7, 1, "flash LED"}, {"locate", LANGUAGE_ETC, 1, 7, 1, "flash LED"}, {"locate", AUD_ALARM_ETC, 1, 7, 1, NULL}, {"locate", NV_CACHE_ETC, 1, 7, 1, "flash LED"}, {"locate", SAS_CONNECTOR_ETC, 1, 7, 1, "flash LED"}, {"locate", SAS_EXPANDER_ETC, 1, 7, 1, "flash LED"}, {"locate", SCC_CELECTR_ETC, 1, 7, 1, "flash LED"}, {"locate", SCSI_IPORT_ETC, 1, 7, 1, "flash LED"}, {"locate", SCSI_PORT_TRAN_ETC, 1, 7, 1, "flash LED"}, {"locate", SCSI_TPORT_ETC, 1, 7, 1, "flash LED"}, {"locate", SIMPLE_SUBENC_ETC, 1, 7, 1, "flash LED"}, {"locate", TEMPERATURE_ETC, 1, 7, 1, "flash LED"}, {"locate", UI_POWER_SUPPLY_ETC, 3, 7, 1, "flash LED"}, {"locate", VOLT_SENSOR_ETC, 1, 7, 1, "flash LED"}, {"lol", SCSI_PORT_TRAN_ETC, 3, 1, 1, "Loss of Link"}, {"mated", SAS_CONNECTOR_ETC, 3, 7, 1, NULL}, {"missing", DEVICE_ETC, 2, 4, 1, NULL}, {"missing", ARRAY_DEV_ETC, 2, 4, 1, NULL}, {"mute", AUD_ALARM_ETC, 3, 6, 1, "control only: mute the alarm"}, {"muted", AUD_ALARM_ETC, 3, 6, 1, "status only: alarm is muted"}, {"off", POWER_SUPPLY_ETC, 3, 4, 1, "Not providing power"}, {"off", COOLING_ETC, 3, 4, 1, "Not providing cooling"}, {"offset_temp", TEMPERATURE_ETC, 1, 5, 6, "Offset for reference " "temperature"}, {"ok", ARRAY_DEV_ETC, 1, 7, 1, NULL}, {"on", COOLING_ETC, 3, 5, 1, NULL}, {"on", POWER_SUPPLY_ETC, 3, 5, 1, "0: turn (remain) off; 1: turn on"}, {"open", DOOR_ETC, 3, 1, 1, NULL}, {"overcurrent", CURR_SENSOR_ETC, 1, 1, 1, "overcurrent"}, {"overcurrent", POWER_SUPPLY_ETC, 2, 1, 1, "DC overcurrent"}, {"overcurrent", SAS_CONNECTOR_ETC, 3, 5, 1, NULL}, /* added ses3r07 */ {"overcurrent_warn", CURR_SENSOR_ETC, 1, 3, 1, "overcurrent warning"}, {"overtemp_fail", TEMPERATURE_ETC, 3, 3, 1, "Overtemperature failure"}, {"overtemp_warn", TEMPERATURE_ETC, 3, 2, 1, "Overtemperature warning"}, {"overvoltage", POWER_SUPPLY_ETC, 2, 3, 1, "DC overvoltage"}, {"overvoltage", VOLT_SENSOR_ETC, 1, 1, 1, "overvoltage"}, {"overvoltage_warn", POWER_SUPPLY_ETC, 1, 3, 1, "DC overvoltage warning"}, {"pow_cycle", ENCLOSURE_ETC, 2, 7, 2, "0: no; 1: start in pow_c_delay minutes; 2: cancel"}, {"pow_c_delay", ENCLOSURE_ETC, 2, 5, 6, "delay in minutes before starting power cycle (max: 60)"}, {"pow_c_duration", ENCLOSURE_ETC, 3, 7, 6, "0: power off, restore within 1 minute; <=60: restore within that many " "minutes; 63: power off, wait for manual power on"}, /* slightly different in Enclosure status element */ {"pow_c_time", ENCLOSURE_ETC, 2, 7, 6, "time in minutes remaining until starting power cycle; 0: not " "scheduled; <=60: scheduled in that many minutes; 63: in zero minutes"}, {"prdfail", -1, 0, 6, 1, "predict failure"}, {"rebuildremap", ARRAY_DEV_ETC, 1, 1, 1, NULL}, {"remove", DEVICE_ETC, 2, 2, 1, NULL}, {"remove", ARRAY_DEV_ETC, 2, 2, 1, NULL}, {"remind", AUD_ALARM_ETC, 3, 4, 1, NULL}, {"report", ENC_SCELECTR_ETC, 2, 0, 1, NULL}, /* status only */ {"report", SCC_CELECTR_ETC, 2, 0, 1, NULL}, {"report", SCSI_IPORT_ETC, 2, 0, 1, NULL}, {"report", SCSI_TPORT_ETC, 2, 0, 1, NULL}, {"rqst_mute", AUD_ALARM_ETC, 3, 7, 1, "status only: alarm was manually muted"}, {"rqst_override", TEMPERATURE_ETC, 3, 7, 1, "Request(ed) override"}, {"rrabort", ARRAY_DEV_ETC, 1, 0, 1, "rebuild/remap abort"}, {"rsvddevice", ARRAY_DEV_ETC, 1, 6, 1, "reserved device"}, {"select_element", ENC_SCELECTR_ETC, 2, 0, 1, NULL}, /* control */ {"short_stat", SIMPLE_SUBENC_ETC, 3, 7, 8, "short enclosure status"}, {"size", NV_CACHE_ETC, 2, 7, 16, NULL}, {"speed_act", COOLING_ETC, 1, 2, 11, "actual speed (rpm / 10)"}, {"speed_code", COOLING_ETC, 3, 2, 3, "0: leave; 1: lowest... 7: highest"}, {"size_mult", NV_CACHE_ETC, 1, 1, 2, NULL}, {"swap", -1, 0, 4, 1, NULL}, /* Reset swap */ {"sw_reset", ENC_SCELECTR_ETC, 1, 3, 1, "software reset"},/* 18-047r1 */ {"temp", TEMPERATURE_ETC, 2, 7, 8, "(Requested) temperature"}, {"unlock", DOOR_ETC, 3, 0, 1, NULL}, {"undertemp_fail", TEMPERATURE_ETC, 3, 1, 1, "Undertemperature failure"}, {"undertemp_warn", TEMPERATURE_ETC, 3, 0, 1, "Undertemperature warning"}, {"undervoltage", POWER_SUPPLY_ETC, 2, 2, 1, "DC undervoltage"}, {"undervoltage", VOLT_SENSOR_ETC, 1, 0, 1, "undervoltage"}, {"undervoltage_warn", POWER_SUPPLY_ETC, 1, 2, 1, "DC undervoltage warning"}, {"ups_fail", UI_POWER_SUPPLY_ETC, 2, 2, 1, NULL}, {"urgency", AUD_ALARM_ETC, 3, 3, 4, NULL}, /* Tone urgency control bits */ {"voltage", VOLT_SENSOR_ETC, 2, 7, 16, "voltage in centivolts"}, {"warning", UI_POWER_SUPPLY_ETC, 2, 1, 1, NULL}, {"warning", ENCLOSURE_ETC, 3, 0, 1, NULL}, {"warning_ind", ENCLOSURE_ETC, 2, 0, 1, NULL}, {"xmit_fail", SCSI_PORT_TRAN_ETC, 3, 0, 1, "Transmitter failure"}, {NULL, 0, 0, 0, 0, NULL}, }; /* These are for the Threshold in/out diagnostic page */ static struct acronym2tuple th_a2t_arr[] = { {"high_crit", -1, 0, 7, 8, NULL}, {"high_warn", -1, 1, 7, 8, NULL}, {"low_crit", -1, 2, 7, 8, NULL}, {"low_warn", -1, 3, 7, 8, NULL}, {NULL, 0, 0, 0, 0, NULL}, }; /* These are for the Additional element status diagnostic page for SAS with * the EIP bit set. First phy only. Index from start of AES descriptor */ static struct acronym2tuple ae_sas_a2t_arr[] = { {"at_sas_addr", -1, 12, 7, 64, NULL}, /* best viewed with --hex --get= */ /* typically this is the expander's SAS address */ {"dev_type", -1, 8, 6, 3, "1: SAS/SATA dev, 2: expander"}, {"dsn", -1, 7, 7, 8, "device slot number (255: none)"}, {"num_phys", -1, 4, 7, 8, "number of phys"}, {"phy_id", -1, 28, 7, 8, NULL}, {"sas_addr", -1, 20, 7, 64, NULL}, /* should be disk or tape ... */ {"exp_sas_addr", -1, 8, 7, 64, NULL}, /* expander address */ {"sata_dev", -1, 11, 0, 1, NULL}, {"sata_port_sel", -1, 11, 7, 1, NULL}, {"smp_init", -1, 10, 1, 1, NULL}, {"smp_targ", -1, 11, 1, 1, NULL}, {"ssp_init", -1, 10, 3, 1, NULL}, {"ssp_targ", -1, 11, 3, 1, NULL}, {"stp_init", -1, 10, 2, 1, NULL}, {"stp_targ", -1, 11, 2, 1, NULL}, {NULL, 0, 0, 0, 0, NULL}, }; /* Boolean array of element types of interest to the Additional Element * Status page. Indexed by element type (0 <= et < 32). */ static bool active_et_aesp_arr[NUM_ACTIVE_ET_AESP_ARR] = { false, true /* dev */, false, false, false, false, false, true /* esce */, false, false, false, false, false, false, false, false, false, false, false, false, true /* starg */, true /* sinit */, false, true /* arr */, true /* sas exp */, false, false, false, false, false, false, false, }; /* Command line long option names with corresponding short letter. */ static struct option long_options[] = { {"all", no_argument, 0, 'a'}, {"ALL", no_argument, 0, 'z'}, {"byte1", required_argument, 0, 'b'}, {"clear", required_argument, 0, 'C'}, {"control", no_argument, 0, 'c'}, {"data", required_argument, 0, 'd'}, {"descriptor", required_argument, 0, 'D'}, {"dev-slot-num", required_argument, 0, 'x'}, {"dev_slot_num", required_argument, 0, 'x'}, {"dsn", required_argument, 0, 'x'}, {"eiioe", required_argument, 0, 'E'}, {"enumerate", no_argument, 0, 'e'}, {"filter", no_argument, 0, 'f'}, {"get", required_argument, 0, 'G'}, {"help", no_argument, 0, 'h'}, {"hex", no_argument, 0, 'H'}, {"index", required_argument, 0, 'I'}, {"inhex", required_argument, 0, 'X'}, {"inner-hex", no_argument, 0, 'i'}, {"inner_hex", no_argument, 0, 'i'}, {"join", no_argument, 0, 'j'}, {"list", no_argument, 0, 'l'}, {"nickid", required_argument, 0, 'N'}, {"nickname", required_argument, 0, 'n'}, {"mask", required_argument, 0, 'M'}, {"maxlen", required_argument, 0, 'm'}, {"page", required_argument, 0, 'p'}, {"quiet", no_argument, 0, 'q'}, {"raw", no_argument, 0, 'r'}, {"readonly", no_argument, 0, 'R'}, {"sas-addr", required_argument, 0, 'A'}, {"sas_addr", required_argument, 0, 'A'}, {"set", required_argument, 0, 'S'}, {"status", no_argument, 0, 's'}, {"verbose", no_argument, 0, 'v'}, {"version", no_argument, 0, 'V'}, {"warn", no_argument, 0, 'w'}, {0, 0, 0, 0}, }; /* For overzealous SES device servers that don't like some status elements * sent back as control elements. This table is as per ses3r06. */ static uint8_t ses3_element_cmask_arr[NUM_ETC][4] = { /* Element type code (ETC) names; comment */ {0x40, 0xff, 0xff, 0xff}, /* [0] unspecified */ {0x40, 0, 0x4e, 0x3c}, /* DEVICE */ {0x40, 0x80, 0, 0x60}, /* POWER_SUPPLY */ {0x40, 0x80, 0, 0x60}, /* COOLING; requested speed as is unless */ {0x40, 0xc0, 0, 0}, /* TEMPERATURE */ {0x40, 0xc0, 0, 0x1}, /* DOOR */ {0x40, 0xc0, 0, 0x5f}, /* AUD_ALARM */ {0x40, 0xc0, 0x1, 0}, /* ENC_SCELECTR_ETC */ {0x40, 0xc0, 0, 0}, /* SCC_CELECTR */ {0x40, 0xc0, 0, 0}, /* NV_CACHE */ {0x40, 0, 0, 0}, /* [10] INV_OP_REASON */ {0x40, 0, 0, 0xc0}, /* UI_POWER_SUPPLY */ {0x40, 0xc0, 0xff, 0xff}, /* DISPLAY */ {0x40, 0xc3, 0, 0}, /* KEY_PAD */ {0x40, 0x80, 0, 0xff}, /* ENCLOSURE */ {0x40, 0xc0, 0, 0x10}, /* SCSI_PORT_TRAN */ {0x40, 0x80, 0xff, 0xff}, /* LANGUAGE */ {0x40, 0xc0, 0, 0x1}, /* COMM_PORT */ {0x40, 0xc0, 0, 0}, /* VOLT_SENSOR */ {0x40, 0xc0, 0, 0}, /* CURR_SENSOR */ {0x40, 0xc0, 0, 0x1}, /* [20] SCSI_TPORT */ {0x40, 0xc0, 0, 0x1}, /* SCSI_IPORT */ {0x40, 0xc0, 0, 0}, /* SIMPLE_SUBENC */ {0x40, 0xff, 0x4e, 0x3c}, /* ARRAY */ {0x40, 0xc0, 0, 0}, /* SAS_EXPANDER */ {0x40, 0x80, 0, 0x40}, /* SAS_CONNECTOR */ }; static int read_hex(const char * inp, uint8_t * arr, int mx_arr_len, int * arr_len, bool in_hex, bool may_gave_at, int verb); static int strcase_eq(const char * s1p, const char * s2p); static void enumerate_diag_pages(void); static bool saddr_non_zero(const uint8_t * bp); static const char * find_in_diag_page_desc(int page_num); static void usage(int help_num) { if (2 != help_num) { pr2serr( "Usage: sg_ses [--all] [--ALL] [--descriptor=DES] " "[--dev-slot-num=SN]\n" " [--eiioe=A_F] [--filter] [--get=STR] " "[--hex]\n" " [--index=IIA | =TIA,II] [--inner-hex] [--join] " "[--maxlen=LEN]\n" " [--page=PG] [--quiet] [--raw] [--readonly] " "[--sas-addr=SA]\n" " [--status] [--verbose] [--warn] DEVICE\n\n" " sg_ses --control [--byte1=B1] [--clear=STR] " "[--data=H,H...]\n" " [--descriptor=DES] [--dev-slot-num=SN] " "[--index=IIA | =TIA,II]\n" " [--inhex=FN] [--mask] [--maxlen=LEN] " "[--nickid=SEID]\n" " [--nickname=SEN] [--page=PG] [--sas-addr=SA] " "[--set=STR]\n" " [--verbose] DEVICE\n\n" " sg_ses --data=@FN --status [-rr] []\n" " sg_ses --inhex=FN --status [-rr] []\n\n" " sg_ses [--enumerate] [--help] [--index=IIA] [--list] " "[--version]\n\n" ); if ((help_num < 1) || (help_num > 2)) { pr2serr("Or the corresponding short option usage: \n" " sg_ses [-a] [-D DES] [-x SN] [-E A_F] [-f] [-G STR] " "[-H] [-I IIA|TIA,II]\n" " [-i] [-j] [-m LEN] [-p PG] [-q] [-r] [-R] " "[-A SA] [-s] [-v] [-w]\n" " DEVICE\n\n" " sg_ses [-b B1] [-C STR] [-c] [-d H,H...] [-D DES] " "[-x SN] [-I IIA|TIA,II]\n" " [-M] [-m LEN] [-N SEID] [-n SEN] [-p PG] " "[-A SA] [-S STR]\n" " [-v] DEVICE\n\n" " sg_ses -d @FN -s [-rr] []\n" " sg_ses -X FN -s [-rr] []\n\n" " sg_ses [-e] [-h] [-I IIA] [-l] [-V]\n" ); pr2serr("\nFor help use '-h' one or more times.\n"); return; } pr2serr( " where the main options are:\n" " --all|-a show (almost) all status pages (same " "as --join)\n" " --clear=STR|-C STR clear field by acronym or position\n" " --control|-c send control information (def: fetch " "status)\n" " --descriptor=DES|-D DES descriptor name (for indexing)\n" " --dev-slot-num=SN|--dsn=SN|-x SN device slot number " "(for indexing)\n" " --filter|-f filter out enclosure status flags that " "are clear\n" " use twice for status=okay entries " "only\n" " --get=STR|-G STR get value of field by acronym or " "position\n" " --help|-h print out usage message, use twice for " "additional\n" " --index=IIA|-I IIA individual index ('-1' for overall) " "or element\n" " type abbreviation (e.g. 'arr'). A " "range may be\n" " given for the individual index " "(e.g. '2-5')\n" " --index=TIA,II|-I TIA,II comma separated pair: TIA is " "type header\n" " index or element type " "abbreviation;\n" " II is individual index ('-1' " "for overall)\n" ); pr2serr( " --join|-j group Enclosure Status, Element " "Descriptor\n" " and Additional Element Status pages. " "Use twice\n" " to add Threshold In page\n" " --page=PG|-p PG diagnostic page code (abbreviation " "or number)\n" " (def: 'ssp' [0x0] (supported diagnostic " "pages))\n" " --sas-addr=SA|-A SA SAS address in hex (for indexing)\n" " --set=STR|-S STR set value of field by acronym or " "position\n" " --status|-s fetch status information (default " "action)\n\n" "First usage above is for fetching pages or fields from a SCSI " "enclosure.\nThe second usage is for changing a page or field in " "an enclosure. The\n'--clear=', '--get=' and '--set=' options " "can appear multiple times.\nUse '-hh' for more help, including " "the options not explained above.\n"); } else { /* for '-hh' or '--help --help' */ pr2serr( " where the remaining sg_ses options are:\n" " --ALL|-z same as --all twice (adds thresholds)\n" " --byte1=B1|-b B1 byte 1 (2nd byte) of control page set " "to B1\n" " --data=H,H...|-d H,H... string of ASCII hex bytes to " "send as a\n" " control page or decode as a " "status page\n" " --data=- | -d - fetch string of ASCII hex bytes from " "stdin\n" " --data=@FN | -d @FN fetch string of ASCII hex bytes from " "file: FN\n" " --eiioe=A_F|-E A_F A_F is either 'auto' or 'force'. " "'force' acts\n" " as if EIIOE field is 1, 'auto' tries " "to guess\n" " --enumerate|-e enumerate page names + element types " "(ignore\n" " DEVICE). Use twice for clear,get,set " "acronyms\n" " --hex|-H print page response (or field) in hex\n" " --inhex=FN|-X FN alternate form of --data=@FN\n" " --inner-hex|-i print innermost level of a" " status page in hex\n" " --list|-l same as '--enumerate' option\n" " --mask|-M ignore status element mask in modify " "actions\n" " (e.g.--set= and --clear=) (def: apply " "mask)\n" " --maxlen=LEN|-m LEN max response length (allocation " "length in cdb)\n" " --nickid=SEID|-N SEID SEID is subenclosure identifier " "(def: 0)\n" " used to specify which nickname to " "change\n" " --nickname=SEN|-n SEN SEN is new subenclosure nickname\n" " --quiet|-q suppress some output messages\n" " --raw|-r print status page in ASCII hex suitable " "for '-d';\n" " when used twice outputs page in binary " "to stdout\n" " --readonly|-R open DEVICE read-only (def: " "read-write)\n" " --verbose|-v increase verbosity\n" " --version|-V print version string and exit\n" " --warn|-w warn about join (and other) issues\n\n" "If no options are given then DEVICE's supported diagnostic " "pages are\nlisted. STR can be ':" "[:][=]'\nor '[=val]'. Element type " "abbreviations may be followed by a\nnumber (e.g. 'ps1' is " "the second power supply element type). Use\n'sg_ses -e' and " "'sg_ses -ee' for more information.\n\n" ); pr2serr( "Low level indexing can be done with one of the two '--index=' " "options.\nAlternatively, medium level indexing can be done " "with either the\n'--descriptor=', 'dev-slot-num=' or " "'--sas-addr=' options. Support for\nthe medium level options " "in the SES device is itself optional.\n" ); } } /* Return 0 for okay, else an error */ static int parse_index(struct opts_t *op) { int n, n2; const char * cp; char * mallcp; char * c2p; const struct element_type_t * etp; char b[64]; const int blen = sizeof(b); op->ind_given = true; n2 = 0; if ((cp = strchr(op->index_str, ','))) { /* decode number following comma */ if (0 == strcmp("-1", cp + 1)) n = -1; else { const char * cc3p; n = sg_get_num_nomult(cp + 1); if ((n < 0) || (n > 255)) { pr2serr("bad argument to '--index=', after comma expect " "number from -1 to 255\n"); return SG_LIB_SYNTAX_ERROR; } if ((cc3p = strchr(cp + 1, '-'))) { n2 = sg_get_num_nomult(cc3p + 1); if ((n2 < n) || (n2 > 255)) { pr2serr("bad argument to '--index', after '-' expect " "number from -%d to 255\n", n); return SG_LIB_SYNTAX_ERROR; } } } op->ind_indiv = n; if (n2 > 0) op->ind_indiv_last = n2; n = cp - op->index_str; if (n >= (blen - 1)) { pr2serr("bad argument to '--index', string prior to comma too " "long\n"); return SG_LIB_SYNTAX_ERROR; } } else { /* no comma found in index_str */ n = strlen(op->index_str); if (n >= (blen - 1)) { pr2serr("bad argument to '--index', string too long\n"); return SG_LIB_SYNTAX_ERROR; } } snprintf(b, blen, "%.*s", n, op->index_str); if (0 == strcmp("-1", b)) { if (cp) { pr2serr("bad argument to '--index', unexpected '-1' type header " "index\n"); return SG_LIB_SYNTAX_ERROR; } op->ind_th = 0; op->ind_indiv = -1; } else if (isdigit((uint8_t)b[0])) { n = sg_get_num_nomult(b); if ((n < 0) || (n > 255)) { pr2serr("bad numeric argument to '--index', expect number from 0 " "to 255\n"); return SG_LIB_SYNTAX_ERROR; } if (cp) /* argument to left of comma */ op->ind_th = n; else { /* no comma found, so 'n' is ind_indiv */ op->ind_th = 0; op->ind_indiv = n; if ((c2p = strchr(b, '-'))) { n2 = sg_get_num_nomult(c2p + 1); if ((n2 < n) || (n2 > 255)) { pr2serr("bad argument to '--index', after '-' expect " "number from -%d to 255\n", n); return SG_LIB_SYNTAX_ERROR; } } op->ind_indiv_last = n2; } } else if ('_' == b[0]) { /* leading "_" prefixes element type code */ if ((c2p = strchr(b + 1, '_'))) *c2p = '\0'; /* subsequent "_" prefixes e.t. index */ n = sg_get_num_nomult(b + 1); if ((n < 0) || (n > 255)) { pr2serr("bad element type code for '--index', expect value from " "0 to 255\n"); return SG_LIB_SYNTAX_ERROR; } element_type_by_code.elem_type_code = n; mallcp = (char *)malloc(8); /* willfully forget about freeing this */ if (NULL == mallcp) return sg_convert_errno(ENOMEM); mallcp[0] = '_'; snprintf(mallcp + 1, 6, "%d", n); element_type_by_code.abbrev = mallcp; if (c2p) { n = sg_get_num_nomult(c2p + 1); if ((n < 0) || (n > 255)) { pr2serr("bad element type code for '--index', expect " " from 0 to 255\n"); return SG_LIB_SYNTAX_ERROR; } op->ind_et_inst = n; } op->ind_etp = &element_type_by_code; if (NULL == cp) op->ind_indiv = -1; } else { /* element type abbreviation perhaps followed by */ int b_len = strlen(b); for (etp = element_type_arr; etp->desc; ++etp) { n = strlen(etp->abbrev); if ((n == b_len) && (0 == strncmp(b, etp->abbrev, n))) break; } if (NULL == etp->desc) { pr2serr("bad element type abbreviation [%s] for '--index'\n" "use '--enumerate' to see possibles\n", b); return SG_LIB_SYNTAX_ERROR; } if (b_len > n) { n = sg_get_num_nomult(b + n); if ((n < 0) || (n > 255)) { pr2serr("bad element type abbreviation for '--index', " "expect from 0 to 255\n"); return SG_LIB_SYNTAX_ERROR; } op->ind_et_inst = n; } op->ind_etp = etp; if (NULL == cp) op->ind_indiv = -1; } if (op->verbose > 1) { if (op->ind_etp) pr2serr(" element type abbreviation: %s, etp_num=%d, " "individual index=%d\n", op->ind_etp->abbrev, op->ind_et_inst, op->ind_indiv); else pr2serr(" type header index=%d, individual index=%d\n", op->ind_th, op->ind_indiv); } return 0; } /* command line process, options and arguments. Returns 0 if ok. */ static int parse_cmd_line(struct opts_t *op, int argc, char *argv[]) { int c, j, n, d_len, ret; const char * data_arg = NULL; const char * inhex_arg = NULL; uint64_t saddr; const char * cp; while (1) { int option_index = 0; c = getopt_long(argc, argv, "aA:b:cC:d:D:eE:fG:hHiI:jln:N:m:Mp:qrRs" "S:vVwx:z", long_options, &option_index); if (c == -1) break; switch (c) { case 'a': /* --all is synonym for --join */ ++op->do_join; break; case 'A': /* SAS address, assumed to be hex */ cp = optarg; if ((strlen(optarg) > 2) && ('X' == toupper((uint8_t)optarg[1]))) cp = optarg + 2; if (1 != sscanf(cp, "%" SCNx64 "", &saddr)) { pr2serr("bad argument to '--sas-addr=SA'\n"); return SG_LIB_SYNTAX_ERROR; } sg_put_unaligned_be64(saddr, op->sas_addr + 0); if (sg_all_ffs(op->sas_addr, 8)) { pr2serr("error decoding '--sas-addr=SA' argument\n"); return SG_LIB_SYNTAX_ERROR; } break; case 'b': op->byte1 = sg_get_num_nomult(optarg); if ((op->byte1 < 0) || (op->byte1 > 255)) { pr2serr("bad argument to '--byte1=B1' (0 to 255 " "inclusive)\n"); return SG_LIB_SYNTAX_ERROR; } op->byte1_given = true; break; case 'c': op->do_control = true; break; case 'C': if (strlen(optarg) >= CGS_STR_MAX_SZ) { pr2serr("--clear= option too long (max %d characters)\n", CGS_STR_MAX_SZ); return SG_LIB_SYNTAX_ERROR; } if (op->num_cgs < CGS_CL_ARR_MAX_SZ) { op->cgs_cl_arr[op->num_cgs].cgs_sel = CLEAR_OPT; strcpy(op->cgs_cl_arr[op->num_cgs].cgs_str, optarg); ++op->num_cgs; } else { pr2serr("Too many --clear=, --get= and --set= options " "(max: %d)\n", CGS_CL_ARR_MAX_SZ); return SG_LIB_CONTRADICT; } break; case 'd': data_arg = optarg; op->do_data = true; break; case 'D': op->desc_name = optarg; break; case 'e': ++op->enumerate; break; case 'E': if (0 == strcmp("auto", optarg)) op->eiioe_auto = true; else if (0 == strcmp("force", optarg)) op->eiioe_force = true; else { pr2serr("--eiioe option expects 'auto' or 'force' as an " "argument\n"); return SG_LIB_CONTRADICT; } break; case 'f': ++op->do_filter; break; case 'G': if (strlen(optarg) >= CGS_STR_MAX_SZ) { pr2serr("--get= option too long (max %d characters)\n", CGS_STR_MAX_SZ); return SG_LIB_SYNTAX_ERROR; } if (op->num_cgs < CGS_CL_ARR_MAX_SZ) { op->cgs_cl_arr[op->num_cgs].cgs_sel = GET_OPT; strcpy(op->cgs_cl_arr[op->num_cgs].cgs_str, optarg); ++op->num_cgs; } else { pr2serr("Too many --clear=, --get= and --set= options " "(max: %d)\n", CGS_CL_ARR_MAX_SZ); return SG_LIB_CONTRADICT; } break; case 'h': ++op->do_help; break; case '?': pr2serr("\n"); usage(0); return SG_LIB_SYNTAX_ERROR; case 'H': ++op->do_hex; break; case 'i': op->inner_hex = true; break; case 'I': op->index_str = optarg; break; case 'j': ++op->do_join; break; case 'l': op->do_list = true; break; case 'n': op->nickname_str = optarg; break; case 'N': op->seid = sg_get_num_nomult(optarg); if ((op->seid < 0) || (op->seid > 255)) { pr2serr("bad argument to '--nickid=SEID' (0 to 255 " "inclusive)\n"); return SG_LIB_SYNTAX_ERROR; } op->seid_given = true; break; case 'm': n = sg_get_num(optarg); if ((n < 0) || (n > 65535)) { pr2serr("bad argument to '--maxlen=LEN' (0 to 65535 " "inclusive expected)\n"); return SG_LIB_SYNTAX_ERROR; } if (0 == n) op->maxlen = MX_ALLOC_LEN; else if (n < MIN_MAXLEN) { pr2serr("Warning: --maxlen=LEN less than %d ignored\n", MIN_MAXLEN); op->maxlen = MX_ALLOC_LEN; } else op->maxlen = n; break; case 'M': op->mask_ign = true; break; case 'p': if (isdigit((uint8_t)optarg[0])) { op->page_code = sg_get_num_nomult(optarg); if ((op->page_code < 0) || (op->page_code > 255)) { pr2serr("bad argument to '--page=PG' (0 to 255 " "inclusive)\n"); return SG_LIB_SYNTAX_ERROR; } } else { const struct diag_page_abbrev * ap; for (ap = dp_abbrev; ap->abbrev; ++ap) { if (strcase_eq(ap->abbrev, optarg)) { op->page_code = ap->page_code; break; } } if (NULL == ap->abbrev) { pr2serr("'--page=PG' argument abbreviation \"%s\" not " "found\nHere are the choices:\n", optarg); enumerate_diag_pages(); return SG_LIB_SYNTAX_ERROR; } } op->page_code_given = true; break; case 'q': op->quiet = true; break; case 'r': ++op->do_raw; break; case 'R': op->o_readonly = true; break; case 's': op->do_status = true; break; case 'S': if (strlen(optarg) >= CGS_STR_MAX_SZ) { pr2serr("--set= option too long (max %d characters)\n", CGS_STR_MAX_SZ); return SG_LIB_SYNTAX_ERROR; } if (op->num_cgs < CGS_CL_ARR_MAX_SZ) { op->cgs_cl_arr[op->num_cgs].cgs_sel = SET_OPT; strcpy(op->cgs_cl_arr[op->num_cgs].cgs_str, optarg); ++op->num_cgs; } else { pr2serr("Too many --clear=, --get= and --set= options " "(max: %d)\n", CGS_CL_ARR_MAX_SZ); return SG_LIB_CONTRADICT; } break; case 'v': op->verbose_given = true; ++op->verbose; break; case 'V': op->version_given = true; return 0; case 'w': op->warn = true; break; case 'x': op->dev_slot_num = sg_get_num_nomult(optarg); if ((op->dev_slot_num < 0) || (op->dev_slot_num > 255)) { pr2serr("bad argument to '--dev-slot-num' (0 to 255 " "inclusive)\n"); return SG_LIB_SYNTAX_ERROR; } break; case 'X': /* --inhex=FN for compatibility with other utils */ inhex_arg = optarg; op->do_data = true; break; case 'z': /* --ALL and -z are synonyms for '--join --join' */ /* -A already used for --sas-addr=SA shortened form */ op->do_join += 2; break; default: pr2serr("unrecognised option code 0x%x ??\n", c); goto err_help; } } if (op->do_help) return 0; if (optind < argc) { if (NULL == op->dev_name) { op->dev_name = argv[optind]; ++optind; } if (optind < argc) { for (; optind < argc; ++optind) pr2serr("Unexpected extra argument: %s\n", argv[optind]); goto err_help; } } op->mx_arr_len = (op->maxlen > MIN_DATA_IN_SZ) ? op->maxlen : MIN_DATA_IN_SZ; op->data_arr = sg_memalign(op->mx_arr_len, 0 /* page aligned */, &op->free_data_arr, false); if (NULL == op->data_arr) { pr2serr("unable to allocate %u bytes on heap\n", op->mx_arr_len); return sg_convert_errno(ENOMEM); } if (data_arg || inhex_arg) { if (inhex_arg) { data_arg = inhex_arg; if (read_hex(data_arg, op->data_arr + DATA_IN_OFF, op->mx_arr_len - DATA_IN_OFF, &op->arr_len, (op->do_raw < 2), false, op->verbose)) { pr2serr("bad argument, expect '--inhex=FN' or '--inhex=-'\n"); return SG_LIB_SYNTAX_ERROR; } } else { if (read_hex(data_arg, op->data_arr + DATA_IN_OFF, op->mx_arr_len - DATA_IN_OFF, &op->arr_len, (op->do_raw < 2), true, op->verbose)) { pr2serr("bad argument, expect '--data=H,H...', '--data=-' or " "'--data=@FN'\n"); return SG_LIB_SYNTAX_ERROR; } } op->do_raw = 0; /* struct data_in_desc_t stuff does not apply when --control */ if (op->do_status && (op->arr_len > 3)) { int off; int pc = 0; const uint8_t * bp = op->data_arr + DATA_IN_OFF; struct data_in_desc_t * didp = data_in_desc_arr; d_len = sg_get_unaligned_be16(bp + 2) + 4; for (n = 0, off = 0; n < MX_DATA_IN_DESCS; ++n, ++didp) { didp->in_use = true; pc = bp[0]; didp->page_code = pc; didp->offset = off; didp->dp_len = d_len; off += d_len; if ((off + 3) < op->arr_len) { bp += d_len; d_len = sg_get_unaligned_be16(bp + 2) + 4; } else { ++n; break; } } if (1 == n) { op->page_code_given = true; op->page_code = pc; } else /* n must be > 1 */ op->many_dpages = true; if (op->verbose > 3) { int k; char b[128]; for (didp = data_in_desc_arr, k = 0; k < n; ++k, ++didp) { if ((cp = find_in_diag_page_desc(didp->page_code))) snprintf(b, sizeof(b), "%s dpage", cp); else snprintf(b, sizeof(b), "dpage 0x%x", didp->page_code); pr2serr("%s found, offset %d, dp_len=%d\n", b, didp->offset, didp->dp_len); } } } } if (op->do_join && op->do_control) { pr2serr("cannot have '--join' and '--control'\n"); goto err_help; } if (op->index_str) { ret = parse_index(op); if (ret) { pr2serr(" For more information use '--help'\n"); return ret; } } if (op->desc_name || (op->dev_slot_num >= 0) || saddr_non_zero(op->sas_addr)) { if (op->ind_given) { pr2serr("cannot have --index with either --descriptor, " "--dev-slot-num or --sas-addr\n"); goto err_help; } if (((!! op->desc_name) + (op->dev_slot_num >= 0) + saddr_non_zero(op->sas_addr)) > 1) { pr2serr("can only have one of --descriptor, " "--dev-slot-num and --sas-addr\n"); goto err_help; } if ((0 == op->do_join) && (! op->do_control) && (0 == op->num_cgs) && (! op->page_code_given)) { ++op->do_join; /* implicit --join */ if (op->verbose) pr2serr("process as if --join option is set\n"); } } if (op->ind_given) { if ((0 == op->do_join) && (! op->do_control) && (0 == op->num_cgs) && (! op->page_code_given)) { op->page_code_given = true; op->page_code = ENC_STATUS_DPC; /* implicit status page */ if (op->verbose) pr2serr("assume --page=2 (es) option is set\n"); } } if (op->do_list || op->enumerate) return 0; if (op->do_control && op->do_status) { pr2serr("cannot have both '--control' and '--status'\n"); goto err_help; } else if (op->do_control) { if (op->nickname_str || op->seid_given) ; else if (! op->do_data) { pr2serr("need to give '--data' in control mode\n"); goto err_help; } } else if (! op->do_status) { if (op->do_data) { pr2serr("when user data given, require '--control' or " "'--status' option\n"); goto err_help; } op->do_status = true; /* default to receiving status pages */ } else if (op->do_status && op->do_data && op->dev_name) { pr2serr(">>> Warning: device name (%s) will be ignored\n", op->dev_name); op->dev_name = NULL; /* quash device name */ } if (op->nickname_str) { if (! op->do_control) { pr2serr("since '--nickname=' implies control mode, require " "'--control' as well\n"); goto err_help; } if (op->page_code_given) { if (SUBENC_NICKNAME_DPC != op->page_code) { pr2serr("since '--nickname=' assume or expect " "'--page=snic'\n"); goto err_help; } } else op->page_code = SUBENC_NICKNAME_DPC; } else if (op->seid_given) { pr2serr("'--nickid=' must be used together with '--nickname='\n"); goto err_help; } if ((op->verbose > 4) && saddr_non_zero(op->sas_addr)) { pr2serr(" SAS address (in hex): "); for (j = 0; j < 8; ++j) pr2serr("%02x", op->sas_addr[j]); pr2serr("\n"); } if ((! (op->do_data && op->do_status)) && (NULL == op->dev_name)) { pr2serr("missing DEVICE name!\n\n"); goto err_help; } return 0; err_help: if (op->verbose) { pr2serr("\n"); usage(0); } return SG_LIB_SYNTAX_ERROR; } /* Parse clear/get/set string, writes output to '*tavp'. Uses 'buff' for * scratch area. Returns 0 on success, else -1. */ static int parse_cgs_str(char * buff, struct tuple_acronym_val * tavp) { char * esp; char * colp; unsigned int ui; tavp->acron = NULL; tavp->val_str = NULL; tavp->start_byte = -1; tavp->num_bits = 1; if ((esp = strchr(buff, '='))) { tavp->val_str = esp + 1; *esp = '\0'; if (0 == strcmp("-1", esp + 1)) tavp->val = -1; else { tavp->val = sg_get_llnum_nomult(esp + 1); if (-1 == tavp->val) { pr2serr("unable to decode: %s value\n", esp + 1); pr2serr(" expected: [=]\n"); return -1; } } } if (isalpha((uint8_t)buff[0])) tavp->acron = buff; else { char * cp; colp = strchr(buff, ':'); if ((NULL == colp) || (buff == colp)) return -1; *colp = '\0'; if (('0' == buff[0]) && ('X' == toupper((uint8_t)buff[1]))) { if (1 != sscanf(buff + 2, "%x", &ui)) return -1; tavp->start_byte = ui; } else if ('H' == toupper((uint8_t)*(colp - 1))) { if (1 != sscanf(buff, "%x", &ui)) return -1; tavp->start_byte = ui; } else { if (1 != sscanf(buff, "%d", &tavp->start_byte)) return -1; } if ((tavp->start_byte < 0) || (tavp->start_byte > 127)) { pr2serr(" needs to be between 0 and 127\n"); return -1; } cp = colp + 1; colp = strchr(cp, ':'); if (cp == colp) return -1; if (colp) *colp = '\0'; if (1 != sscanf(cp, "%d", &tavp->start_bit)) return -1; if ((tavp->start_bit < 0) || (tavp->start_bit > 7)) { pr2serr(" needs to be between 0 and 7\n"); return -1; } if (colp) { if (1 != sscanf(colp + 1, "%d", &tavp->num_bits)) return -1; } if ((tavp->num_bits < 1) || (tavp->num_bits > 64)) { pr2serr(" needs to be between 1 and 64\n"); return -1; } } return 0; } /* Fetch diagnostic page name (control or out). Returns NULL if not found. */ static const char * find_out_diag_page_desc(int page_num) { const struct diag_page_code * pcdp; for (pcdp = out_dpc_arr; pcdp->desc; ++pcdp) { if (page_num == pcdp->page_code) return pcdp->desc; else if (page_num < pcdp->page_code) return NULL; } return NULL; } static bool match_ind_indiv(int index, const struct opts_t * op) { if (index == op->ind_indiv) return true; if (op->ind_indiv_last > op->ind_indiv) { if ((index > op->ind_indiv) && (index <= op->ind_indiv_last)) return true; } return false; } #if 0 static bool match_last_ind_indiv(int index, const struct opts_t * op) { if (op->ind_indiv_last >= op->ind_indiv) return (index == op->ind_indiv_last); return (index == op->ind_indiv); } #endif /* Return of 0 -> success, SG_LIB_CAT_* positive values or -1 -> other * failures */ static int do_senddiag(struct sg_pt_base * ptvp, void * outgoing_pg, int outgoing_len, bool noisy, int verbose) { int ret; if (outgoing_pg && (verbose > 2)) { int page_num = ((const char *)outgoing_pg)[0]; const char * cp = find_out_diag_page_desc(page_num); if (cp) pr2serr(" Send diagnostic command page name: %s\n", cp); else pr2serr(" Send diagnostic command page number: 0x%x\n", page_num); } ret = sg_ll_send_diag_pt(ptvp, 0 /* sf_code */, true /* pf_bit */, false /* sf_bit */, false /* devofl_bit */, false /* unitofl_bit */, 0 /* long_duration */, outgoing_pg, outgoing_len, noisy, verbose); clear_scsi_pt_obj(ptvp); return ret; } /* Fetch diagnostic page name (status and/or control). Returns NULL if not * found. */ static const char * find_diag_page_desc(int page_num) { const struct diag_page_code * pcdp; for (pcdp = dpc_arr; pcdp->desc; ++pcdp) { if (page_num == pcdp->page_code) return pcdp->desc; else if (page_num < pcdp->page_code) return NULL; } return NULL; } /* Fetch diagnostic page name (status or in). Returns NULL if not found. */ static const char * find_in_diag_page_desc(int page_num) { const struct diag_page_code * pcdp; for (pcdp = in_dpc_arr; pcdp->desc; ++pcdp) { if (page_num == pcdp->page_code) return pcdp->desc; else if (page_num < pcdp->page_code) return NULL; } return NULL; } /* Fetch element type name. Returns NULL if not found. */ static char * etype_str(int elem_type_code, char * b, int mlen_b) { const struct element_type_t * etp; int len; if ((NULL == b) || (mlen_b < 1)) return b; for (etp = element_type_arr; etp->desc; ++etp) { if (elem_type_code == etp->elem_type_code) { len = strlen(etp->desc); if (len < mlen_b) strcpy(b, etp->desc); else { strncpy(b, etp->desc, mlen_b - 1); b[mlen_b - 1] = '\0'; } return b; } else if (elem_type_code < etp->elem_type_code) break; } if (elem_type_code < 0x80) snprintf(b, mlen_b - 1, "[0x%x]", elem_type_code); else snprintf(b, mlen_b - 1, "vendor specific [0x%x]", elem_type_code); b[mlen_b - 1] = '\0'; return b; } /* Returns true if el_type (element type) is of interest to the Additional * Element Status page. Otherwise return false. */ static bool is_et_used_by_aes(int el_type) { if ((el_type >= 0) && (el_type < NUM_ACTIVE_ET_AESP_ARR)) return active_et_aesp_arr[el_type]; else return false; } #if 0 static struct join_row_t * find_join_row(struct th_es_t * tesp, int index, enum fj_select_t sel) { int k; struct join_row_t * jrp = tesp->j_base; if (index < 0) return NULL; switch (sel) { case FJ_IOE: /* index includes overall element */ if (index >= tesp->num_j_rows) return NULL; return jrp + index; case FJ_EOE: /* index excludes overall element */ if (index >= tesp->num_j_eoe) return NULL; for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) { if (index == jrp->ei_eoe) return jrp; } return NULL; case FJ_AESS: /* index includes only AES listed element types */ if (index >= tesp->num_j_eoe) return NULL; for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) { if (index == jrp->ei_aess) return jrp; } return NULL; case FJ_SAS_CON: /* index on non-overall SAS connector etype */ if (index >= tesp->num_j_rows) return NULL; for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) { if (SAS_CONNECTOR_ETC == jrp->etype) { if (index == jrp->indiv_i) return jrp; } } return NULL; default: pr2serr("%s: bad selector: %d\n", __func__, (int)sel); return NULL; } } #endif static const struct join_row_t * find_join_row_cnst(const struct th_es_t * tesp, int index, enum fj_select_t sel) { int k; const struct join_row_t * jrp = tesp->j_base; if (index < 0) return NULL; switch (sel) { case FJ_IOE: /* index includes overall element */ if (index >= tesp->num_j_rows) return NULL; return jrp + index; case FJ_EOE: /* index excludes overall element */ if (index >= tesp->num_j_eoe) return NULL; for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) { if (index == jrp->ei_eoe) return jrp; } return NULL; case FJ_AESS: /* index includes only AES listed element types */ if (index >= tesp->num_j_eoe) return NULL; for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) { if (index == jrp->ei_aess) return jrp; } return NULL; case FJ_SAS_CON: /* index on non-overall SAS connector etype */ if (index >= tesp->num_j_rows) return NULL; for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) { if (SAS_CONNECTOR_ETC == jrp->etype) { if (index == jrp->indiv_i) return jrp; } } return NULL; default: pr2serr("%s: bad selector: %d\n", __func__, (int)sel); return NULL; } } /* Return of 0 -> success, SG_LIB_CAT_* positive values or -2 if response * had bad format, -1 -> other failures */ static int do_rec_diag(struct sg_pt_base * ptvp, int page_code, uint8_t * rsp_buff, int rsp_buff_size, struct opts_t * op, int * rsp_lenp) { int k, d_len, rsp_len, res; int resid = 0; int vb = op->verbose; const char * cp; char b[80]; char bb[120]; static const char * rdr = "Receive diagnostic results"; memset(rsp_buff, 0, rsp_buff_size); if (rsp_lenp) *rsp_lenp = 0; if ((cp = find_in_diag_page_desc(page_code))) snprintf(bb, sizeof(bb), "%s dpage", cp); else snprintf(bb, sizeof(bb), "dpage 0x%x", page_code); cp = bb; if (op->data_arr && op->do_data) { /* user provided data */ /* N.B. First 4 bytes in data_arr are not used, user data was read in * starting at byte offset 4 */ bool found = false; int off = 0; const uint8_t * bp = op->data_arr + DATA_IN_OFF; const struct data_in_desc_t * didp = data_in_desc_arr; for (k = 0, d_len = 0; k < MX_DATA_IN_DESCS; ++k, ++didp) { if (! didp->in_use) break; if (page_code == didp->page_code) { off = didp->offset; d_len = didp->dp_len; found = true; break; } } if (found) memcpy(rsp_buff, bp + off, d_len); else { if (vb) pr2serr("%s: %s not found in user data\n", __func__, cp); return SG_LIB_CAT_OTHER; } cp = find_in_diag_page_desc(page_code); if (vb > 2) { pr2serr(" %s: response data from user", rdr); if (3 == vb) { pr2serr("%s:\n", (d_len > 256 ? ", first 256 bytes" : "")); hex2stderr(rsp_buff, (d_len > 256 ? 256 : d_len), -1); } else { pr2serr(":\n"); hex2stderr(rsp_buff, d_len, 0); } } res = 0; resid = rsp_buff_size - d_len; goto decode; /* step over the device access */ } if (vb > 1) pr2serr(" %s command for %s\n", rdr, cp); res = sg_ll_receive_diag_pt(ptvp, true /* pcv */, page_code, rsp_buff, rsp_buff_size, 0 /* default timeout */, &resid, ! op->quiet, vb); clear_scsi_pt_obj(ptvp); decode: if (0 == res) { rsp_len = sg_get_unaligned_be16(rsp_buff + 2) + 4; if (rsp_len > rsp_buff_size) { if (rsp_buff_size > 8) /* tried to get more than header */ pr2serr("<<< warning response buffer too small [was %d but " "need %d]>>>\n", rsp_buff_size, rsp_len); if (resid > 0) rsp_buff_size -= resid; } else if (resid > 0) rsp_buff_size -= resid; rsp_len = (rsp_len < rsp_buff_size) ? rsp_len : rsp_buff_size; if (rsp_len < 0) { pr2serr("<<< warning: resid=%d too large, implies negative " "reply length: %d\n", resid, rsp_len); rsp_len = 0; } if (rsp_lenp) *rsp_lenp = rsp_len; if ((rsp_len > 1) && (page_code != rsp_buff[0])) { if ((0x9 == rsp_buff[0]) && (1 & rsp_buff[1])) { pr2serr("Enclosure busy, try again later\n"); if (op->do_hex) hex2stderr(rsp_buff, rsp_len, 0); } else if (0x8 == rsp_buff[0]) { pr2serr("Enclosure only supports Short Enclosure Status: " "0x%x\n", rsp_buff[1]); } else { pr2serr("Invalid response, wanted page code: 0x%x but got " "0x%x\n", page_code, rsp_buff[0]); hex2stderr(rsp_buff, rsp_len, 0); } return -2; } return 0; } else if (vb) { pr2serr("Attempt to fetch %s failed\n", cp); sg_get_category_sense_str(res, sizeof(b), b, op->verbose); pr2serr(" %s\n", b); } return res; } #if 1 static void dStrRaw(const uint8_t * str, int len) { int k; for (k = 0; k < len; ++k) printf("%c", str[k]); } #else static void dStrRaw(const uint8_t * str, int len) { int res, err; if (len > 0) { res = write(fileno(stdout), str, len); if (res < 0) { err = errno; pr2serr("%s: write to stdout failed: %s [%d]\n", __func__, strerror(err), err); } } } #endif /* CONFIGURATION_DPC [0x1] * Display Configuration diagnostic page. */ static void configuration_sdg(const uint8_t * resp, int resp_len) { int j, k, el, num_subs, sum_elem_types; uint32_t gen_code; const uint8_t * bp; const uint8_t * last_bp; const uint8_t * text_bp; char b[64]; printf("Configuration diagnostic page:\n"); if (resp_len < 4) goto truncated; num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */ sum_elem_types = 0; last_bp = resp + resp_len - 1; printf(" number of secondary subenclosures: %d\n", num_subs - 1); gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%" PRIx32 "\n", gen_code); bp = resp + 8; printf(" enclosure descriptor list\n"); for (k = 0; k < num_subs; ++k, bp += el) { if ((bp + 3) > last_bp) goto truncated; el = bp[3] + 4; sum_elem_types += bp[2]; printf(" Subenclosure identifier: %d%s\n", bp[1], (bp[1] ? "" : " [primary]")); printf(" relative ES process id: %d, number of ES processes" ": %d\n", ((bp[0] & 0x70) >> 4), (bp[0] & 0x7)); printf(" number of type descriptor headers: %d\n", bp[2]); if (el < 40) { pr2serr(" enc descriptor len=%d ??\n", el); continue; } printf(" enclosure logical identifier (hex): "); for (j = 0; j < 8; ++j) printf("%02x", bp[4 + j]); printf("\n enclosure vendor: %.8s product: %.16s rev: %.4s\n", bp + 12, bp + 20, bp + 36); if (el > 40) { char bb[1024]; printf(" vendor-specific data:\n"); hex2str(bp + 40, el - 40, " ", 0, sizeof(bb), bb); printf("%s\n", bb); } } /* printf("\n"); */ printf(" type descriptor header and text list\n"); text_bp = bp + (sum_elem_types * 4); for (k = 0; k < sum_elem_types; ++k, bp += 4) { if ((bp + 3) > last_bp) goto truncated; printf(" Element type: %s, subenclosure id: %d\n", etype_str(bp[0], b, sizeof(b)), bp[2]); printf(" number of possible elements: %d\n", bp[1]); if (bp[3] > 0) { if (text_bp > last_bp) goto truncated; printf(" text: %.*s\n", bp[3], text_bp); text_bp += bp[3]; } } return; truncated: pr2serr(" <<>>\n"); return; } /* CONFIGURATION_DPC [0x1] read and used to build array pointed to by * 'tdhp' with no more than 'max_elems' elements. If 'generationp' is non * NULL then writes generation code where it points. if 'primary_ip" is * non NULL the writes rimary enclosure info where it points. * Returns total number of type descriptor headers written to 'tdhp' or -1 * if there is a problem */ static int build_type_desc_hdr_arr(struct sg_pt_base * ptvp, struct type_desc_hdr_t * tdhp, int max_elems, uint32_t * generationp, struct enclosure_info * primary_ip, struct opts_t * op) { int resp_len, k, el, num_subs, sum_type_dheaders, res, n; int ret = 0; uint32_t gen_code; const uint8_t * bp; const uint8_t * last_bp; if (NULL == config_dp_resp) { config_dp_resp = sg_memalign(op->maxlen, 0, &free_config_dp_resp, false); if (NULL == config_dp_resp) { pr2serr("%s: unable to allocate %d bytes on heap\n", __func__, op->maxlen); ret = -1; goto the_end; } res = do_rec_diag(ptvp, CONFIGURATION_DPC, config_dp_resp, op->maxlen, op, &resp_len); if (res) { pr2serr("%s: couldn't read config page, res=%d\n", __func__, res); ret = -1; free(free_config_dp_resp); free_config_dp_resp = NULL; goto the_end; } if (resp_len < 4) { ret = -1; free(free_config_dp_resp); free_config_dp_resp = NULL; goto the_end; } config_dp_resp_len = resp_len; } else resp_len = config_dp_resp_len; num_subs = config_dp_resp[1] + 1; sum_type_dheaders = 0; last_bp = config_dp_resp + resp_len - 1; gen_code = sg_get_unaligned_be32(config_dp_resp + 4); if (generationp) *generationp = gen_code; bp = config_dp_resp + 8; for (k = 0; k < num_subs; ++k, bp += el) { if ((bp + 3) > last_bp) goto p_truncated; el = bp[3] + 4; sum_type_dheaders += bp[2]; if (el < 40) { pr2serr("%s: short enc descriptor len=%d ??\n", __func__, el); continue; } if ((0 == k) && primary_ip) { ++primary_ip->have_info; primary_ip->rel_esp_id = (bp[0] & 0x70) >> 4; primary_ip->num_esp = (bp[0] & 0x7); memcpy(primary_ip->enc_log_id, bp + 4, 8); memcpy(primary_ip->enc_vendor_id, bp + 12, 8); memcpy(primary_ip->product_id, bp + 20, 16); memcpy(primary_ip->product_rev_level, bp + 36, 4); } } for (k = 0; k < sum_type_dheaders; ++k, bp += 4) { if ((bp + 3) > last_bp) goto p_truncated; if (k >= max_elems) { pr2serr("%s: too many elements\n", __func__); ret = -1; goto the_end; } tdhp[k].etype = bp[0]; tdhp[k].num_elements = bp[1]; tdhp[k].se_id = bp[2]; tdhp[k].txt_len = bp[3]; } if (op->ind_given && op->ind_etp) { n = op->ind_et_inst; for (k = 0; k < sum_type_dheaders; ++k) { if (op->ind_etp->elem_type_code == tdhp[k].etype) { if (0 == n) break; else --n; } } if (k < sum_type_dheaders) op->ind_th = k; else { if (op->ind_et_inst) pr2serr("%s: unable to find element type '%s%d'\n", __func__, op->ind_etp->abbrev, op->ind_et_inst); else pr2serr("%s: unable to find element type '%s'\n", __func__, op->ind_etp->abbrev); ret = -1; goto the_end; } } ret = sum_type_dheaders; goto the_end; p_truncated: pr2serr("%s: config too short\n", __func__); ret = -1; the_end: if (0 == ret) ++type_desc_hdr_count; return ret; } static char * find_sas_connector_type(int conn_type, bool abridged, char * buff, int buff_len) { switch (conn_type) { case 0x0: snprintf(buff, buff_len, "No information"); break; case 0x1: if (abridged) snprintf(buff, buff_len, "SAS 4x"); else snprintf(buff, buff_len, "SAS 4x receptacle (SFF-8470) " "[max 4 phys]"); break; case 0x2: if (abridged) snprintf(buff, buff_len, "Mini SAS 4x"); else snprintf(buff, buff_len, "Mini SAS 4x receptacle (SFF-8088) " "[max 4 phys]"); break; case 0x3: if (abridged) snprintf(buff, buff_len, "QSFP+"); else snprintf(buff, buff_len, "QSFP+ receptacle (SFF-8436) " "[max 4 phys]"); break; case 0x4: if (abridged) snprintf(buff, buff_len, "Mini SAS 4x active"); else snprintf(buff, buff_len, "Mini SAS 4x active receptacle " "(SFF-8088) [max 4 phys]"); break; case 0x5: if (abridged) snprintf(buff, buff_len, "Mini SAS HD 4x"); else snprintf(buff, buff_len, "Mini SAS HD 4x receptacle (SFF-8644) " "[max 4 phys]"); break; case 0x6: if (abridged) snprintf(buff, buff_len, "Mini SAS HD 8x"); else snprintf(buff, buff_len, "Mini SAS HD 8x receptacle (SFF-8644) " "[max 8 phys]"); break; case 0x7: if (abridged) snprintf(buff, buff_len, "Mini SAS HD 16x"); else snprintf(buff, buff_len, "Mini SAS HD 16x receptacle (SFF-8644) " "[max 16 phys]"); break; case 0xf: snprintf(buff, buff_len, "Vendor specific"); break; case 0x10: if (abridged) snprintf(buff, buff_len, "SAS 4i"); else snprintf(buff, buff_len, "SAS 4i plug (SFF-8484) [max 4 phys]"); break; case 0x11: if (abridged) snprintf(buff, buff_len, "Mini SAS 4i"); else snprintf(buff, buff_len, "Mini SAS 4i receptacle (SFF-8087) " "[max 4 phys]"); break; case 0x12: if (abridged) snprintf(buff, buff_len, "Mini SAS HD 4i"); else snprintf(buff, buff_len, "Mini SAS HD 4i receptacle (SFF-8643) " "[max 4 phys]"); break; case 0x13: if (abridged) snprintf(buff, buff_len, "Mini SAS HD 8i"); else snprintf(buff, buff_len, "Mini SAS HD 8i receptacle (SFF-8643) " "[max 8 phys]"); break; case 0x14: if (abridged) snprintf(buff, buff_len, "Mini SAS HD 16i"); else snprintf(buff, buff_len, "Mini SAS HD 16i receptacle (SFF-8643) " "[max 16 phys]"); break; case 0x15: if (abridged) snprintf(buff, buff_len, "SlimSAS 4i"); /* was "SAS SlimLine" */ else snprintf(buff, buff_len, "SlimSAS 4i (SFF-8654) [max 4 phys]"); break; case 0x16: if (abridged) snprintf(buff, buff_len, "SlimSAS 8i"); /* was "SAS SlimLine" */ else snprintf(buff, buff_len, "SlimSAS 8i (SFF-8654) [max 8 phys]"); break; case 0x17: if (abridged) snprintf(buff, buff_len, "SAS MiniLink 4i"); else snprintf(buff, buff_len, "SAS MiniLink 4i (SFF-8612) " "[max 4 phys]"); break; case 0x18: if (abridged) snprintf(buff, buff_len, "SAS MiniLink 8i"); else snprintf(buff, buff_len, "SAS MiniLink 8i (SFF-8612) " "[max 8 phys]"); break; case 0x20: if (abridged) snprintf(buff, buff_len, "SAS Drive backplane"); else snprintf(buff, buff_len, "SAS Drive backplane receptacle " "(SFF-8482) [max 2 phys]"); break; case 0x21: if (abridged) snprintf(buff, buff_len, "SATA host plug"); else snprintf(buff, buff_len, "SATA host plug [max 1 phy]"); break; case 0x22: if (abridged) snprintf(buff, buff_len, "SAS Drive plug"); else snprintf(buff, buff_len, "SAS Drive plug (SFF-8482) " "[max 2 phys]"); break; case 0x23: if (abridged) snprintf(buff, buff_len, "SATA device plug"); else snprintf(buff, buff_len, "SATA device plug [max 1 phy]"); break; case 0x24: if (abridged) snprintf(buff, buff_len, "Micro SAS receptacle"); else snprintf(buff, buff_len, "Micro SAS receptacle [max 2 phys]"); break; case 0x25: if (abridged) snprintf(buff, buff_len, "Micro SATA device plug"); else snprintf(buff, buff_len, "Micro SATA device plug [max 1 phy]"); break; case 0x26: if (abridged) snprintf(buff, buff_len, "Micro SAS plug"); else snprintf(buff, buff_len, "Micro SAS plug (SFF-8486) [max 2 " "phys]"); break; case 0x27: if (abridged) snprintf(buff, buff_len, "Micro SAS/SATA plug"); else snprintf(buff, buff_len, "Micro SAS/SATA plug (SFF-8486) " "[max 2 phys]"); break; case 0x28: if (abridged) snprintf(buff, buff_len, "12 Gb/s SAS drive backplane"); else snprintf(buff, buff_len, "12 Gb/s SAS drive backplane receptacle " "(SFF-8680) [max 2 phys]"); break; case 0x29: if (abridged) snprintf(buff, buff_len, "12 Gb/s SAS drive plug"); else snprintf(buff, buff_len, "12 Gb/s SAS drive plug (SFF-8680) " "[max 2 phys]"); break; case 0x2a: if (abridged) snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x receptacle"); else snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x unshielded " "receptacle (SFF-8639)"); break; case 0x2b: if (abridged) snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x plug"); else snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x unshielded " "plug (SFF-8639)"); break; case 0x2c: if (abridged) snprintf(buff, buff_len, "SAS MultiLink Drive backplane " "receptacle"); else snprintf(buff, buff_len, "SAS MultiLink Drive backplane " "receptacle (SFF-8630)"); break; case 0x2d: if (abridged) snprintf(buff, buff_len, "SAS MultiLink Drive backplane plug"); else snprintf(buff, buff_len, "SAS MultiLink Drive backplane plug " "(SFF-8630)"); break; case 0x2e: if (abridged) snprintf(buff, buff_len, "Reserved"); else snprintf(buff, buff_len, "Reserved for internal connectors to " "end device"); break; case 0x2f: if (abridged) snprintf(buff, buff_len, "SAS virtual connector"); else snprintf(buff, buff_len, "SAS virtual connector [max 1 phy]"); break; case 0x3f: if (abridged) snprintf(buff, buff_len, "VS internal connector"); else snprintf(buff, buff_len, "Vendor specific internal connector"); break; case 0x40: if (abridged) snprintf(buff, buff_len, "SAS high density drive backplane " "receptacle"); else snprintf(buff, buff_len, "SAS high density drive backplane " "receptacle (SFF-8631) [max 8 phys]"); break; case 0x41: if (abridged) snprintf(buff, buff_len, "SAS high density drive backplane " "plug"); else snprintf(buff, buff_len, "SAS high density drive backplane " "plug (SFF-8631) [max 8 phys]"); break; default: if (conn_type < 0x10) snprintf(buff, buff_len, "unknown external connector type: 0x%x", conn_type); else if (conn_type < 0x20) snprintf(buff, buff_len, "unknown internal wide connector type: " "0x%x", conn_type); else if (conn_type < 0x3f) snprintf(buff, buff_len, "reserved for internal connector, " "type: 0x%x", conn_type); else if (conn_type < 0x70) snprintf(buff, buff_len, "reserved connector type: 0x%x", conn_type); else if (conn_type < 0x80) snprintf(buff, buff_len, "vendor specific connector type: 0x%x", conn_type); else /* conn_type is a 7 bit field, so this is impossible */ snprintf(buff, buff_len, "unexpected connector type: 0x%x", conn_type); break; } return buff; } /* 'Fan speed factor' new in ses4r04 */ static int calc_fan_speed(int fan_speed_factor, int actual_fan_speed) { switch (fan_speed_factor) { case 0: return actual_fan_speed * 10; case 1: return (actual_fan_speed * 10) + 20480; case 2: return actual_fan_speed * 100; default: break; } return -1; /* something is wrong */ } static const char * elem_status_code_desc[] = { "Unsupported", "OK", "Critical", "Noncritical", "Unrecoverable", "Not installed", "Unknown", "Not available", "No access allowed", "reserved [9]", "reserved [10]", "reserved [11]", "reserved [12]", "reserved [13]", "reserved [14]", "reserved [15]", }; static const char * actual_speed_desc[] = { "stopped", "at lowest speed", "at second lowest speed", "at third lowest speed", "at intermediate speed", "at third highest speed", "at second highest speed", "at highest speed" }; static const char * nv_cache_unit[] = { "Bytes", "KiB", "MiB", "GiB" }; static const char * invop_type_desc[] = { "SEND DIAGNOSTIC page code error", "SEND DIAGNOSTIC page format error", "Reserved", "Vendor specific error" }; static void enc_status_helper(const char * pad, const uint8_t * statp, int etype, bool abridged, const struct opts_t * op) { int res, a, b, ct, bblen; bool nofilter = ! op->do_filter; char bb[128]; if (op->inner_hex) { printf("%s%02x %02x %02x %02x\n", pad, statp[0], statp[1], statp[2], statp[3]); return; } if (! abridged) printf("%sPredicted failure=%d, Disabled=%d, Swap=%d, status: %s\n", pad, !!(statp[0] & 0x40), !!(statp[0] & 0x20), !!(statp[0] & 0x10), elem_status_code_desc[statp[0] & 0xf]); switch (etype) { /* element types */ case UNSPECIFIED_ETC: if (op->verbose) printf("%sstatus in hex: %02x %02x %02x %02x\n", pad, statp[0], statp[1], statp[2], statp[3]); break; case DEVICE_ETC: if (ARRAY_STATUS_DPC == op->page_code) { /* obsolete after SES-1 */ if (nofilter || (0xf0 & statp[1])) printf("%sOK=%d, Reserved device=%d, Hot spare=%d, Cons " "check=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[1] & 0x20), !!(statp[1] & 0x10)); if (nofilter || (0xf & statp[1])) printf("%sIn crit array=%d, In failed array=%d, Rebuild/" "remap=%d, R/R abort=%d\n", pad, !!(statp[1] & 0x8), !!(statp[1] & 0x4), !!(statp[1] & 0x2), !!(statp[1] & 0x1)); if (nofilter || ((0x46 & statp[2]) || (0x8 & statp[3]))) printf("%sDo not remove=%d, RMV=%d, Ident=%d, Enable bypass " "A=%d\n", pad, !!(statp[2] & 0x40), !!(statp[2] & 0x4), !!(statp[2] & 0x2), !!(statp[3] & 0x8)); if (nofilter || (0x7 & statp[3])) printf("%sEnable bypass B=%d, Bypass A enabled=%d, Bypass B " "enabled=%d\n", pad, !!(statp[3] & 0x4), !!(statp[3] & 0x2), !!(statp[3] & 0x1)); break; } printf("%sSlot address: %d\n", pad, statp[1]); if (nofilter || (0xe0 & statp[2])) printf("%sApp client bypassed A=%d, Do not remove=%d, Enc " "bypassed A=%d\n", pad, !!(statp[2] & 0x80), !!(statp[2] & 0x40), !!(statp[2] & 0x20)); if (nofilter || (0x1c & statp[2])) printf("%sEnc bypassed B=%d, Ready to insert=%d, RMV=%d, Ident=" "%d\n", pad, !!(statp[2] & 0x10), !!(statp[2] & 0x8), !!(statp[2] & 0x4), !!(statp[2] & 0x2)); if (nofilter || ((1 & statp[2]) || (0xe0 & statp[3]))) printf("%sReport=%d, App client bypassed B=%d, Fault sensed=%d, " "Fault requested=%d\n", pad, !!(statp[2] & 0x1), !!(statp[3] & 0x80), !!(statp[3] & 0x40), !!(statp[3] & 0x20)); if (nofilter || (0x1e & statp[3])) printf("%sDevice off=%d, Bypassed A=%d, Bypassed B=%d, Device " "bypassed A=%d\n", pad, !!(statp[3] & 0x10), !!(statp[3] & 0x8), !!(statp[3] & 0x4), !!(statp[3] & 0x2)); if (nofilter || (0x1 & statp[3])) printf("%sDevice bypassed B=%d\n", pad, !!(statp[3] & 0x1)); break; case POWER_SUPPLY_ETC: if (nofilter || ((0xc0 & statp[1]) || (0xc & statp[2]))) { printf("%sIdent=%d, Do not remove=%d, DC overvoltage=%d, " "DC undervoltage=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[2] & 0x8), !!(statp[2] & 0x4)); } if (nofilter || ((0x2 & statp[2]) || (0xf0 & statp[3]))) printf("%sDC overcurrent=%d, Hot swap=%d, Fail=%d, Requested " "on=%d, Off=%d\n", pad, !!(statp[2] & 0x2), !!(statp[3] & 0x80), !!(statp[3] & 0x40), !!(statp[3] & 0x20), !!(statp[3] & 0x10)); if (nofilter || (0xf & statp[3])) printf("%sOvertmp fail=%d, Temperature warn=%d, AC fail=%d, " "DC fail=%d\n", pad, !!(statp[3] & 0x8), !!(statp[3] & 0x4), !!(statp[3] & 0x2), !!(statp[3] & 0x1)); break; case COOLING_ETC: if (nofilter || ((0xc0 & statp[1]) || (0xf0 & statp[3]))) printf("%sIdent=%d, Do not remove=%d, Hot swap=%d, Fail=%d, " "Requested on=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[3] & 0x80), !!(statp[3] & 0x40), !!(statp[3] & 0x20)); printf("%sOff=%d, Actual speed=%d rpm, Fan %s\n", pad, !!(statp[3] & 0x10), calc_fan_speed((statp[1] >> 3) & 0x3, ((0x7 & statp[1]) << 8) + statp[2]), actual_speed_desc[7 & statp[3]]); if (op->verbose > 1) /* show real field values */ printf("%s [Fan_speed_factor=%d, Actual_fan_speed=%d]\n", pad, (statp[1] >> 3) & 0x3, ((0x7 & statp[1]) << 8) + statp[2]); break; case TEMPERATURE_ETC: /* temperature sensor */ if (nofilter || ((0xc0 & statp[1]) || (0xf & statp[3]))) { printf("%sIdent=%d, Fail=%d, OT failure=%d, OT warning=%d, " "UT failure=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[3] & 0x8), !!(statp[3] & 0x4), !!(statp[3] & 0x2)); printf("%sUT warning=%d\n", pad, !!(statp[3] & 0x1)); } if (statp[2]) printf("%sTemperature=%d C\n", pad, (int)statp[2] - TEMPERAT_OFF); else printf("%sTemperature: \n", pad); break; case DOOR_ETC: /* OPEN field added in ses3r05 */ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[3]))) printf("%sIdent=%d, Fail=%d, Open=%d, Unlock=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[3] & 0x2), !!(statp[3] & 0x1)); break; case AUD_ALARM_ETC: /* audible alarm */ if (nofilter || ((0xc0 & statp[1]) || (0xd0 & statp[3]))) printf("%sIdent=%d, Fail=%d, Request mute=%d, Mute=%d, " "Remind=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[3] & 0x80), !!(statp[3] & 0x40), !!(statp[3] & 0x10)); if (nofilter || (0xf & statp[3])) printf("%sTone indicator: Info=%d, Non-crit=%d, Crit=%d, " "Unrecov=%d\n", pad, !!(statp[3] & 0x8), !!(statp[3] & 0x4), !!(statp[3] & 0x2), !!(statp[3] & 0x1)); break; case ENC_SCELECTR_ETC: /* enclosure services controller electronics */ if (nofilter || (0xe0 & statp[1]) || (0x1 & statp[2]) || (0x80 & statp[3])) printf("%sIdent=%d, Fail=%d, Do not remove=%d, Report=%d, " "Hot swap=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[1] & 0x20), !!(statp[2] & 0x1), !!(statp[3] & 0x80)); break; case SCC_CELECTR_ETC: /* SCC controller electronics */ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[2]))) printf("%sIdent=%d, Fail=%d, Report=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[2] & 0x1)); break; case NV_CACHE_ETC: /* Non volatile cache */ res = sg_get_unaligned_be16(statp + 2); printf("%sIdent=%d, Fail=%d, Size multiplier=%d, Non volatile cache " "size=0x%x\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), (statp[1] & 0x3), res); printf("%sHence non volatile cache size: %d %s\n", pad, res, nv_cache_unit[statp[1] & 0x3]); break; case INV_OP_REASON_ETC: /* Invalid operation reason */ res = ((statp[1] >> 6) & 3); printf("%sInvop type=%d %s\n", pad, res, invop_type_desc[res]); switch (res) { case 0: printf("%sPage not supported=%d\n", pad, (statp[1] & 1)); break; case 1: printf("%sByte offset=%d, bit number=%d\n", pad, sg_get_unaligned_be16(statp + 2), (statp[1] & 7)); break; case 2: case 3: printf("%slast 3 bytes (hex): %02x %02x %02x\n", pad, statp[1], statp[2], statp[3]); break; } break; case UI_POWER_SUPPLY_ETC: /* Uninterruptible power supply */ if (0 == statp[1]) printf("%sBattery status: discharged or unknown\n", pad); else if (255 == statp[1]) printf("%sBattery status: 255 or more minutes remaining\n", pad); else printf("%sBattery status: %d minutes remaining\n", pad, statp[1]); if (nofilter || (0xf8 & statp[2])) printf("%sAC low=%d, AC high=%d, AC qual=%d, AC fail=%d, DC fail=" "%d\n", pad, !!(statp[2] & 0x80), !!(statp[2] & 0x40), !!(statp[2] & 0x20), !!(statp[2] & 0x10), !!(statp[2] & 0x8)); if (nofilter || ((0x7 & statp[2]) || (0xe3 & statp[3]))) { printf("%sUPS fail=%d, Warn=%d, Intf fail=%d, Ident=%d, Fail=%d, " "Do not remove=%d\n", pad, !!(statp[2] & 0x4), !!(statp[2] & 0x2), !!(statp[2] & 0x1), !!(statp[3] & 0x80), !!(statp[3] & 0x40), !!(statp[3] & 0x20)); printf("%sBatt fail=%d, BPF=%d\n", pad, !!(statp[3] & 0x2), !!(statp[3] & 0x1)); } break; case DISPLAY_ETC: /* Display (ses2r15) */ if (nofilter || (0xc0 & statp[1])) { int dms = statp[1] & 0x3; printf("%sIdent=%d, Fail=%d, Display mode status=%d", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), dms); if ((1 == dms) || (2 == dms)) { uint16_t dcs = sg_get_unaligned_be16(statp + 2); printf(", Display character status=0x%x", dcs); if (statp[2] && (0 == statp[3])) printf(" ['%c']", statp[2]); } printf("\n"); } break; case KEY_PAD_ETC: /* Key pad entry */ if (nofilter || (0xc0 & statp[1])) printf("%sIdent=%d, Fail=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40)); break; case ENCLOSURE_ETC: a = ((statp[2] >> 2) & 0x3f); if (nofilter || ((0x80 & statp[1]) || a || (0x2 & statp[2]))) printf("%sIdent=%d, Time until power cycle=%d, " "Failure indication=%d\n", pad, !!(statp[1] & 0x80), a, !!(statp[2] & 0x2)); b = ((statp[3] >> 2) & 0x3f); if (nofilter || (0x1 & statp[2]) || a || b) printf("%sWarning indication=%d, Requested power off " "duration=%d\n", pad, !!(statp[2] & 0x1), b); if (nofilter || (0x3 & statp[3])) printf("%sFailure requested=%d, Warning requested=%d\n", pad, !!(statp[3] & 0x2), !!(statp[3] & 0x1)); break; case SCSI_PORT_TRAN_ETC: /* SCSI port/transceiver */ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[2]) || (0x13 & statp[3]))) printf("%sIdent=%d, Fail=%d, Report=%d, Disabled=%d, Loss of " "link=%d, Xmit fail=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[2] & 0x1), !!(statp[3] & 0x10), !!(statp[3] & 0x2), !!(statp[3] & 0x1)); break; case LANGUAGE_ETC: printf("%sIdent=%d, Language code: %.2s\n", pad, !!(statp[1] & 0x80), statp + 2); break; case COMM_PORT_ETC: /* Communication port */ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[3]))) printf("%sIdent=%d, Fail=%d, Disabled=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[3] & 0x1)); break; case VOLT_SENSOR_ETC: /* Voltage sensor */ if (nofilter || (0xcf & statp[1])) { printf("%sIdent=%d, Fail=%d, Warn Over=%d, Warn Under=%d, " "Crit Over=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[1] & 0x8), !!(statp[1] & 0x4), !!(statp[1] & 0x2)); printf("%sCrit Under=%d\n", pad, !!(statp[1] & 0x1)); } #ifdef SG_LIB_MINGW printf("%sVoltage: %g volts\n", pad, ((int)(short)sg_get_unaligned_be16(statp + 2) / 100.0)); #else printf("%sVoltage: %.2f volts\n", pad, ((int)(short)sg_get_unaligned_be16(statp + 2) / 100.0)); #endif break; case CURR_SENSOR_ETC: /* Current sensor */ if (nofilter || (0xca & statp[1])) printf("%sIdent=%d, Fail=%d, Warn Over=%d, Crit Over=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[1] & 0x8), !!(statp[1] & 0x2)); #ifdef SG_LIB_MINGW printf("%sCurrent: %g amps\n", pad, ((int)(short)sg_get_unaligned_be16(statp + 2) / 100.0)); #else printf("%sCurrent: %.2f amps\n", pad, ((int)(short)sg_get_unaligned_be16(statp + 2) / 100.0)); #endif break; case SCSI_TPORT_ETC: /* SCSI target port */ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[2]) || (0x1 & statp[3]))) printf("%sIdent=%d, Fail=%d, Report=%d, Enabled=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[2] & 0x1), !!(statp[3] & 0x1)); break; case SCSI_IPORT_ETC: /* SCSI initiator port */ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[2]) || (0x1 & statp[3]))) printf("%sIdent=%d, Fail=%d, Report=%d, Enabled=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[2] & 0x1), !!(statp[3] & 0x1)); break; case SIMPLE_SUBENC_ETC: /* Simple subenclosure */ printf("%sIdent=%d, Fail=%d, Short enclosure status: 0x%x\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), statp[3]); break; case ARRAY_DEV_ETC: /* Array device */ if (nofilter || (0xf0 & statp[1])) printf("%sOK=%d, Reserved device=%d, Hot spare=%d, Cons check=" "%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40), !!(statp[1] & 0x20), !!(statp[1] & 0x10)); if (nofilter || (0xf & statp[1])) printf("%sIn crit array=%d, In failed array=%d, Rebuild/remap=%d" ", R/R abort=%d\n", pad, !!(statp[1] & 0x8), !!(statp[1] & 0x4), !!(statp[1] & 0x2), !!(statp[1] & 0x1)); if (nofilter || (0xf0 & statp[2])) printf("%sApp client bypass A=%d, Do not remove=%d, Enc bypass " "A=%d, Enc bypass B=%d\n", pad, !!(statp[2] & 0x80), !!(statp[2] & 0x40), !!(statp[2] & 0x20), !!(statp[2] & 0x10)); if (nofilter || (0xf & statp[2])) printf("%sReady to insert=%d, RMV=%d, Ident=%d, Report=%d\n", pad, !!(statp[2] & 0x8), !!(statp[2] & 0x4), !!(statp[2] & 0x2), !!(statp[2] & 0x1)); if (nofilter || (0xf0 & statp[3])) printf("%sApp client bypass B=%d, Fault sensed=%d, Fault reqstd=" "%d, Device off=%d\n", pad, !!(statp[3] & 0x80), !!(statp[3] & 0x40), !!(statp[3] & 0x20), !!(statp[3] & 0x10)); if (nofilter || (0xf & statp[3])) printf("%sBypassed A=%d, Bypassed B=%d, Dev bypassed A=%d, " "Dev bypassed B=%d\n", pad, !!(statp[3] & 0x8), !!(statp[3] & 0x4), !!(statp[3] & 0x2), !!(statp[3] & 0x1)); break; case SAS_EXPANDER_ETC: printf("%sIdent=%d, Fail=%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40)); break; case SAS_CONNECTOR_ETC: /* OC (overcurrent) added in ses3r07 */ ct = (statp[1] & 0x7f); bblen = sizeof(bb); if (abridged) printf("%s%s, pl=%d", pad, find_sas_connector_type(ct, true, bb, bblen), statp[2]); else { printf("%sIdent=%d, %s\n", pad, !!(statp[1] & 0x80), find_sas_connector_type(ct, false, bb, bblen)); /* Mated added in ses3r10 */ printf("%sConnector physical link=0x%x, Mated=%d, Fail=%d, " "OC=%d\n", pad, statp[2], !!(statp[3] & 0x80), !!(statp[3] & 0x40), !!(statp[3] & 0x20)); } break; default: if (etype < 0x80) printf("%sUnknown element type, status in hex: %02x %02x %02x " "%02x\n", pad, statp[0], statp[1], statp[2], statp[3]); else printf("%sVendor specific element type, status in hex: %02x " "%02x %02x %02x\n", pad, statp[0], statp[1], statp[2], statp[3]); break; } } /* ENC_STATUS_DPC [0x2] * Display enclosure status diagnostic page. */ static void enc_status_dp(const struct th_es_t * tesp, uint32_t ref_gen_code, const uint8_t * resp, int resp_len, const struct opts_t * op) { int j, k; uint32_t gen_code; bool got1, match_ind_th; const uint8_t * bp; const uint8_t * last_bp; const struct type_desc_hdr_t * tdhp = tesp->th_base; char b[64]; printf("Enclosure Status diagnostic page:\n"); if (resp_len < 4) goto truncated; printf(" INVOP=%d, INFO=%d, NON-CRIT=%d, CRIT=%d, UNRECOV=%d\n", !!(resp[1] & 0x10), !!(resp[1] & 0x8), !!(resp[1] & 0x4), !!(resp[1] & 0x2), !!(resp[1] & 0x1)); last_bp = resp + resp_len - 1; if (resp_len < 8) goto truncated; gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%x\n", gen_code); if (ref_gen_code != gen_code) { pr2serr(" <>\n"); return; } printf(" status descriptor list\n"); bp = resp + 8; for (k = 0, got1 = false; k < tesp->num_ths; ++k, ++tdhp) { if ((bp + 3) > last_bp) goto truncated; match_ind_th = (op->ind_given && (k == op->ind_th)); if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) { printf(" Element type: %s, subenclosure id: %d [ti=%d]\n", etype_str(tdhp->etype, b, sizeof(b)), tdhp->se_id, k); printf(" Overall descriptor:\n"); enc_status_helper(" ", bp, tdhp->etype, false, op); got1 = true; } for (bp += 4, j = 0; j < tdhp->num_elements; ++j, bp += 4) { if (op->ind_given) { if ((! match_ind_th) || (-1 == op->ind_indiv) || (! match_ind_indiv(j, op))) continue; } printf(" Element %d descriptor:\n", j); enc_status_helper(" ", bp, tdhp->etype, false, op); got1 = true; } } if (op->ind_given && (! got1)) { printf(" >>> no match on --index=%d,%d", op->ind_th, op->ind_indiv); if (op->ind_indiv_last > op->ind_indiv) printf("-%d\n", op->ind_indiv_last); else printf("\n"); } return; truncated: pr2serr(" <<>>\n"); return; } /* ARRAY_STATUS_DPC [0x6] * Display array status diagnostic page. */ static void array_status_dp(const struct th_es_t * tesp, uint32_t ref_gen_code, const uint8_t * resp, int resp_len, const struct opts_t * op) { int j, k; uint32_t gen_code; bool got1, match_ind_th; const uint8_t * bp; const uint8_t * last_bp; const struct type_desc_hdr_t * tdhp = tesp->th_base; char b[64]; printf("Array Status diagnostic page:\n"); if (resp_len < 4) goto truncated; printf(" INVOP=%d, INFO=%d, NON-CRIT=%d, CRIT=%d, UNRECOV=%d\n", !!(resp[1] & 0x10), !!(resp[1] & 0x8), !!(resp[1] & 0x4), !!(resp[1] & 0x2), !!(resp[1] & 0x1)); last_bp = resp + resp_len - 1; if (resp_len < 8) goto truncated; gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%x\n", gen_code); if (ref_gen_code != gen_code) { pr2serr(" <>\n"); return; } printf(" status descriptor list\n"); bp = resp + 8; for (k = 0, got1 = false; k < tesp->num_ths; ++k, ++tdhp) { if ((bp + 3) > last_bp) goto truncated; match_ind_th = (op->ind_given && (k == op->ind_th)); if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) { printf(" Element type: %s, subenclosure id: %d [ti=%d]\n", etype_str(tdhp->etype, b, sizeof(b)), tdhp->se_id, k); printf(" Overall descriptor:\n"); enc_status_helper(" ", bp, tdhp->etype, false, op); got1 = true; } for (bp += 4, j = 0; j < tdhp->num_elements; ++j, bp += 4) { if (op->ind_given) { if ((! match_ind_th) || (-1 == op->ind_indiv) || (! match_ind_indiv(j, op))) continue; } printf(" Element %d descriptor:\n", j); enc_status_helper(" ", bp, tdhp->etype, false, op); got1 = true; } } if (op->ind_given && (! got1)) { printf(" >>> no match on --index=%d,%d", op->ind_th, op->ind_indiv); if (op->ind_indiv_last > op->ind_indiv) printf("-%d\n", op->ind_indiv_last); else printf("\n"); } return; truncated: pr2serr(" <<>>\n"); return; } static char * reserved_or_num(char * buff, int buff_len, int num, int reserve_num) { if (num == reserve_num) strncpy(buff, "", buff_len); else snprintf(buff, buff_len, "%d", num); if (buff_len > 0) buff[buff_len - 1] = '\0'; return buff; } static void threshold_helper(const char * header, const char * pad, const uint8_t *tp, int etype, const struct opts_t * op) { char b[128]; char b2[128]; if (op->inner_hex) { if (header) printf("%s", header); printf("%s%02x %02x %02x %02x\n", pad, tp[0], tp[1], tp[2], tp[3]); return; } switch (etype) { case 0x4: /*temperature */ if (header) printf("%s", header); printf("%shigh critical=%s, high warning=%s", pad, reserved_or_num(b, 128, tp[0] - TEMPERAT_OFF, -TEMPERAT_OFF), reserved_or_num(b2, 128, tp[1] - TEMPERAT_OFF, -TEMPERAT_OFF)); if (op->do_filter && (0 == tp[2]) && (0 == tp[3])) { printf(" (in Celsius)\n"); break; } printf("\n%slow warning=%s, low critical=%s (in Celsius)\n", pad, reserved_or_num(b, 128, tp[2] - TEMPERAT_OFF, -TEMPERAT_OFF), reserved_or_num(b2, 128, tp[3] - TEMPERAT_OFF, -TEMPERAT_OFF)); break; case 0xb: /* UPS */ if (header) printf("%s", header); if (0 == tp[2]) strcpy(b, ""); else snprintf(b, sizeof(b), "%d", tp[2]); printf("%slow warning=%s, ", pad, b); if (0 == tp[3]) strcpy(b, ""); else snprintf(b, sizeof(b), "%d", tp[3]); printf("low critical=%s (in minutes)\n", b); break; case 0x12: /* voltage */ if (header) printf("%s", header); #ifdef SG_LIB_MINGW printf("%shigh critical=%g %%, high warning=%g %% (above nominal " "voltage)\n", pad, 0.5 * tp[0], 0.5 * tp[1]); printf("%slow warning=%g %%, low critical=%g %% (below nominal " "voltage)\n", pad, 0.5 * tp[2], 0.5 * tp[3]); #else printf("%shigh critical=%.1f %%, high warning=%.1f %% (above nominal " "voltage)\n", pad, 0.5 * tp[0], 0.5 * tp[1]); printf("%slow warning=%.1f %%, low critical=%.1f %% (below nominal " "voltage)\n", pad, 0.5 * tp[2], 0.5 * tp[3]); #endif break; case 0x13: /* current */ if (header) printf("%s", header); #ifdef SG_LIB_MINGW printf("%shigh critical=%g %%, high warning=%g %%", pad, 0.5 * tp[0], 0.5 * tp[1]); #else printf("%shigh critical=%.1f %%, high warning=%.1f %%", pad, 0.5 * tp[0], 0.5 * tp[1]); #endif printf(" (above nominal current)\n"); break; default: if (op->verbose) { if (header) printf("%s", header); printf("%s<< no thresholds for this element type >>\n", pad); } break; } } /* THRESHOLD_DPC [0x5] */ static void threshold_sdg(const struct th_es_t * tesp, uint32_t ref_gen_code, const uint8_t * resp, int resp_len, const struct opts_t * op) { int j, k; uint32_t gen_code; bool got1, match_ind_th; const uint8_t * bp; const uint8_t * last_bp; const struct type_desc_hdr_t * tdhp = tesp->th_base; char b[64]; printf("Threshold In diagnostic page:\n"); if (resp_len < 4) goto truncated; printf(" INVOP=%d\n", !!(resp[1] & 0x10)); last_bp = resp + resp_len - 1; if (resp_len < 8) goto truncated; gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%" PRIx32 "\n", gen_code); if (ref_gen_code != gen_code) { pr2serr(" <>\n"); return; } printf(" Threshold status descriptor list\n"); bp = resp + 8; for (k = 0, got1 = false; k < tesp->num_ths; ++k, ++tdhp) { if ((bp + 3) > last_bp) goto truncated; match_ind_th = (op->ind_given && (k == op->ind_th)); if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) { printf(" Element type: %s, subenclosure id: %d [ti=%d]\n", etype_str(tdhp->etype, b, sizeof(b)), tdhp->se_id, k); threshold_helper(" Overall descriptor:\n", " ", bp, tdhp->etype, op); got1 = true; } for (bp += 4, j = 0; j < tdhp->num_elements; ++j, bp += 4) { if (op->ind_given) { if ((! match_ind_th) || (-1 == op->ind_indiv) || (! match_ind_indiv(j, op))) continue; } snprintf(b, sizeof(b), " Element %d descriptor:\n", j); threshold_helper(b, " ", bp, tdhp->etype, op); got1 = true; } } if (op->ind_given && (! got1)) { printf(" >>> no match on --index=%d,%d", op->ind_th, op->ind_indiv); if (op->ind_indiv_last > op->ind_indiv) printf("-%d\n", op->ind_indiv_last); else printf("\n"); } return; truncated: pr2serr(" <<>>\n"); return; } /* ELEM_DESC_DPC [0x7] * This page essentially contains names of overall and individual * elements. */ static void element_desc_sdg(const struct th_es_t * tesp, uint32_t ref_gen_code, const uint8_t * resp, int resp_len, const struct opts_t * op) { int j, k, desc_len; uint32_t gen_code; bool got1, match_ind_th; const uint8_t * bp; const uint8_t * last_bp; const struct type_desc_hdr_t * tp; char b[64]; printf("Element Descriptor In diagnostic page:\n"); if (resp_len < 4) goto truncated; last_bp = resp + resp_len - 1; if (resp_len < 8) goto truncated; gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%" PRIx32 "\n", gen_code); if (ref_gen_code != gen_code) { pr2serr(" <>\n"); return; } printf(" element descriptor list (grouped by type):\n"); bp = resp + 8; got1 = false; for (k = 0, tp = tesp->th_base; k < tesp->num_ths; ++k, ++tp) { if ((bp + 3) > last_bp) goto truncated; desc_len = sg_get_unaligned_be16(bp + 2) + 4; match_ind_th = (op->ind_given && (k == op->ind_th)); if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) { printf(" Element type: %s, subenclosure id: %d [ti=%d]\n", etype_str(tp->etype, b, sizeof(b)), tp->se_id, k); if (desc_len > 4) printf(" Overall descriptor: %.*s\n", desc_len - 4, bp + 4); else printf(" Overall descriptor: \n"); got1 = true; } for (bp += desc_len, j = 0; j < tp->num_elements; ++j, bp += desc_len) { desc_len = sg_get_unaligned_be16(bp + 2) + 4; if (op->ind_given) { if ((! match_ind_th) || (-1 == op->ind_indiv) || (! match_ind_indiv(j, op))) continue; } if (desc_len > 4) printf(" Element %d descriptor: %.*s\n", j, desc_len - 4, bp + 4); else printf(" Element %d descriptor: \n", j); got1 = true; } } if (op->ind_given && (! got1)) { printf(" >>> no match on --index=%d,%d", op->ind_th, op->ind_indiv); if (op->ind_indiv_last > op->ind_indiv) printf("-%d\n", op->ind_indiv_last); else printf("\n"); } return; truncated: pr2serr(" <<>>\n"); return; } static bool saddr_non_zero(const uint8_t * bp) { return ! sg_all_zeros(bp, 8); } static const char * sas_device_type[] = { "no SAS device attached", /* but might be SATA device */ "end device", "expander device", /* in SAS-1.1 this was a "edge expander device */ "expander device (fanout, SAS-1.1)", /* marked obsolete in SAS-2 */ "reserved [4]", "reserved [5]", "reserved [6]", "reserved [7]" }; static void additional_elem_sas(const char * pad, const uint8_t * ae_bp, int etype, const struct th_es_t * tesp, const struct opts_t * op) { int phys, j, m, n, desc_type, eiioe, eip_offset; bool nofilter = ! op->do_filter; bool eip; const struct join_row_t * jrp; const uint8_t * aep; const uint8_t * ed_bp; const char * cp; char b[64]; eip = !!(0x10 & ae_bp[0]); eiioe = eip ? (0x3 & ae_bp[2]) : 0; eip_offset = eip ? 2 : 0; desc_type = (ae_bp[3 + eip_offset] >> 6) & 0x3; if (op->verbose > 1) printf("%sdescriptor_type: %d\n", pad, desc_type); if (0 == desc_type) { phys = ae_bp[2 + eip_offset]; printf("%snumber of phys: %d, not all phys: %d", pad, phys, ae_bp[3 + eip_offset] & 1); if (eip_offset) printf(", device slot number: %d", ae_bp[5 + eip_offset]); printf("\n"); aep = ae_bp + 4 + eip_offset + eip_offset; for (j = 0; j < phys; ++j, aep += 28) { bool print_sas_addr = false; bool saddr_nz; printf("%sphy index: %d\n", pad, j); printf("%s SAS device type: %s\n", pad, sas_device_type[(0x70 & aep[0]) >> 4]); if (nofilter || (0xe & aep[2])) printf("%s initiator port for:%s%s%s\n", pad, ((aep[2] & 8) ? " SSP" : ""), ((aep[2] & 4) ? " STP" : ""), ((aep[2] & 2) ? " SMP" : "")); if (nofilter || (0x8f & aep[3])) printf("%s target port for:%s%s%s%s%s\n", pad, ((aep[3] & 0x80) ? " SATA_port_selector" : ""), ((aep[3] & 8) ? " SSP" : ""), ((aep[3] & 4) ? " STP" : ""), ((aep[3] & 2) ? " SMP" : ""), ((aep[3] & 1) ? " SATA_device" : "")); saddr_nz = saddr_non_zero(aep + 4); if (nofilter || saddr_nz) { print_sas_addr = true; printf("%s attached SAS address: 0x", pad); if (saddr_nz) { for (m = 0; m < 8; ++m) printf("%02x", aep[4 + m]); } else printf("0"); } saddr_nz = saddr_non_zero(aep + 12); if (nofilter || saddr_nz) { print_sas_addr = true; printf("\n%s SAS address: 0x", pad); if (saddr_nz) { for (m = 0; m < 8; ++m) printf("%02x", aep[12 + m]); } else printf("0"); } if (print_sas_addr) printf("\n%s phy identifier: 0x%x\n", pad, aep[20]); } } else if (1 == desc_type) { phys = ae_bp[2 + eip_offset]; if (SAS_EXPANDER_ETC == etype) { printf("%snumber of phys: %d\n", pad, phys); printf("%sSAS address: 0x", pad); for (m = 0; m < 8; ++m) printf("%02x", ae_bp[6 + eip_offset + m]); printf("\n%sAttached connector; other_element pairs:\n", pad); aep = ae_bp + 14 + eip_offset; for (j = 0; j < phys; ++j, aep += 2) { printf("%s [%d] ", pad, j); m = aep[0]; /* connector element index */ if (0xff == m) printf("no connector"); else { if (tesp->j_base) { if (0 == eiioe) jrp = find_join_row_cnst(tesp, m, FJ_SAS_CON); else if ((1 == eiioe) || (3 == eiioe)) jrp = find_join_row_cnst(tesp, m, FJ_IOE); else jrp = find_join_row_cnst(tesp, m, FJ_EOE); if ((NULL == jrp) || (NULL == jrp->enc_statp) || (SAS_CONNECTOR_ETC != jrp->etype)) printf("broken [conn_idx=%d]", m); else { enc_status_helper("", jrp->enc_statp, jrp->etype, true, op); printf(" [%d]", jrp->indiv_i); } } else printf("connector ei: %d", m); } m = aep[1]; /* other element index */ if (0xff != m) { printf("; "); if (tesp->j_base) { if (0 == eiioe) jrp = find_join_row_cnst(tesp, m, FJ_AESS); else if ((1 == eiioe) || (3 == eiioe)) jrp = find_join_row_cnst(tesp, m, FJ_IOE); else jrp = find_join_row_cnst(tesp, m, FJ_EOE); if (NULL == jrp) printf("broken [oth_elem_idx=%d]", m); else if (jrp->elem_descp) { cp = etype_str(jrp->etype, b, sizeof(b)); ed_bp = jrp->elem_descp; n = sg_get_unaligned_be16(ed_bp + 2); if (n > 0) printf("%.*s [%d,%d] etype: %s", n, (const char *)(ed_bp + 4), jrp->th_i, jrp->indiv_i, cp); else printf("[%d,%d] etype: %s", jrp->th_i, jrp->indiv_i, cp); } else { cp = etype_str(jrp->etype, b, sizeof(b)); printf("[%d,%d] etype: %s", jrp->th_i, jrp->indiv_i, cp); } } else printf("other ei: %d", m); } printf("\n"); } } else if ((SCSI_TPORT_ETC == etype) || (SCSI_IPORT_ETC == etype) || (ENC_SCELECTR_ETC == etype)) { printf("%snumber of phys: %d\n", pad, phys); aep = ae_bp + 6 + eip_offset; for (j = 0; j < phys; ++j, aep += 12) { printf("%sphy index: %d\n", pad, j); printf("%s phy_id: 0x%x\n", pad, aep[0]); printf("%s ", pad); m = aep[2]; /* connector element index */ if (0xff == m) printf("no connector"); else { if (tesp->j_base) { if (0 == eiioe) jrp = find_join_row_cnst(tesp, m, FJ_SAS_CON); else if ((1 == eiioe) || (3 == eiioe)) jrp = find_join_row_cnst(tesp, m, FJ_IOE); else jrp = find_join_row_cnst(tesp, m, FJ_EOE); if ((NULL == jrp) || (NULL == jrp->enc_statp) || (SAS_CONNECTOR_ETC != jrp->etype)) printf("broken [conn_idx=%d]", m); else { enc_status_helper("", jrp->enc_statp, jrp->etype, true, op); printf(" [%d]", jrp->indiv_i); } } else printf("connector ei: %d", m); } m = aep[3]; /* other element index */ if (0xff != m) { printf("; "); if (tesp->j_base) { if (0 == eiioe) jrp = find_join_row_cnst(tesp, m, FJ_AESS); else if ((1 == eiioe) || (3 == eiioe)) jrp = find_join_row_cnst(tesp, m, FJ_IOE); else jrp = find_join_row_cnst(tesp, m, FJ_EOE); if (NULL == jrp) printf("broken [oth_elem_idx=%d]", m); else if (jrp->elem_descp) { cp = etype_str(jrp->etype, b, sizeof(b)); ed_bp = jrp->elem_descp; n = sg_get_unaligned_be16(ed_bp + 2); if (n > 0) printf("%.*s [%d,%d] etype: %s", n, (const char *)(ed_bp + 4), jrp->th_i, jrp->indiv_i, cp); else printf("[%d,%d] etype: %s", jrp->th_i, jrp->indiv_i, cp); } else { cp = etype_str(jrp->etype, b, sizeof(b)); printf("[%d,%d] etype: %s", jrp->th_i, jrp->indiv_i, cp); } } else printf("other ei: %d", m); } printf("\n"); printf("%s SAS address: 0x", pad); for (m = 0; m < 8; ++m) printf("%02x", aep[4 + m]); printf("\n"); } /* end_for: loop over phys in SCSI initiator, target */ } else printf("%sunrecognised element type [%d] for desc_type " "1\n", pad, etype); } else printf("%sunrecognised descriptor type [%d]\n", pad, desc_type); } static void additional_elem_helper(const char * pad, const uint8_t * ae_bp, int len, int etype, const struct th_es_t * tesp, const struct opts_t * op) { int ports, phys, j, m, eip_offset, pcie_pt; bool eip; uint16_t pcie_vid; const uint8_t * aep; char b[64]; if (op->inner_hex) { for (j = 0; j < len; ++j) { if (0 == (j % 16)) printf("%s%s", ((0 == j) ? "" : "\n"), pad); printf("%02x ", ae_bp[j]); } printf("\n"); return; } eip = !!(0x10 & ae_bp[0]); eip_offset = eip ? 2 : 0; switch (0xf & ae_bp[0]) { /* switch on protocol identifier */ case TPROTO_FCP: printf("%sTransport protocol: FCP\n", pad); if (len < (12 + eip_offset)) break; ports = ae_bp[2 + eip_offset]; printf("%snumber of ports: %d\n", pad, ports); printf("%snode_name: ", pad); for (m = 0; m < 8; ++m) printf("%02x", ae_bp[6 + eip_offset + m]); if (eip_offset) printf(", device slot number: %d", ae_bp[5 + eip_offset]); printf("\n"); aep = ae_bp + 14 + eip_offset; for (j = 0; j < ports; ++j, aep += 16) { printf("%s port index: %d, port loop position: %d, port " "bypass reason: 0x%x\n", pad, j, aep[0], aep[1]); printf("%srequested hard address: %d, n_port identifier: " "%02x%02x%02x\n", pad, aep[4], aep[5], aep[6], aep[7]); printf("%s n_port name: ", pad); for (m = 0; m < 8; ++m) printf("%02x", aep[8 + m]); printf("\n"); } break; case TPROTO_SAS: printf("%sTransport protocol: SAS\n", pad); if (len < (4 + eip_offset)) break; additional_elem_sas(pad, ae_bp, etype, tesp, op); break; case TPROTO_PCIE: /* added in ses3r08; contains little endian fields */ printf("%sTransport protocol: PCIe\n", pad); if (0 == eip_offset) { printf("%sfor this protocol EIP must be set (it isn't)\n", pad); break; } if (len < 6) break; pcie_pt = (ae_bp[5] >> 5) & 0x7; if (TPROTO_PCIE_PS_NVME == pcie_pt) printf("%sPCIe protocol type: NVMe\n", pad); else { /* no others currently defined */ printf("%sTransport protocol: PCIe subprotocol=0x%x not " "decoded\n", pad, pcie_pt); if (op->verbose) hex2stdout(ae_bp, len, 0); break; } phys = ae_bp[4]; printf("%snumber of ports: %d, not all ports: %d", pad, phys, ae_bp[5] & 1); printf(", device slot number: %d\n", ae_bp[7]); pcie_vid = sg_get_unaligned_le16(ae_bp + 10); /* N.B. LE */ printf("%sPCIe vendor id: 0x%" PRIx16 "%s\n", pad, pcie_vid, (0xffff == pcie_vid) ? " (not reported)" : ""); printf("%sserial number: %.20s\n", pad, ae_bp + 12); printf("%smodel number: %.40s\n", pad, ae_bp + 32); aep = ae_bp + 72; for (j = 0; j < phys; ++j, aep += 8) { bool psn_valid = !!(0x4 & aep[0]); bool bdf_valid = !!(0x2 & aep[0]); bool cid_valid = !!(0x1 & aep[0]); printf("%sport index: %d\n", pad, j); printf("%s PSN_VALID=%d, BDF_VALID=%d, CID_VALID=%d\n", pad, (int)psn_valid, (int)bdf_valid, (int)cid_valid); if (cid_valid) /* N.B. little endian */ printf("%s controller id: 0x%" PRIx16 "\n", pad, sg_get_unaligned_le16(aep + 1)); /* N.B. LEndian */ if (bdf_valid) printf("%s bus number: 0x%x, device number: 0x%x, " "function number: 0x%x\n", pad, aep[4], (aep[5] >> 3) & 0x1f, 0x7 & aep[5]); if (psn_valid) /* little endian, top 3 bits assumed zero */ printf("%s physical slot number: 0x%" PRIx16 "\n", pad, 0x1fff & sg_get_unaligned_le16(aep + 6)); /* N.B. LE */ } break; default: printf("%sTransport protocol: %s not decoded\n", pad, sg_get_trans_proto_str((0xf & ae_bp[0]), sizeof(b), b)); if (op->verbose) hex2stdout(ae_bp, len, 0); break; } } /* ADD_ELEM_STATUS_DPC [0xa] Additional Element Status dpage * Previously called "Device element status descriptor". Changed "device" * to "additional" to allow for SAS expander and SATA devices */ static void additional_elem_sdg(const struct th_es_t * tesp, uint32_t ref_gen_code, const uint8_t * resp, int resp_len, const struct opts_t * op) { int j, k, desc_len, etype, el_num, ind, elem_count, ei, eiioe, num_elems; int fake_ei; uint32_t gen_code; bool eip, invalid, match_ind_th, my_eiioe_force, skip; const uint8_t * bp; const uint8_t * last_bp; const struct type_desc_hdr_t * tp = tesp->th_base; char b[64]; printf("Additional element status diagnostic page:\n"); if (resp_len < 4) goto truncated; last_bp = resp + resp_len - 1; gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%" PRIx32 "\n", gen_code); if (ref_gen_code != gen_code) { pr2serr(" <>\n"); return; } printf(" additional element status descriptor list\n"); bp = resp + 8; my_eiioe_force = op->eiioe_force; for (k = 0, elem_count = 0; k < tesp->num_ths; ++k, ++tp) { fake_ei = -1; etype = tp->etype; num_elems = tp->num_elements; if (! is_et_used_by_aes(etype)) { elem_count += num_elems; continue; /* skip if not element type of interest */ } if ((bp + 1) > last_bp) goto truncated; eip = !! (bp[0] & 0x10); if (eip) { /* do bounds check on the element index */ ei = bp[3]; skip = false; if ((0 == k) && op->eiioe_auto && (1 == ei)) { /* heuristic: if first AES descriptor has EIP set and its * element index equal to 1, then act as if the EIIOE field * is one. */ my_eiioe_force = true; } eiioe = (0x3 & bp[2]); if (my_eiioe_force && (0 == eiioe)) eiioe = 1; if (1 == eiioe) { if ((ei < (elem_count + k)) || (ei > (elem_count + k + num_elems))) { elem_count += num_elems; skip = true; } } else { if ((ei < elem_count) || (ei > elem_count + num_elems)) { if ((0 == ei) && (TPROTO_SAS == (0xf & bp[0])) && (1 == (bp[5] >> 6))) { /* heuristic (hack) for Areca 8028 */ fake_ei = elem_count; if (op->verbose > 2) pr2serr("%s: hack, bad ei=%d, fake_ei=%d\n", __func__, ei, fake_ei); ei = fake_ei; } else { elem_count += num_elems; skip = true; } } } if (skip) { if (op->verbose > 2) pr2serr("skipping etype=0x%x, k=%d due to " "element_index=%d bounds\n effective eiioe=%d, " "elem_count=%d, num_elems=%d\n", etype, k, ei, eiioe, elem_count, num_elems); continue; } } match_ind_th = (op->ind_given && (k == op->ind_th)); if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) { printf(" Element type: %s, subenclosure id: %d [ti=%d]\n", etype_str(etype, b, sizeof(b)), tp->se_id, k); } el_num = 0; for (j = 0; j < num_elems; ++j, bp += desc_len, ++el_num) { invalid = !!(bp[0] & 0x80); desc_len = bp[1] + 2; eip = !!(bp[0] & 0x10); eiioe = eip ? (0x3 & bp[2]) : 0; if (fake_ei >= 0) ind = fake_ei; else ind = eip ? bp[3] : el_num; if (op->ind_given) { if ((! match_ind_th) || (-1 == op->ind_indiv) || (! match_ind_indiv(el_num, op))) continue; } if (eip) printf(" Element index: %d eiioe=%d%s\n", ind, eiioe, (((0 != eiioe) && my_eiioe_force) ? " but overridden" : "")); else printf(" Element %d descriptor\n", ind); if (invalid && (! op->inner_hex)) printf(" flagged as invalid (no further " "information)\n"); else additional_elem_helper(" ", bp, desc_len, etype, tesp, op); } elem_count += tp->num_elements; } /* end_for: loop over type descriptor headers */ return; truncated: pr2serr(" <<>>\n"); return; } /* SUBENC_HELP_TEXT_DPC [0xb] */ static void subenc_help_sdg(const uint8_t * resp, int resp_len) { int k, el, num_subs; uint32_t gen_code; const uint8_t * bp; const uint8_t * last_bp; printf("Subenclosure help text diagnostic page:\n"); if (resp_len < 4) goto truncated; num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */ last_bp = resp + resp_len - 1; printf(" number of secondary subenclosures: %d\n", num_subs - 1); gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%" PRIx32 "\n", gen_code); bp = resp + 8; for (k = 0; k < num_subs; ++k, bp += el) { if ((bp + 3) > last_bp) goto truncated; el = sg_get_unaligned_be16(bp + 2) + 4; printf(" subenclosure identifier: %d\n", bp[1]); if (el > 4) printf(" %.*s\n", el - 4, bp + 4); else printf(" \n"); } return; truncated: pr2serr(" <<>>\n"); return; } /* SUBENC_STRING_DPC [0xc] */ static void subenc_string_sdg(const uint8_t * resp, int resp_len) { int k, el, num_subs; uint32_t gen_code; const uint8_t * bp; const uint8_t * last_bp; printf("Subenclosure string in diagnostic page:\n"); if (resp_len < 4) goto truncated; num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */ last_bp = resp + resp_len - 1; printf(" number of secondary subenclosures: %d\n", num_subs - 1); gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%" PRIx32 "\n", gen_code); bp = resp + 8; for (k = 0; k < num_subs; ++k, bp += el) { if ((bp + 3) > last_bp) goto truncated; el = sg_get_unaligned_be16(bp + 2) + 4; printf(" subenclosure identifier: %d\n", bp[1]); if (el > 4) { char bb[1024]; hex2str(bp + 40, el - 40, " ", 0, sizeof(bb), bb); printf("%s\n", bb); } else printf(" \n"); } return; truncated: pr2serr(" <<>>\n"); return; } /* SUBENC_NICKNAME_DPC [0xf] */ static void subenc_nickname_sdg(const uint8_t * resp, int resp_len) { int k, el, num_subs; uint32_t gen_code; const uint8_t * bp; const uint8_t * last_bp; printf("Subenclosure nickname status diagnostic page:\n"); if (resp_len < 4) goto truncated; num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */ last_bp = resp + resp_len - 1; printf(" number of secondary subenclosures: %d\n", num_subs - 1); gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%" PRIx32 "\n", gen_code); bp = resp + 8; el = 40; for (k = 0; k < num_subs; ++k, bp += el) { if ((bp + el - 1) > last_bp) goto truncated; printf(" subenclosure identifier: %d\n", bp[1]); printf(" nickname status: 0x%x\n", bp[2]); printf(" nickname additional status: 0x%x\n", bp[3]); printf(" nickname language code: %.2s\n", bp + 6); printf(" nickname: %.*s\n", 32, bp + 8); } return; truncated: pr2serr(" <<>>\n"); return; } /* SUPPORTED_SES_DPC [0xd] */ static void supported_pages_sdg(const char * leadin, const uint8_t * resp, int resp_len) { int k, code, prev; bool got1; const struct diag_page_abbrev * ap; printf("%s:\n", leadin); for (k = 0, prev = 0; k < (resp_len - 4); ++k, prev = code) { const char * cp; code = resp[k + 4]; if (code < prev) break; /* assume to be padding at end */ cp = find_diag_page_desc(code); if (cp) { printf(" %s [", cp); for (ap = dp_abbrev, got1 = false; ap->abbrev; ++ap) { if (ap->page_code == code) { printf("%s%s", (got1 ? "," : ""), ap->abbrev); got1 = true; } } printf("] [0x%x]\n", code); } else printf(" [0x%x]\n", code); } } /* An array of Download microcode status field values and descriptions */ static struct diag_page_code mc_status_arr[] = { {0x0, "No download microcode operation in progress"}, {0x1, "Download in progress, awaiting more"}, {0x2, "Download complete, updating non-volatile storage"}, {0x3, "Updating non-volatile storage with deferred microcode"}, {0x10, "Complete, no error, starting now"}, {0x11, "Complete, no error, start after hard reset or power cycle"}, {0x12, "Complete, no error, start after power cycle"}, {0x13, "Complete, no error, start after activate_mc, hard reset or " "power cycle"}, {0x80, "Error, discarded, see additional status"}, {0x81, "Error, discarded, image error"}, {0x82, "Timeout, discarded"}, {0x83, "Internal error, need new microcode before reset"}, {0x84, "Internal error, need new microcode, reset safe"}, {0x85, "Unexpected activate_mc received"}, {0x1000, NULL}, }; static const char * get_mc_status(uint8_t status_val) { const struct diag_page_code * mcsp; for (mcsp = mc_status_arr; mcsp->desc; ++mcsp) { if (status_val == mcsp->page_code) return mcsp->desc; } return ""; } /* DOWNLOAD_MICROCODE_DPC [0xe] */ static void download_code_sdg(const uint8_t * resp, int resp_len) { int k, num_subs; uint32_t gen_code; const uint8_t * bp; const uint8_t * last_bp; const char * cp; printf("Download microcode status diagnostic page:\n"); if (resp_len < 4) goto truncated; num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */ last_bp = resp + resp_len - 1; printf(" number of secondary subenclosures: %d\n", num_subs - 1); gen_code = sg_get_unaligned_be32(resp + 4); printf(" generation code: 0x%" PRIx32 "\n", gen_code); bp = resp + 8; for (k = 0; k < num_subs; ++k, bp += 16) { if ((bp + 3) > last_bp) goto truncated; cp = (0 == bp[1]) ? " [primary]" : ""; printf(" subenclosure identifier: %d%s\n", bp[1], cp); cp = get_mc_status(bp[2]); if (strlen(cp) > 0) { printf(" download microcode status: %s [0x%x]\n", cp, bp[2]); printf(" download microcode additional status: 0x%x\n", bp[3]); } else printf(" download microcode status: 0x%x [additional " "status: 0x%x]\n", bp[2], bp[3]); printf(" download microcode maximum size: %d bytes\n", sg_get_unaligned_be32(bp + 4)); printf(" download microcode expected buffer id: 0x%x\n", bp[11]); printf(" download microcode expected buffer id offset: %d\n", sg_get_unaligned_be32(bp + 12)); } return; truncated: pr2serr(" <<>>\n"); return; } /* Reads hex data from command line, stdin or a file when in_hex is true. * Reads binary from stdin or file when in_hex is false. Returns 0 on * success, 1 otherwise. If inp is a file and may_have_at, then the * first character is skipped to get filename (since it should be '@'). */ static int read_hex(const char * inp, uint8_t * arr, int mx_arr_len, int * arr_len, bool in_hex, bool may_have_at, int vb) { bool has_stdin, split_line; int in_len, k, j, m, off, off_fn; unsigned int h; const char * lcp; char * cp; char * c2p; char line[512]; char carry_over[4]; FILE * fp = NULL; if ((NULL == inp) || (NULL == arr) || (NULL == arr_len)) return 1; off_fn = may_have_at ? 1 : 0; lcp = inp; in_len = strlen(inp); if (0 == in_len) { *arr_len = 0; return 0; } has_stdin = ((1 == in_len) && ('-' == inp[0])); if (! in_hex) { /* binary, assume its not on the command line, */ int fd; /* that leaves stdin or a file (pipe) */ struct stat a_stat; if (has_stdin) fd = STDIN_FILENO; else { fd = open(inp + off_fn, O_RDONLY); if (fd < 0) { pr2serr("unable to open binary file %s: %s\n", inp + off_fn, safe_strerror(errno)); return 1; } } k = read(fd, arr, mx_arr_len); if (k <= 0) { if (0 == k) pr2serr("read 0 bytes from binary file %s\n", inp + off_fn); else pr2serr("read from binary file %s: %s\n", inp + off_fn, safe_strerror(errno)); if (! has_stdin) close(fd); return 1; } if ((0 == fstat(fd, &a_stat)) && S_ISFIFO(a_stat.st_mode)) { /* pipe; keep reading till error or 0 read */ while (k < mx_arr_len) { m = read(fd, arr + k, mx_arr_len - k); if (0 == m) break; if (m < 0) { pr2serr("read from binary pipe %s: %s\n", inp + off_fn, safe_strerror(errno)); if (! has_stdin) close(fd); return 1; } k += m; } } *arr_len = k; if (! has_stdin) close(fd); return 0; } if (has_stdin || (! may_have_at) || ('@' == inp[0])) { /* read hex from stdin or file */ if (has_stdin) fp = stdin; else { fp = fopen(inp + off_fn, "r"); if (NULL == fp) { pr2serr("%s: unable to open file: %s\n", __func__, inp + off_fn); return 1; } } carry_over[0] = 0; for (j = 0, off = 0; j < MX_DATA_IN_LINES; ++j) { if (NULL == fgets(line, sizeof(line), fp)) break; in_len = strlen(line); if (in_len > 0) { if ('\n' == line[in_len - 1]) { --in_len; line[in_len] = '\0'; split_line = false; } else split_line = true; } if (in_len < 1) { carry_over[0] = 0; continue; } if (carry_over[0]) { if (isxdigit((uint8_t)line[0])) { carry_over[1] = line[0]; carry_over[2] = '\0'; if (1 == sscanf(carry_over, "%x", &h)) arr[off - 1] = h; /* back up and overwrite */ else { pr2serr("%s: carry_over error ['%s'] around line " "%d\n", __func__, carry_over, j + 1); goto err_with_fp; } lcp = line + 1; --in_len; } else lcp = line; carry_over[0] = 0; } else lcp = line; m = strspn(lcp, " \t"); if (m == in_len) continue; lcp += m; in_len -= m; if ('#' == *lcp) continue; k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); if (in_len != k) { pr2serr("%s: syntax error at line %d, pos %d\n", __func__, j + 1, m + k + 1); if (vb > 2) pr2serr("first 40 characters of line: %.40s\n", line); goto err_with_fp; } for (k = 0; k < (mx_arr_len - off); ++k) { if (1 == sscanf(lcp, "%x", &h)) { if (h > 0xff) { pr2serr("%s: hex number larger than 0xff in line %d, " "pos %d\n", __func__, j + 1, (int)(lcp - line + 1)); if (vb > 2) pr2serr("first 40 characters of line: %.40s\n", line); goto err_with_fp; } if (split_line && (1 == strlen(lcp))) { /* single trailing hex digit might be a split pair */ carry_over[0] = *lcp; } arr[off + k] = h; lcp = strpbrk(lcp, " ,\t"); if (NULL == lcp) break; lcp += strspn(lcp, " ,\t"); if ('\0' == *lcp) break; } else { pr2serr("%s: error in line %d, at pos %d\n", __func__, j + 1, (int)(lcp - line + 1)); if (vb > 2) pr2serr("first 40 characters of line: %.40s\n", line); goto err_with_fp; } } off += k + 1; if (off >= mx_arr_len) break; } *arr_len = off; } else { /* hex string on command line */ k = strspn(inp, "0123456789aAbBcCdDeEfF, "); if (in_len != k) { pr2serr("%s: error at pos %d\n", __func__, k + 1); goto err_with_fp; } for (k = 0; k < mx_arr_len; ++k) { if (1 == sscanf(lcp, "%x", &h)) { if (h > 0xff) { pr2serr("%s: hex number larger than 0xff at pos %d\n", __func__, (int)(lcp - inp + 1)); goto err_with_fp; } arr[k] = h; cp = (char *)strchr(lcp, ','); c2p = (char *)strchr(lcp, ' '); if (NULL == cp) cp = c2p; if (NULL == cp) break; if (c2p && (c2p < cp)) cp = c2p; lcp = cp + 1; } else { pr2serr("%s: error at pos %d\n", __func__, (int)(lcp - inp + 1)); goto err_with_fp; } } *arr_len = k + 1; } if (vb > 3) { pr2serr("%s: user provided data:\n", __func__); hex2stderr(arr, *arr_len, 0); } if (fp && (fp != stdin)) fclose(fp); return 0; err_with_fp: if (fp && (fp != stdin)) fclose(fp); return 1; } static int process_status_dpage(struct sg_pt_base * ptvp, int page_code, uint8_t * resp, int resp_len, struct opts_t * op) { int j, num_ths; int ret = 0; uint32_t ref_gen_code; const char * cp; struct enclosure_info primary_info; struct th_es_t tes; struct th_es_t * tesp; char bb[120]; tesp = &tes; memset(tesp, 0, sizeof(tes)); if ((cp = find_in_diag_page_desc(page_code))) snprintf(bb, sizeof(bb), "%s dpage", cp); else snprintf(bb, sizeof(bb), "dpage 0x%x", page_code); cp = bb; if (op->do_raw) { if (1 == op->do_raw) hex2stdout(resp + 4, resp_len - 4, -1); else { if (sg_set_binary_mode(STDOUT_FILENO) < 0) perror("sg_set_binary_mode"); dStrRaw(resp, resp_len); } goto fini; } else if (op->do_hex) { if (op->do_hex > 2) { if (op->do_hex > 3) { if (4 == op->do_hex) printf("\n# %s:\n", cp); else printf("\n# %s [0x%x]:\n", cp, page_code); } hex2stdout(resp, resp_len, -1); } else { printf("# Response in hex for %s:\n", cp); hex2stdout(resp, resp_len, (2 == op->do_hex)); } goto fini; } memset(&primary_info, 0, sizeof(primary_info)); switch (page_code) { case SUPPORTED_DPC: supported_pages_sdg("Supported diagnostic pages", resp, resp_len); break; case CONFIGURATION_DPC: configuration_sdg(resp, resp_len); break; case ENC_STATUS_DPC: num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr, MX_ELEM_HDR, &ref_gen_code, &primary_info, op); if (num_ths < 0) { ret = num_ths; goto fini; } if ((1 == type_desc_hdr_count) && primary_info.have_info) { printf(" Primary enclosure logical identifier (hex): "); for (j = 0; j < 8; ++j) printf("%02x", primary_info.enc_log_id[j]); printf("\n"); } tesp->th_base = type_desc_hdr_arr; tesp->num_ths = num_ths; enc_status_dp(tesp, ref_gen_code, resp, resp_len, op); break; case ARRAY_STATUS_DPC: num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr, MX_ELEM_HDR, &ref_gen_code, &primary_info, op); if (num_ths < 0) { ret = num_ths; goto fini; } if ((1 == type_desc_hdr_count) && primary_info.have_info) { printf(" Primary enclosure logical identifier (hex): "); for (j = 0; j < 8; ++j) printf("%02x", primary_info.enc_log_id[j]); printf("\n"); } tesp->th_base = type_desc_hdr_arr; tesp->num_ths = num_ths; array_status_dp(tesp, ref_gen_code, resp, resp_len, op); break; case HELP_TEXT_DPC: printf("Help text diagnostic page (for primary " "subenclosure):\n"); if (resp_len > 4) printf(" %.*s\n", resp_len - 4, resp + 4); else printf(" \n"); break; case STRING_DPC: printf("String In diagnostic page (for primary " "subenclosure):\n"); if (resp_len > 4) hex2stdout(resp + 4, resp_len - 4, 0); else printf(" \n"); break; case THRESHOLD_DPC: num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr, MX_ELEM_HDR, &ref_gen_code, &primary_info, op); if (num_ths < 0) { ret = num_ths; goto fini; } if ((1 == type_desc_hdr_count) && primary_info.have_info) { printf(" Primary enclosure logical identifier (hex): "); for (j = 0; j < 8; ++j) printf("%02x", primary_info.enc_log_id[j]); printf("\n"); } tesp->th_base = type_desc_hdr_arr; tesp->num_ths = num_ths; threshold_sdg(tesp, ref_gen_code, resp, resp_len, op); break; case ELEM_DESC_DPC: num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr, MX_ELEM_HDR, &ref_gen_code, &primary_info, op); if (num_ths < 0) { ret = num_ths; goto fini; } if ((1 == type_desc_hdr_count) && primary_info.have_info) { printf(" Primary enclosure logical identifier (hex): "); for (j = 0; j < 8; ++j) printf("%02x", primary_info.enc_log_id[j]); printf("\n"); } tesp->th_base = type_desc_hdr_arr; tesp->num_ths = num_ths; element_desc_sdg(tesp, ref_gen_code, resp, resp_len, op); break; case SHORT_ENC_STATUS_DPC: printf("Short enclosure status diagnostic page, " "status=0x%x\n", resp[1]); break; case ENC_BUSY_DPC: printf("Enclosure Busy diagnostic page, " "busy=%d [vendor specific=0x%x]\n", resp[1] & 1, (resp[1] >> 1) & 0xff); break; case ADD_ELEM_STATUS_DPC: num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr, MX_ELEM_HDR, &ref_gen_code, &primary_info, op); if (num_ths < 0) { ret = num_ths; goto fini; } if (primary_info.have_info) { printf(" Primary enclosure logical identifier (hex): "); for (j = 0; j < 8; ++j) printf("%02x", primary_info.enc_log_id[j]); printf("\n"); } tesp->th_base = type_desc_hdr_arr; tesp->num_ths = num_ths; additional_elem_sdg(tesp, ref_gen_code, resp, resp_len, op); break; case SUBENC_HELP_TEXT_DPC: subenc_help_sdg(resp, resp_len); break; case SUBENC_STRING_DPC: subenc_string_sdg(resp, resp_len); break; case SUPPORTED_SES_DPC: supported_pages_sdg("Supported SES diagnostic pages", resp, resp_len); break; case DOWNLOAD_MICROCODE_DPC: download_code_sdg(resp, resp_len); break; case SUBENC_NICKNAME_DPC: subenc_nickname_sdg(resp, resp_len); break; default: printf("Cannot decode response from diagnostic page: %s\n", cp); hex2stdout(resp, resp_len, 0); } fini: return ret; } /* Display "status" page or pages (if op->page_code==0xff) . data-in from * SES device or user provided (with --data= option). Return 0 for success */ static int process_status_page_s(struct sg_pt_base * ptvp, struct opts_t * op) { int page_code, ret, resp_len; uint8_t * resp = NULL; uint8_t * free_resp = NULL; resp = sg_memalign(op->maxlen, 0, &free_resp, false); if (NULL == resp) { pr2serr("%s: unable to allocate %d bytes on heap\n", __func__, op->maxlen); ret = -1; goto fini; } page_code = op->page_code; if (ALL_DPC == page_code) { int k, n; uint8_t pc, prev; uint8_t supp_dpg_arr[256]; const int s_arr_sz = sizeof(supp_dpg_arr); memset(supp_dpg_arr, 0, s_arr_sz); ret = do_rec_diag(ptvp, SUPPORTED_DPC, resp, op->maxlen, op, &resp_len); if (ret) /* SUPPORTED_DPC failed so try SUPPORTED_SES_DPC */ ret = do_rec_diag(ptvp, SUPPORTED_SES_DPC, resp, op->maxlen, op, &resp_len); if (ret) goto fini; for (n = 0, pc = 0; (n < s_arr_sz) && (n < (resp_len - 4)); ++n) { prev = pc; pc = resp[4 + n]; if (prev > pc) { if (pc) { /* could be zero pad at end which is ok */ pr2serr("%s: Supported (SES) dpage seems corrupt, " "should ascend\n", __func__); ret = SG_LIB_CAT_OTHER; goto fini; } break; } if (pc > 0x2f) break; supp_dpg_arr[n] = pc; } for (k = 0; k < n; ++k) { page_code = supp_dpg_arr[k]; ret = do_rec_diag(ptvp, page_code, resp, op->maxlen, op, &resp_len); if (ret) goto fini; ret = process_status_dpage(ptvp, page_code, resp, resp_len, op); } } else { /* asking for a specific page code */ ret = do_rec_diag(ptvp, page_code, resp, op->maxlen, op, &resp_len); if (ret) goto fini; ret = process_status_dpage(ptvp, page_code, resp, resp_len, op); } fini: if (free_resp) free(free_resp); return ret; } static void devslotnum_and_sasaddr(struct join_row_t * jrp, const uint8_t * ae_bp) { if ((NULL == jrp) || (NULL == ae_bp) || (0 == (0x10 & ae_bp[0]))) return; /* sanity and expect EIP=1 */ switch (0xf & ae_bp[0]) { case TPROTO_FCP: jrp->dev_slot_num = ae_bp[7]; break; case TPROTO_SAS: if (0 == (0xc0 & ae_bp[5])) { /* only for device slot and array device slot elements */ jrp->dev_slot_num = ae_bp[7]; if (ae_bp[4] > 0) { /* number of phys */ int m; /* Use the first phy's "SAS ADDRESS" field */ for (m = 0; m < 8; ++m) jrp->sas_addr[m] = ae_bp[(4 + 4 + 12) + m]; } } break; case TPROTO_PCIE: jrp->dev_slot_num = ae_bp[7]; break; default: ; } } static const char * offset_str(long offset, bool in_hex, char * b, int blen) { if (in_hex && (offset >= 0)) snprintf(b, blen, "0x%lx", offset); else snprintf(b, blen, "%ld", offset); return b; } /* Returns broken_ei which is only true when EIP=1 and EIIOE=0 is overridden * as outlined in join array description near the top of this file. */ static bool join_aes_helper(const uint8_t * ae_bp, const uint8_t * ae_last_bp, const struct th_es_t * tesp, const struct opts_t * op) { int k, j, ei, eiioe, aes_i, hex, blen; bool eip, broken_ei; struct join_row_t * jrp; struct join_row_t * jr2p; const struct type_desc_hdr_t * tdhp = tesp->th_base; char b[20]; jrp = tesp->j_base; blen = sizeof(b); hex = op->do_hex; broken_ei = false; /* loop over all type descriptor headers in the Configuration dpge */ for (k = 0, aes_i = 0; k < tesp->num_ths; ++k, ++tdhp) { if (is_et_used_by_aes(tdhp->etype)) { /* only consider element types that AES element are permiited * to refer to, then loop over those number of elements */ for (j = 0; j < tdhp->num_elements; ++j, ++aes_i, ae_bp += ae_bp[1] + 2) { if ((ae_bp + 1) > ae_last_bp) { if (op->verbose || op->warn) pr2serr("warning: %s: off end of ae page\n", __func__); return broken_ei; } eip = !!(ae_bp[0] & 0x10); /* EIP == Element Index Present */ if (eip) { eiioe = 0x3 & ae_bp[2]; if ((0 == eiioe) && op->eiioe_force) eiioe = 1; } else eiioe = 0; if (eip && (1 == eiioe)) { /* EIP and EIIOE=1 */ ei = ae_bp[3]; jr2p = tesp->j_base + ei; if ((ei >= tesp->num_j_eoe) || (NULL == jr2p->enc_statp)) { pr2serr("%s: oi=%d, ei=%d [num_eoe=%d], eiioe=1 " "not in join_arr\n", __func__, k, ei, tesp->num_j_eoe); return broken_ei; } devslotnum_and_sasaddr(jr2p, ae_bp); if (jr2p->ae_statp) { if (op->warn || op->verbose) { pr2serr("warning: aes slot already in use, " "keep existing AES+%s\n\t", offset_str(jr2p->ae_statp - add_elem_rsp, hex, b, blen)); pr2serr("dropping AES+%s [length=%d, oi=%d, " "ei=%d, aes_i=%d]\n", offset_str(ae_bp - add_elem_rsp, hex, b, blen), ae_bp[1] + 2, k, ei, aes_i); } } else jr2p->ae_statp = ae_bp; } else if (eip && (0 == eiioe)) { /* SES-2 so be careful */ ei = ae_bp[3]; try_again: /* Check AES dpage descriptor ei is valid */ for (jr2p = tesp->j_base; jr2p->enc_statp; ++jr2p) { if (broken_ei) { if (ei == jr2p->ei_aess) break; } else { if (ei == jr2p->ei_eoe) break; } } if (NULL == jr2p->enc_statp) { pr2serr("warning: %s: oi=%d, ei=%d (broken_ei=%d) " "not in join_arr\n", __func__, k, ei, (int)broken_ei); return broken_ei; } if (! is_et_used_by_aes(jr2p->etype)) { /* unexpected element type so ... */ broken_ei = true; goto try_again; } devslotnum_and_sasaddr(jr2p, ae_bp); if (jr2p->ae_statp) { /* 1 to 1 AES to ES mapping assumption violated */ if ((0 == ei) && (TPROTO_SAS == (0xf & ae_bp[0])) && (1 == (ae_bp[5] >> 6))) { /* heuristic for (hack) Areca 8028 */ for (jr2p = tesp->j_base; jr2p->enc_statp; ++jr2p) { if ((-1 == jr2p->indiv_i) || (! is_et_used_by_aes(jr2p->etype)) || jr2p->ae_statp) continue; jr2p->ae_statp = ae_bp; break; } if ((NULL == jr2p->enc_statp) && (op->warn || op->verbose)) pr2serr("warning2: dropping AES+%s [length=" "%d, oi=%d, ei=%d, aes_i=%d]\n", offset_str(ae_bp - add_elem_rsp, hex, b, blen), ae_bp[1] + 2, k, ei, aes_i); } else if (op->warn || op->verbose) { pr2serr("warning3: aes slot already in use, " "keep existing AES+%s\n\t", offset_str(jr2p->ae_statp - add_elem_rsp, hex, b, blen)); pr2serr("dropping AES+%s [length=%d, oi=%d, ei=" "%d, aes_i=%d]\n", offset_str(ae_bp - add_elem_rsp, hex, b, blen), ae_bp[1] + 2, k, ei, aes_i); } } else jr2p->ae_statp = ae_bp; } else if (eip) { /* EIP and EIIOE=2,3 */ ei = ae_bp[3]; for (jr2p = tesp->j_base; jr2p->enc_statp; ++jr2p) { if (ei == jr2p->ei_eoe) break; /* good, found match on ei_eoe */ } if (NULL == jr2p->enc_statp) { pr2serr("warning: %s: oi=%d, ei=%d, not in " "join_arr\n", __func__, k, ei); return broken_ei; } if (! is_et_used_by_aes(jr2p->etype)) { pr2serr("warning: %s: oi=%d, ei=%d, unexpected " "element_type=0x%x\n", __func__, k, ei, jr2p->etype); return broken_ei; } devslotnum_and_sasaddr(jr2p, ae_bp); if (jr2p->ae_statp) { if (op->warn || op->verbose) { pr2serr("warning3: aes slot already in use, " "keep existing AES+%s\n\t", offset_str(jr2p->ae_statp - add_elem_rsp, hex, b, blen)); pr2serr("dropping AES+%s [length=%d, oi=%d, ei=" "%d, aes_i=%d]\n", offset_str(ae_bp - add_elem_rsp, hex, b, blen), ae_bp[1] + 2, k, ei, aes_i); } } else jr2p->ae_statp = ae_bp; } else { /* EIP=0 */ /* step jrp over overall elements or those with * jrp->ae_statp already used */ while (jrp->enc_statp && ((-1 == jrp->indiv_i) || jrp->ae_statp)) ++jrp; if (NULL == jrp->enc_statp) { pr2serr("warning: %s: join_arr has no space for " "ae\n", __func__); return broken_ei; } jrp->ae_statp = ae_bp; ++jrp; } } /* end_for: loop over non-overall elements of the * current type descriptor header */ } else { /* element type _not_ relevant to ae status */ /* step jrp over overall and individual elements */ for (j = 0; j <= tdhp->num_elements; ++j, ++jrp) { if (NULL == jrp->enc_statp) { pr2serr("warning: %s: join_arr has no space\n", __func__); return broken_ei; } } } } /* end_for: loop over type descriptor headers */ return broken_ei; } /* User output of join array */ static void join_array_display(struct th_es_t * tesp, struct opts_t * op) { bool got1, need_aes; int k, j, blen, desc_len, dn_len; const uint8_t * ae_bp; const char * cp; const uint8_t * ed_bp; struct join_row_t * jrp; uint8_t * t_bp; char b[64]; blen = sizeof(b); need_aes = (op->page_code_given && (ADD_ELEM_STATUS_DPC == op->page_code)); dn_len = op->desc_name ? (int)strlen(op->desc_name) : 0; for (k = 0, jrp = tesp->j_base, got1 = false; ((k < MX_JOIN_ROWS) && jrp->enc_statp); ++k, ++jrp) { if (op->ind_given) { if (op->ind_th != jrp->th_i) continue; if (! match_ind_indiv(jrp->indiv_i, op)) continue; } if (need_aes && (NULL == jrp->ae_statp)) continue; ed_bp = jrp->elem_descp; if (op->desc_name) { if (NULL == ed_bp) continue; desc_len = sg_get_unaligned_be16(ed_bp + 2); /* some element descriptor strings have trailing NULLs and * count them in their length; adjust */ while (desc_len && ('\0' == ed_bp[4 + desc_len - 1])) --desc_len; if (desc_len != dn_len) continue; if (0 != strncmp(op->desc_name, (const char *)(ed_bp + 4), desc_len)) continue; } else if (op->dev_slot_num >= 0) { if (op->dev_slot_num != jrp->dev_slot_num) continue; } else if (saddr_non_zero(op->sas_addr)) { for (j = 0; j < 8; ++j) { if (op->sas_addr[j] != jrp->sas_addr[j]) break; } if (j < 8) continue; } got1 = true; if ((op->do_filter > 1) && (1 != (0xf & jrp->enc_statp[0]))) continue; /* when '-ff' and status!=OK, skip */ cp = etype_str(jrp->etype, b, blen); if (ed_bp) { desc_len = sg_get_unaligned_be16(ed_bp + 2) + 4; if (desc_len > 4) printf("%.*s [%d,%d] Element type: %s\n", desc_len - 4, (const char *)(ed_bp + 4), jrp->th_i, jrp->indiv_i, cp); else printf("[%d,%d] Element type: %s\n", jrp->th_i, jrp->indiv_i, cp); } else printf("[%d,%d] Element type: %s\n", jrp->th_i, jrp->indiv_i, cp); printf(" Enclosure Status:\n"); enc_status_helper(" ", jrp->enc_statp, jrp->etype, false, op); if (jrp->ae_statp) { printf(" Additional Element Status:\n"); ae_bp = jrp->ae_statp; desc_len = ae_bp[1] + 2; additional_elem_helper(" ", ae_bp, desc_len, jrp->etype, tesp, op); } if (jrp->thresh_inp) { t_bp = jrp->thresh_inp; threshold_helper(" Threshold In:\n", " ", t_bp, jrp->etype, op); } } if (! got1) { if (op->ind_given) { printf(" >>> no match on --index=%d,%d", op->ind_th, op->ind_indiv); if (op->ind_indiv_last > op->ind_indiv) printf("-%d\n", op->ind_indiv_last); else printf("\n"); } else if (op->desc_name) printf(" >>> no match on --descriptor=%s\n", op->desc_name); else if (op->dev_slot_num >= 0) printf(" >>> no match on --dev-slot-name=%d\n", op->dev_slot_num); else if (saddr_non_zero(op->sas_addr)) { printf(" >>> no match on --sas-addr=0x"); for (j = 0; j < 8; ++j) printf("%02x", op->sas_addr[j]); printf("\n"); } } } /* This is for debugging, output to stderr */ static void join_array_dump(struct th_es_t * tesp, int broken_ei, struct opts_t * op) { int k, j, blen, hex; int eiioe_count = 0; int eip_count = 0; struct join_row_t * jrp; char b[64]; blen = sizeof(b); hex = op->do_hex; pr2serr("Dump of join array, each line is a row. Lines start with\n"); pr2serr("[: ,]\n"); pr2serr("'-1' indicates overall element or not applicable.\n"); jrp = tesp->j_base; for (k = 0; ((k < MX_JOIN_ROWS) && jrp->enc_statp); ++k, ++jrp) { pr2serr("[0x%x: %d,%d] ", jrp->etype, jrp->th_i, jrp->indiv_i); if (jrp->se_id > 0) pr2serr("se_id=%d ", jrp->se_id); pr2serr("ei_ioe,_eoe,_aess=%s", offset_str(k, hex, b, blen)); pr2serr(",%s", offset_str(jrp->ei_eoe, hex, b, blen)); pr2serr(",%s", offset_str(jrp->ei_aess, hex, b, blen)); pr2serr(" dsn=%s", offset_str(jrp->dev_slot_num, hex, b, blen)); if (op->do_join > 2) { pr2serr(" sa=0x"); if (saddr_non_zero(jrp->sas_addr)) { for (j = 0; j < 8; ++j) pr2serr("%02x", jrp->sas_addr[j]); } else pr2serr("0"); } if (jrp->enc_statp) pr2serr(" ES+%s", offset_str(jrp->enc_statp - enc_stat_rsp, hex, b, blen)); if (jrp->elem_descp) pr2serr(" ED+%s", offset_str(jrp->elem_descp - elem_desc_rsp, hex, b, blen)); if (jrp->ae_statp) { pr2serr(" AES+%s", offset_str(jrp->ae_statp - add_elem_rsp, hex, b, blen)); if (jrp->ae_statp[0] & 0x10) { ++eip_count; if (jrp->ae_statp[2] & 0x3) ++eiioe_count; } } if (jrp->thresh_inp) pr2serr(" TI+%s", offset_str(jrp->thresh_inp - threshold_rsp, hex, b, blen)); pr2serr("\n"); } pr2serr(">> ES len=%s, ", offset_str(enc_stat_rsp_len, hex, b, blen)); pr2serr("ED len=%s, ", offset_str(elem_desc_rsp_len, hex, b, blen)); pr2serr("AES len=%s, ", offset_str(add_elem_rsp_len, hex, b, blen)); pr2serr("TI len=%s\n", offset_str(threshold_rsp_len, hex, b, blen)); pr2serr(">> join_arr elements=%s, ", offset_str(k, hex, b, blen)); pr2serr("eip_count=%s, ", offset_str(eip_count, hex, b, blen)); pr2serr("eiioe_count=%s ", offset_str(eiioe_count, hex, b, blen)); pr2serr("broken_ei=%d\n", (int)broken_ei); } /* EIIOE juggling (standards + heuristics) for join with AES page */ static void join_juggle_aes(struct th_es_t * tesp, uint8_t * es_bp, const uint8_t * ed_bp, uint8_t * t_bp) { int k, j, eoe, ei4aess; struct join_row_t * jrp; const struct type_desc_hdr_t * tdhp; jrp = tesp->j_base; tdhp = tesp->th_base; for (k = 0, eoe = 0, ei4aess = 0; k < tesp->num_ths; ++k, ++tdhp) { bool et_used_by_aes; jrp->th_i = k; jrp->indiv_i = -1; jrp->etype = tdhp->etype; jrp->ei_eoe = -1; et_used_by_aes = is_et_used_by_aes(tdhp->etype); jrp->ei_aess = -1; jrp->se_id = tdhp->se_id; /* check es_bp < es_last_bp still in range */ jrp->enc_statp = es_bp; es_bp += 4; jrp->elem_descp = ed_bp; if (ed_bp) ed_bp += sg_get_unaligned_be16(ed_bp + 2) + 4; jrp->ae_statp = NULL; jrp->thresh_inp = t_bp; jrp->dev_slot_num = -1; /* assume sas_addr[8] zeroed since it's static file scope */ if (t_bp) t_bp += 4; ++jrp; for (j = 0; j < tdhp->num_elements; ++j, ++jrp) { if (jrp >= join_arr_lastp) break; jrp->th_i = k; jrp->indiv_i = j; jrp->ei_eoe = eoe++; if (et_used_by_aes) jrp->ei_aess = ei4aess++; else jrp->ei_aess = -1; jrp->etype = tdhp->etype; jrp->se_id = tdhp->se_id; jrp->enc_statp = es_bp; es_bp += 4; jrp->elem_descp = ed_bp; if (ed_bp) ed_bp += sg_get_unaligned_be16(ed_bp + 2) + 4; jrp->thresh_inp = t_bp; jrp->dev_slot_num = -1; /* assume sas_addr[8] zeroed since it's static file scope */ if (t_bp) t_bp += 4; jrp->ae_statp = NULL; ++tesp->num_j_eoe; } if (jrp >= join_arr_lastp) { /* ++k; */ break; /* leave last row all zeros */ } } tesp->num_j_rows = jrp - tesp->j_base; } /* Fetch Configuration, Enclosure Status, Element Descriptor, Additional * Element Status and optionally Threshold In pages, place in static arrays. * Collate (join) overall and individual elements into the static join_arr[]. * When 'display' is true then the join_arr[] is output to stdout in a form * suitable for end users. For debug purposes the join_arr[] is output to * stderr when op->verbose > 3. Returns 0 for success, any other return value * is an error. */ static int join_work(struct sg_pt_base * ptvp, struct opts_t * op, bool display) { bool broken_ei; int res, num_ths, mlen; uint32_t ref_gen_code, gen_code; const uint8_t * ae_bp; const uint8_t * ae_last_bp; const char * enc_state_changed = " <>\n"; uint8_t * es_bp; const uint8_t * ed_bp; uint8_t * t_bp; struct th_es_t * tesp; struct enclosure_info primary_info; struct th_es_t tes; memset(&primary_info, 0, sizeof(primary_info)); num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr, MX_ELEM_HDR, &ref_gen_code, &primary_info, op); if (num_ths < 0) return num_ths; tesp = &tes; memset(tesp, 0, sizeof(tes)); tesp->th_base = type_desc_hdr_arr; tesp->num_ths = num_ths; if (display && primary_info.have_info) { int j; printf(" Primary enclosure logical identifier (hex): "); for (j = 0; j < 8; ++j) printf("%02x", primary_info.enc_log_id[j]); printf("\n"); } mlen = enc_stat_rsp_sz; if (mlen > op->maxlen) mlen = op->maxlen; res = do_rec_diag(ptvp, ENC_STATUS_DPC, enc_stat_rsp, mlen, op, &enc_stat_rsp_len); if (res) return res; if (enc_stat_rsp_len < 8) { pr2serr("Enclosure Status response too short\n"); return -1; } gen_code = sg_get_unaligned_be32(enc_stat_rsp + 4); if (ref_gen_code != gen_code) { pr2serr("%s", enc_state_changed); return -1; } es_bp = enc_stat_rsp + 8; /* es_last_bp = enc_stat_rsp + enc_stat_rsp_len - 1; */ mlen = elem_desc_rsp_sz; if (mlen > op->maxlen) mlen = op->maxlen; res = do_rec_diag(ptvp, ELEM_DESC_DPC, elem_desc_rsp, mlen, op, &elem_desc_rsp_len); if (0 == res) { if (elem_desc_rsp_len < 8) { pr2serr("Element Descriptor response too short\n"); return -1; } gen_code = sg_get_unaligned_be32(elem_desc_rsp + 4); if (ref_gen_code != gen_code) { pr2serr("%s", enc_state_changed); return -1; } ed_bp = elem_desc_rsp + 8; /* ed_last_bp = elem_desc_rsp + elem_desc_rsp_len - 1; */ } else { elem_desc_rsp_len = 0; ed_bp = NULL; res = 0; if (op->verbose) pr2serr(" Element Descriptor page not available\n"); } /* check if we want to add the AES page to the join */ if (display || (ADD_ELEM_STATUS_DPC == op->page_code) || (op->dev_slot_num >= 0) || saddr_non_zero(op->sas_addr)) { mlen = add_elem_rsp_sz; if (mlen > op->maxlen) mlen = op->maxlen; res = do_rec_diag(ptvp, ADD_ELEM_STATUS_DPC, add_elem_rsp, mlen, op, &add_elem_rsp_len); if (0 == res) { if (add_elem_rsp_len < 8) { pr2serr("Additional Element Status response too short\n"); return -1; } gen_code = sg_get_unaligned_be32(add_elem_rsp + 4); if (ref_gen_code != gen_code) { pr2serr("%s", enc_state_changed); return -1; } ae_bp = add_elem_rsp + 8; ae_last_bp = add_elem_rsp + add_elem_rsp_len - 1; if (op->eiioe_auto && (add_elem_rsp_len > 11)) { /* heuristic: if first AES descriptor has EIP set and its * EI equal to 1, then act as if the EIIOE field is 1. */ if ((ae_bp[0] & 0x10) && (1 == ae_bp[3])) op->eiioe_force = true; } } else { /* unable to read AES dpage */ add_elem_rsp_len = 0; ae_bp = NULL; ae_last_bp = NULL; res = 0; if (op->verbose) pr2serr(" Additional Element Status page not available\n"); } } else { ae_bp = NULL; ae_last_bp = NULL; } if ((op->do_join > 1) || ((! display) && (THRESHOLD_DPC == op->page_code))) { mlen = threshold_rsp_sz; if (mlen > op->maxlen) mlen = op->maxlen; res = do_rec_diag(ptvp, THRESHOLD_DPC, threshold_rsp, mlen, op, &threshold_rsp_len); if (0 == res) { if (threshold_rsp_len < 8) { pr2serr("Threshold In response too short\n"); return -1; } gen_code = sg_get_unaligned_be32(threshold_rsp + 4); if (ref_gen_code != gen_code) { pr2serr("%s", enc_state_changed); return -1; } t_bp = threshold_rsp + 8; /* t_last_bp = threshold_rsp + threshold_rsp_len - 1; */ } else { threshold_rsp_len = 0; t_bp = NULL; res = 0; if (op->verbose) pr2serr(" Threshold In page not available\n"); } } else { threshold_rsp_len = 0; t_bp = NULL; } tesp->j_base = join_arr; join_juggle_aes(tesp, es_bp, ed_bp, t_bp); broken_ei = false; if (ae_bp) broken_ei = join_aes_helper(ae_bp, ae_last_bp, tesp, op); if (op->verbose > 3) join_array_dump(tesp, broken_ei, op); join_done = true; if (display) /* probably wanted join_arr[] built only */ join_array_display(tesp, op); return res; } /* Returns 1 if strings equal (same length, characters same or only differ * by case), else returns 0. Assumes 7 bit ASCII (English alphabet). */ static int strcase_eq(const char * s1p, const char * s2p) { int c1; do { int c2; c1 = *s1p++; c2 = *s2p++; if (c1 != c2) { if (c2 >= 'a') c2 = toupper(c2); else if (c1 >= 'a') c1 = toupper(c1); else return 0; if (c1 != c2) return 0; } } while (c1); return 1; } static bool is_acronym_in_status_ctl(const struct tuple_acronym_val * tavp) { const struct acronym2tuple * ap; for (ap = ecs_a2t_arr; ap->acron; ++ ap) { if (strcase_eq(tavp->acron, ap->acron)) break; } return ap->acron; } static bool is_acronym_in_threshold(const struct tuple_acronym_val * tavp) { const struct acronym2tuple * ap; for (ap = th_a2t_arr; ap->acron; ++ ap) { if (strcase_eq(tavp->acron, ap->acron)) break; } return ap->acron; } static bool is_acronym_in_additional(const struct tuple_acronym_val * tavp) { const struct acronym2tuple * ap; for (ap = ae_sas_a2t_arr; ap->acron; ++ ap) { if (strcase_eq(tavp->acron, ap->acron)) break; } return ap->acron; } /* ENC_STATUS_DPC ENC_CONTROL_DPC * Do clear/get/set (cgs) on Enclosure Control/Status page. Return 0 for ok * -2 for acronym not found, else -1 . */ static int cgs_enc_ctl_stat(struct sg_pt_base * ptvp, struct join_row_t * jrp, const struct tuple_acronym_val * tavp, const struct opts_t * op, bool last) { int s_byte, s_bit, n_bits; const struct acronym2tuple * ap; if (NULL == tavp->acron) { s_byte = tavp->start_byte; s_bit = tavp->start_bit; n_bits = tavp->num_bits; } if (tavp->acron) { for (ap = ecs_a2t_arr; ap->acron; ++ ap) { if (((jrp->etype == ap->etype) || (-1 == ap->etype)) && strcase_eq(tavp->acron, ap->acron)) break; } if (ap->acron) { s_byte = ap->start_byte; s_bit = ap->start_bit; n_bits = ap->num_bits; } else { if (-1 != ap->etype) { for (ap = ecs_a2t_arr; ap->acron; ++ap) { if (0 == strcase_eq(tavp->acron, ap->acron)) { pr2serr(">>> Found %s acronym but not for element " "type %d\n", tavp->acron, jrp->etype); break; } } } return -2; } } if (op->verbose > 1) pr2serr(" s_byte=%d, s_bit=%d, n_bits=%d\n", s_byte, s_bit, n_bits); if (GET_OPT == tavp->cgs_sel) { uint64_t ui = sg_get_big_endian(jrp->enc_statp + s_byte, s_bit, n_bits); if (op->do_hex) printf("0x%" PRIx64 "\n", ui); else printf("%" PRId64 "\n", (int64_t)ui); } else { /* --set or --clear */ int len; if ((! op->mask_ign) && (jrp->etype < NUM_ETC)) { int k; if (op->verbose > 2) pr2serr("Applying mask to element status [etc=%d] prior to " "modify then write\n", jrp->etype); for (k = 0; k < 4; ++k) jrp->enc_statp[k] &= ses3_element_cmask_arr[jrp->etype][k]; } else jrp->enc_statp[0] &= 0x40; /* keep PRDFAIL is set in byte 0 */ /* next we modify requested bit(s) */ sg_set_big_endian((uint64_t)tavp->val, jrp->enc_statp + s_byte, s_bit, n_bits); jrp->enc_statp[0] |= 0x80; /* set SELECT bit */ if (op->byte1_given) enc_stat_rsp[1] = op->byte1; len = sg_get_unaligned_be16(enc_stat_rsp + 2) + 4; if (last) { int ret = do_senddiag(ptvp, enc_stat_rsp, len, ! op->quiet, op->verbose); if (ret) { pr2serr("couldn't send Enclosure Control page\n"); return -1; } } } return 0; } /* THRESHOLD_DPC * Do clear/get/set (cgs) on Threshold In/Out page. Return 0 for ok, * -2 for acronym not found, else -1 . */ static int cgs_threshold(struct sg_pt_base * ptvp, const struct join_row_t * jrp, const struct tuple_acronym_val * tavp, const struct opts_t * op, bool last) { int s_byte, s_bit, n_bits; const struct acronym2tuple * ap; if (NULL == jrp->thresh_inp) { pr2serr("No Threshold In/Out element available\n"); return -1; } if (NULL == tavp->acron) { s_byte = tavp->start_byte; s_bit = tavp->start_bit; n_bits = tavp->num_bits; } if (tavp->acron) { for (ap = th_a2t_arr; ap->acron; ++ap) { if (((jrp->etype == ap->etype) || (-1 == ap->etype)) && strcase_eq(tavp->acron, ap->acron)) break; } if (ap->acron) { s_byte = ap->start_byte; s_bit = ap->start_bit; n_bits = ap->num_bits; } else return -2; } if (GET_OPT == tavp->cgs_sel) { uint64_t ui = sg_get_big_endian(jrp->thresh_inp + s_byte, s_bit, n_bits); if (op->do_hex) printf("0x%" PRIx64 "\n", ui); else printf("%" PRId64 "\n", (int64_t)ui); } else { int len; sg_set_big_endian((uint64_t)tavp->val, jrp->thresh_inp + s_byte, s_bit, n_bits); if (op->byte1_given) threshold_rsp[1] = op->byte1; len = sg_get_unaligned_be16(threshold_rsp + 2) + 4; if (last) { int ret = do_senddiag(ptvp, threshold_rsp, len, ! op->quiet, op->verbose); if (ret) { pr2serr("couldn't send Threshold Out page\n"); return -1; } } } return 0; } /* ADD_ELEM_STATUS_DPC * Do get (cgs) on Additional element status page. Return 0 for ok, * -2 for acronym not found, else -1 . */ static int cgs_additional_el(const struct join_row_t * jrp, const struct tuple_acronym_val * tavp, const struct opts_t * op) { int s_byte, s_bit, n_bits; const struct acronym2tuple * ap; if (NULL == jrp->ae_statp) { pr2serr("No additional element status element available\n"); return -1; } if (NULL == tavp->acron) { s_byte = tavp->start_byte; s_bit = tavp->start_bit; n_bits = tavp->num_bits; } if (tavp->acron) { for (ap = ae_sas_a2t_arr; ap->acron; ++ap) { if (((jrp->etype == ap->etype) || (-1 == ap->etype)) && strcase_eq(tavp->acron, ap->acron)) break; } if (ap->acron) { s_byte = ap->start_byte; s_bit = ap->start_bit; n_bits = ap->num_bits; } else return -2; } if (GET_OPT == tavp->cgs_sel) { uint64_t ui = sg_get_big_endian(jrp->ae_statp + s_byte, s_bit, n_bits); if (op->do_hex) printf("0x%" PRIx64 "\n", ui); else printf("%" PRId64 "\n", (int64_t)ui); } else { pr2serr("--clear and --set not available for Additional Element " "Status page\n"); return -1; } return 0; } /* Do --clear, --get or --set . * Returns 0 for success, any other return value is an error. */ static int ses_cgs(struct sg_pt_base * ptvp, const struct tuple_acronym_val * tavp, struct opts_t * op, bool last) { int ret, k, j, desc_len, dn_len; bool found; struct join_row_t * jrp; const uint8_t * ed_bp; char b[64]; if ((NULL == ptvp) && (GET_OPT != tavp->cgs_sel)) { pr2serr("%s: --clear= and --set= only supported when DEVICE is " "given\n", __func__); return SG_LIB_CONTRADICT; } found = false; if (NULL == tavp->acron) { if (! op->page_code_given) op->page_code = ENC_CONTROL_DPC; found = true; } else if (is_acronym_in_status_ctl(tavp)) { if (op->page_code > 0) { if (ENC_CONTROL_DPC != op->page_code) goto inconsistent; } else op->page_code = ENC_CONTROL_DPC; found = true; } else if (is_acronym_in_threshold(tavp)) { if (op->page_code > 0) { if (THRESHOLD_DPC != op->page_code) goto inconsistent; } else op->page_code = THRESHOLD_DPC; found = true; } else if (is_acronym_in_additional(tavp)) { if (op->page_code > 0) { if (ADD_ELEM_STATUS_DPC != op->page_code) goto inconsistent; } else op->page_code = ADD_ELEM_STATUS_DPC; found = true; } if (! found) { pr2serr("acroynm %s not found (try '-ee' option)\n", tavp->acron); return -1; } if (false == join_done) { ret = join_work(ptvp, op, false); if (ret) return ret; } dn_len = op->desc_name ? (int)strlen(op->desc_name) : 0; for (k = 0, jrp = join_arr; ((k < MX_JOIN_ROWS) && jrp->enc_statp); ++k, ++jrp) { if (op->ind_given) { if (op->ind_th != jrp->th_i) continue; if (! match_ind_indiv(jrp->indiv_i, op)) continue; } else if (op->desc_name) { ed_bp = jrp->elem_descp; if (NULL == ed_bp) continue; desc_len = sg_get_unaligned_be16(ed_bp + 2); /* some element descriptor strings have trailing NULLs and * count them; adjust */ while (desc_len && ('\0' == ed_bp[4 + desc_len - 1])) --desc_len; if (desc_len != dn_len) continue; if (0 != strncmp(op->desc_name, (const char *)(ed_bp + 4), desc_len)) continue; } else if (op->dev_slot_num >= 0) { if (op->dev_slot_num != jrp->dev_slot_num) continue; } else if (saddr_non_zero(op->sas_addr)) { for (j = 0; j < 8; ++j) { if (op->sas_addr[j] != jrp->sas_addr[j]) break; } if (j < 8) continue; } if (ENC_CONTROL_DPC == op->page_code) ret = cgs_enc_ctl_stat(ptvp, jrp, tavp, op, last); else if (THRESHOLD_DPC == op->page_code) ret = cgs_threshold(ptvp, jrp, tavp, op, last); else if (ADD_ELEM_STATUS_DPC == op->page_code) ret = cgs_additional_el(jrp, tavp, op); else { pr2serr("page %s not supported for cgs\n", etype_str(op->page_code, b, sizeof(b))); ret = -1; } if (ret) return ret; if (op->ind_indiv_last <= op->ind_indiv) break; } /* end of loop over join array */ if ((k >= MX_JOIN_ROWS || (NULL == jrp->enc_statp))) { if (op->desc_name) pr2serr("descriptor name: %s not found (check the 'ed' page " "[0x7])\n", op->desc_name); else if (op->dev_slot_num >= 0) pr2serr("device slot number: %d not found\n", op->dev_slot_num); else if (saddr_non_zero(op->sas_addr)) pr2serr("SAS address not found\n"); else { pr2serr("index: %d,%d", op->ind_th, op->ind_indiv); if (op->ind_indiv_last > op->ind_indiv) printf("-%d not found\n", op->ind_indiv_last); else printf(" not found\n"); } return -1; } return 0; inconsistent: pr2serr("acroynm %s inconsistent with page_code=0x%x\n", tavp->acron, op->page_code); return -1; } /* Called when '--nickname=SEN' given. First calls status page to fetch * the generation code. Returns 0 for success, any other return value is * an error. */ static int ses_set_nickname(struct sg_pt_base * ptvp, struct opts_t * op) { int res, len; int resp_len = 0; uint8_t b[64]; const int control_plen = 0x24; if (NULL == ptvp) { pr2serr("%s: ignored when no device name\n", __func__); return 0; } memset(b, 0, sizeof(b)); /* Only after the generation code, offset 4 for 4 bytes */ res = do_rec_diag(ptvp, SUBENC_NICKNAME_DPC, b, 8, op, &resp_len); if (res) { pr2serr("%s: Subenclosure nickname status page, res=%d\n", __func__, res); return -1; } if (resp_len < 8) { pr2serr("%s: Subenclosure nickname status page, response length too " "short: %d\n", __func__, resp_len); return -1; } if (op->verbose) { uint32_t gc; gc = sg_get_unaligned_be32(b + 4); pr2serr("%s: generation code from status page: %" PRIu32 "\n", __func__, gc); } b[0] = (uint8_t)SUBENC_NICKNAME_DPC; /* just in case */ b[1] = (uint8_t)op->seid; sg_put_unaligned_be16((uint16_t)control_plen, b + 2); len = strlen(op->nickname_str); if (len > 32) len = 32; memcpy(b + 8, op->nickname_str, len); return do_senddiag(ptvp, b, control_plen + 4, ! op->quiet, op->verbose); } static void enumerate_diag_pages(void) { bool got1; const struct diag_page_code * pcdp; const struct diag_page_abbrev * ap; printf("Diagnostic pages, followed by abbreviation(s) then page code:\n"); for (pcdp = dpc_arr; pcdp->desc; ++pcdp) { printf(" %s [", pcdp->desc); for (ap = dp_abbrev, got1 = false; ap->abbrev; ++ap) { if (ap->page_code == pcdp->page_code) { printf("%s%s", (got1 ? "," : ""), ap->abbrev); got1 = true; } } printf("] [0x%x]\n", pcdp->page_code); } } /* Output from --enumerate or --list option. Note that the output is * different when the option is given twice. */ static void enumerate_work(const struct opts_t * op) { int num; if (op->dev_name) printf(">>> DEVICE %s ignored when --%s option given.\n", op->dev_name, (op->do_list ? "list" : "enumerate")); num = op->enumerate + (int)op->do_list; if (num < 2) { const struct element_type_t * etp; enumerate_diag_pages(); printf("\nSES element type names, followed by abbreviation and " "element type code:\n"); for (etp = element_type_arr; etp->desc; ++etp) printf(" %s [%s] [0x%x]\n", etp->desc, etp->abbrev, etp->elem_type_code); } else { bool given_et = false; const struct acronym2tuple * ap; const char * cp; char a[160]; char b[64]; char bb[64]; /* command line has multiple --enumerate and/or --list options */ printf("--clear, --get, --set acronyms for Enclosure Status/Control " "['es' or 'ec'] page"); if (op->ind_given && op->ind_etp && (cp = etype_str(op->ind_etp->elem_type_code, bb, sizeof(bb)))) { printf("\n(element type: %s)", cp); given_et = true; } printf(":\n"); for (ap = ecs_a2t_arr; ap->acron; ++ap) { if (given_et && (op->ind_etp->elem_type_code != ap->etype)) continue; cp = (ap->etype < 0) ? "*" : etype_str(ap->etype, b, sizeof(b)); snprintf(a, sizeof(a), " %s [%s] [%d:%d:%d]", ap->acron, (cp ? cp : "??"), ap->start_byte, ap->start_bit, ap->num_bits); if (ap->info) printf("%-44s %s\n", a, ap->info); else printf("%s\n", a); } if (given_et) return; printf("\n--clear, --get, --set acronyms for Threshold In/Out " "['th'] page:\n"); for (ap = th_a2t_arr; ap->acron; ++ap) { cp = (ap->etype < 0) ? "*" : etype_str(ap->etype, b, sizeof(b)); snprintf(a, sizeof(a), " %s [%s] [%d:%d:%d]", ap->acron, (cp ? cp : "??"), ap->start_byte, ap->start_bit, ap->num_bits); if (ap->info) printf("%-34s %s\n", a, ap->info); else printf("%s\n", a); } printf("\n--get acronyms for Additional Element Status ['aes'] page " "(SAS EIP=1):\n"); for (ap = ae_sas_a2t_arr; ap->acron; ++ap) { cp = (ap->etype < 0) ? "*" : etype_str(ap->etype, b, sizeof(b)); snprintf(a, sizeof(a), " %s [%s] [%d:%d:%d]", ap->acron, (cp ? cp : "??"), ap->start_byte, ap->start_bit, ap->num_bits); if (ap->info) printf("%-34s %s\n", a, ap->info); else printf("%s\n", a); } } } int main(int argc, char * argv[]) { bool have_cgs = false; int k, n, d_len, res, resid, vb; int sg_fd = -1; int pd_type = 0; int ret = 0; const char * cp; struct opts_t opts; struct opts_t * op; struct tuple_acronym_val * tavp; struct cgs_cl_t * cgs_clp; uint8_t * free_enc_stat_rsp = NULL; uint8_t * free_elem_desc_rsp = NULL; uint8_t * free_add_elem_rsp = NULL; uint8_t * free_threshold_rsp = NULL; struct sg_pt_base * ptvp = NULL; struct tuple_acronym_val tav_arr[CGS_CL_ARR_MAX_SZ]; char buff[128]; char b[128]; op = &opts; memset(op, 0, sizeof(*op)); op->dev_slot_num = -1; op->ind_indiv_last = -1; op->maxlen = MX_ALLOC_LEN; res = parse_cmd_line(op, argc, argv); vb = op->verbose; if (res) { ret = SG_LIB_SYNTAX_ERROR; goto early_out; } if (op->do_help) { usage(op->do_help); goto early_out; } #ifdef DEBUG pr2serr("In DEBUG mode, "); if (op->verbose_given && op->version_given) { pr2serr("but override: '-vV' given, zero verbose and continue\n"); op->verbose_given = false; op->version_given = false; op->verbose = 0; } else if (! op->verbose_given) { pr2serr("set '-vv'\n"); op->verbose = 2; } else pr2serr("keep verbose=%d\n", op->verbose); #else if (op->verbose_given && op->version_given) pr2serr("Not in DEBUG mode, so '-vV' has no special action\n"); #endif if (op->version_given) { pr2serr("version: %s\n", version_str); goto early_out; } vb = op->verbose; /* may have changed */ if (op->enumerate || op->do_list) { enumerate_work(op); goto early_out; } enc_stat_rsp = sg_memalign(op->maxlen, 0, &free_enc_stat_rsp, false); if (NULL == enc_stat_rsp) { pr2serr("Unable to get heap for enc_stat_rsp\n"); goto err_out; } enc_stat_rsp_sz = op->maxlen; elem_desc_rsp = sg_memalign(op->maxlen, 0, &free_elem_desc_rsp, false); if (NULL == elem_desc_rsp) { pr2serr("Unable to get heap for elem_desc_rsp\n"); goto err_out; } elem_desc_rsp_sz = op->maxlen; add_elem_rsp = sg_memalign(op->maxlen, 0, &free_add_elem_rsp, false); if (NULL == add_elem_rsp) { pr2serr("Unable to get heap for add_elem_rsp\n"); goto err_out; } add_elem_rsp_sz = op->maxlen; threshold_rsp = sg_memalign(op->maxlen, 0, &free_threshold_rsp, false); if (NULL == threshold_rsp) { pr2serr("Unable to get heap for threshold_rsp\n"); goto err_out; } threshold_rsp_sz = op->maxlen; if (op->num_cgs) { have_cgs = true; if (op->page_code_given && ! ((ENC_STATUS_DPC == op->page_code) || (THRESHOLD_DPC == op->page_code) || (ADD_ELEM_STATUS_DPC == op->page_code))) { pr2serr("--clear, --get or --set options only supported for the " "Enclosure\nControl/Status, Threshold In/Out and " "Additional Element Status pages\n"); ret = SG_LIB_SYNTAX_ERROR; goto err_out; } if (! (op->ind_given || op->desc_name || (op->dev_slot_num >= 0) || saddr_non_zero(op->sas_addr))) { pr2serr("with --clear, --get or --set option need either\n " "--index, --descriptor, --dev-slot-num or --sas-addr\n"); ret = SG_LIB_CONTRADICT; goto err_out; } for (k = 0, cgs_clp = op->cgs_cl_arr, tavp = tav_arr; k < op->num_cgs; ++k, ++cgs_clp, ++tavp) { if (parse_cgs_str(cgs_clp->cgs_str, tavp)) { pr2serr("unable to decode STR argument to: %s\n", cgs_clp->cgs_str); ret = SG_LIB_SYNTAX_ERROR; goto err_out; } if ((GET_OPT == cgs_clp->cgs_sel) && tavp->val_str) pr2serr("--get option ignoring = at the end of STR " "argument\n"); if (NULL == tavp->val_str) { if (CLEAR_OPT == cgs_clp->cgs_sel) tavp->val = DEF_CLEAR_VAL; if (SET_OPT == cgs_clp->cgs_sel) tavp->val = DEF_SET_VAL; } if (!strcmp(cgs_clp->cgs_str, "sas_addr") && op->dev_slot_num < 0) { pr2serr("--get=sas_addr requires --dev-slot-num. For " "expander SAS address, use exp_sas_addr instead.\n"); ret = SG_LIB_SYNTAX_ERROR; goto err_out; } tavp->cgs_sel = cgs_clp->cgs_sel; } /* keep this descending for loop directly after ascending for loop */ for (--k, --cgs_clp; k >= 0; --k, --cgs_clp) { if ((CLEAR_OPT == cgs_clp->cgs_sel) || (SET_OPT == cgs_clp->cgs_sel)) { cgs_clp->last_cs = true; break; } } } #ifdef SG_LIB_WIN32 #ifdef SG_LIB_WIN32_DIRECT if (vb > 4) pr2serr("Initial win32 SPT interface state: %s\n", scsi_pt_win32_spt_state() ? "direct" : "indirect"); if (op->maxlen >= 16384) scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */); #endif #endif #if 0 pr2serr("Debug dump of input parameters:\n"); pr2serr(" index option given: %d, ind_th=%d, ind_indiv=%d, " "ind_indiv_last=%d\n", op->ind_given, op->ind_th, op->ind_indiv, op->ind_indiv_last); pr2serr(" num_cgs=%d, contents:\n", op->num_cgs); for (k = 0, tavp = tav_arr, cgs_clp = op->cgs_cl_arr; k < op->num_cgs; ++k, ++tavp, ++cgs_clp) { pr2serr(" k=%d, cgs_sel=%d, last_cs=%d, tavp=%p str: %s\n", k, (int)cgs_clp->cgs_sel, (int)cgs_clp->last_cs, tavp, cgs_clp->cgs_str); } #endif if (op->dev_name) { sg_fd = sg_cmds_open_device(op->dev_name, op->o_readonly, vb); if (sg_fd < 0) { if (vb) pr2serr("open error: %s: %s\n", op->dev_name, safe_strerror(-sg_fd)); ret = sg_convert_errno(-sg_fd); goto early_out; } ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb); if (NULL == ptvp) { pr2serr("construct pt_base failed, probably out of memory\n"); ret = sg_convert_errno(ENOMEM); goto err_out; } if (! (op->do_raw || have_cgs || (op->do_hex > 2))) { uint8_t inq_rsp[36]; memset(inq_rsp, 0, sizeof(inq_rsp)); if ((ret = sg_ll_inquiry_pt(ptvp, false, 0, inq_rsp, 36, 0, &resid, ! op->quiet, vb))) { pr2serr("%s doesn't respond to a SCSI INQUIRY\n", op->dev_name); goto err_out; } else { if (resid > 0) pr2serr("Short INQUIRY response, not looking good\n"); printf(" %.8s %.16s %.4s\n", inq_rsp + 8, inq_rsp + 16, inq_rsp + 32); pd_type = PDT_MASK & inq_rsp[0]; cp = sg_get_pdt_str(pd_type, sizeof(buff), buff); if (0xd == pd_type) { if (vb) printf(" enclosure services device\n"); } else if (0x40 & inq_rsp[6]) printf(" %s device has EncServ bit set\n", cp); else { if (0 != memcmp("NVMe", inq_rsp + 8, 4)) printf(" %s device (not an enclosure)\n", cp); } } clear_scsi_pt_obj(ptvp); } } else if (op->do_control) { pr2serr("Cannot do SCSI Send diagnostic command without a DEVICE\n"); return SG_LIB_SYNTAX_ERROR; } #if (HAVE_NVME && (! IGNORE_NVME)) if (ptvp && pt_device_is_nvme(ptvp) && (enc_stat_rsp_sz > 4095)) { /* Fetch VPD 0xde (vendor specific: sg3_utils) for Identify ctl */ ret = sg_ll_inquiry_pt(ptvp, true, 0xde, enc_stat_rsp, 4096, 0, &resid, ! op->quiet, vb); if (ret) { if (vb) pr2serr("Fetch VPD page 0xde (NVMe Identify ctl) failed, " "continue\n"); } else if (resid > 0) { if (vb) pr2serr("VPD page 0xde (NVMe Identify ctl) less than 4096 " "bytes, continue\n"); } else { uint8_t nvmsr; uint16_t oacs; nvmsr = enc_stat_rsp[253]; oacs = sg_get_unaligned_le16(enc_stat_rsp + 256); /* N.B. LE */ if (vb > 3) pr2serr("NVMe Identify ctl response: nvmsr=%u, oacs=0x%x\n", nvmsr, oacs); if (! ((0x2 & nvmsr) && (0x40 & oacs))) { pr2serr(">>> Warning: A NVMe enclosure needs both the " "enclosure bit and support for\n"); pr2serr(">>> MI Send+Receive commands bit set; current " "state: %s, %s\n", (0x2 & nvmsr) ? "set" : "clear", (0x40 & oacs) ? "set" : "clear"); } } clear_scsi_pt_obj(ptvp); memset(enc_stat_rsp, 0, enc_stat_rsp_sz); } #endif if (ptvp) { n = (enc_stat_rsp_sz < REQUEST_SENSE_RESP_SZ) ? enc_stat_rsp_sz : REQUEST_SENSE_RESP_SZ; ret = sg_ll_request_sense_pt(ptvp, false, enc_stat_rsp, n, ! op->quiet, vb); if (0 == ret) { int sense_len = n - get_scsi_pt_resid(ptvp); struct sg_scsi_sense_hdr ssh; if ((sense_len > 7) && sg_scsi_normalize_sense(enc_stat_rsp, sense_len, &ssh)) { const char * aa_str = sg_get_asc_ascq_str(ssh.asc, ssh.ascq, sizeof(b), b); /* Ignore the possibility that multiple UAs queued up */ if (SPC_SK_UNIT_ATTENTION == ssh.sense_key) pr2serr("Unit attention detected: %s\n ... continue\n", aa_str); else { if (vb) { pr2serr("Request Sense near startup detected " "something:\n"); pr2serr(" Sense key: %s, additional: %s\n ... " "continue\n", sg_get_sense_key_str(ssh.sense_key, sizeof(buff), buff), aa_str); } } } } else { if (vb) pr2serr("Request sense failed (res=%d), most likely " " problems ahead\n", ret); } clear_scsi_pt_obj(ptvp); memset(enc_stat_rsp, 0, enc_stat_rsp_sz); } if (op->nickname_str) ret = ses_set_nickname(ptvp, op); else if (have_cgs) { for (k = 0, tavp = tav_arr, cgs_clp = op->cgs_cl_arr; k < op->num_cgs; ++k, ++tavp, ++cgs_clp) { ret = ses_cgs(ptvp, tavp, op, cgs_clp->last_cs); if (ret) break; } } else if (op->do_join) ret = join_work(ptvp, op, true); else if (op->do_status) ret = process_status_page_s(ptvp, op); else { /* control page requested */ op->data_arr[0] = op->page_code; op->data_arr[1] = op->byte1; d_len = op->arr_len + DATA_IN_OFF; sg_put_unaligned_be16((uint16_t)op->arr_len, op->data_arr + 2); switch (op->page_code) { case ENC_CONTROL_DPC: /* Enclosure Control diagnostic page [0x2] */ printf("Sending Enclosure Control [0x%x] page, with page " "length=%d bytes\n", op->page_code, op->arr_len); ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb); if (ret) { pr2serr("couldn't send Enclosure Control page\n"); goto err_out; } break; case STRING_DPC: /* String Out diagnostic page [0x4] */ printf("Sending String Out [0x%x] page, with page length=%d " "bytes\n", op->page_code, op->arr_len); ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb); if (ret) { pr2serr("couldn't send String Out page\n"); goto err_out; } break; case THRESHOLD_DPC: /* Threshold Out diagnostic page [0x5] */ printf("Sending Threshold Out [0x%x] page, with page length=%d " "bytes\n", op->page_code, op->arr_len); ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb); if (ret) { pr2serr("couldn't send Threshold Out page\n"); goto err_out; } break; case ARRAY_CONTROL_DPC: /* Array control diagnostic page [0x6] */ printf("Sending Array Control [0x%x] page, with page " "length=%d bytes\n", op->page_code, op->arr_len); ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb); if (ret) { pr2serr("couldn't send Array Control page\n"); goto err_out; } break; case SUBENC_STRING_DPC: /* Subenclosure String Out page [0xc] */ printf("Sending Subenclosure String Out [0x%x] page, with page " "length=%d bytes\n", op->page_code, op->arr_len); ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb); if (ret) { pr2serr("couldn't send Subenclosure String Out page\n"); goto err_out; } break; case DOWNLOAD_MICROCODE_DPC: /* Download Microcode Control [0xe] */ printf("Sending Download Microcode Control [0x%x] page, with " "page length=%d bytes\n", op->page_code, d_len); printf(" Perhaps it would be better to use the sg_ses_microcode " "utility\n"); ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb); if (ret) { pr2serr("couldn't send Download Microcode Control page\n"); goto err_out; } break; case SUBENC_NICKNAME_DPC: /* Subenclosure Nickname Control [0xf] */ printf("Sending Subenclosure Nickname Control [0x%x] page, with " "page length=%d bytes\n", op->page_code, d_len); ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb); if (ret) { pr2serr("couldn't send Subenclosure Nickname Control page\n"); goto err_out; } break; default: pr2serr("Setting SES control page 0x%x not supported by this " "utility\n", op->page_code); pr2serr("That can be done with the sg_senddiag utility with its " "'--raw=' option\n"); ret = SG_LIB_SYNTAX_ERROR; break; } } err_out: if (! op->do_status) { sg_get_category_sense_str(ret, sizeof(b), b, vb); pr2serr(" %s\n", b); } if (free_enc_stat_rsp) free(free_enc_stat_rsp); if (free_elem_desc_rsp) free(free_elem_desc_rsp); if (free_add_elem_rsp) free(free_add_elem_rsp); if (free_threshold_rsp) free(free_threshold_rsp); early_out: if (sg_fd >= 0) { res = sg_cmds_close_device(sg_fd); if (res < 0) { pr2serr("close error: %s\n", safe_strerror(-res)); if (0 == ret) ret = sg_convert_errno(-res); } } if (ptvp) destruct_scsi_pt_obj(ptvp); if ((0 == vb) && (! op->quiet)) { if (! sg_if_can2stderr("sg_ses failed: ", ret)) pr2serr("Some error occurred, try again with '-v' or '-vv' for " "more information\n"); else if ((SG_LIB_SYNTAX_ERROR == ret) && (0 == vb)) pr2serr("Add '-h' to command line for usage information\n"); } if (op->free_data_arr) free(op->free_data_arr); if (free_config_dp_resp) free(free_config_dp_resp); return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; }