| /* |
| * 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 <unistd.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <getopt.h> |
| #define __STDC_FORMAT_MACROS 1 |
| #include <inttypes.h> |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "sg_lib.h" |
| #include "sg_cmds_basic.h" |
| #include "sg_cmds_extra.h" |
| #include "sg_unaligned.h" |
| #include "sg_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 <acronym>[=<value>] or |
| * <start_byte>:<start_bit>[:<num_bits>][=<value>]. 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 <acronym> to <start_byte>:<start_bit>:<num_bits> 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] [<most options from " |
| "first form>]\n" |
| " sg_ses --inhex=FN --status [-rr] [<most options from " |
| "first form>]\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] [<most options from first " |
| "form>]\n" |
| " sg_ses -X FN -s [-rr] [<most options from first " |
| "form>]\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 '<start_byte>:<start_bit>" |
| "[:<num_bits>][=<val>]'\nor '<acronym>[=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 <num> for '--index', expect " |
| "<num> 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 <num> */ |
| 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 <num> for '--index', " |
| "expect <num> 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: <acronym>[=<val>]\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("<start_byte> 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("<start_bit> 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("<num_bits> 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(" <<<ses_configuration_sdg: response too short>>>\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: <reserved>\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(" <<state of enclosure changed, please try again>>\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(" <<<enc: response too short>>>\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(" <<state of enclosure changed, please try again>>\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(" <<<arr: response too short>>>\n"); |
| return; |
| } |
| |
| static char * |
| reserved_or_num(char * buff, int buff_len, int num, int reserve_num) |
| { |
| if (num == reserve_num) |
| strncpy(buff, "<res>", 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, "<vendor>"); |
| else |
| snprintf(b, sizeof(b), "%d", tp[2]); |
| printf("%slow warning=%s, ", pad, b); |
| if (0 == tp[3]) |
| strcpy(b, "<vendor>"); |
| 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(" <<state of enclosure changed, please try again>>\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(" <<<thresh: response too short>>>\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(" <<state of enclosure changed, please try again>>\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: <empty>\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: <empty>\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(" <<<element: response too short>>>\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(" <<state of enclosure changed, please try again>>\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(" <<<additional: response too short>>>\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(" <empty>\n"); |
| } |
| return; |
| truncated: |
| pr2serr(" <<<subenc: response too short>>>\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(" <empty>\n"); |
| } |
| return; |
| truncated: |
| pr2serr(" <<<subence str: response too short>>>\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(" <<<subence str: response too short>>>\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(" <unknown> [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(" <<<download: response too short>>>\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(" <empty>\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(" <empty>\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("[<element_type>: <type_hdr_index>,<elem_ind_within>]\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 = " <<state of enclosure changed, " |
| "please try again>>\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 =<val> 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; |
| } |