diff options
Diffstat (limited to 'src/sg_ses.c')
-rw-r--r-- | src/sg_ses.c | 5986 |
1 files changed, 5986 insertions, 0 deletions
diff --git a/src/sg_ses.c b/src/sg_ses.c new file mode 100644 index 00000000..6ac26e8b --- /dev/null +++ b/src/sg_ses.c @@ -0,0 +1,5986 @@ +/* + * 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 strategies: + * [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; +} |