| /* |
| * Copyright (c) 2004-2016 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. |
| */ |
| |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.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" /* needed for scsi_pt_win32_direct() */ |
| #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.07 20160201"; /* ses3r08->11 */ |
| |
| #define MX_ALLOC_LEN ((64 * 1024) - 4) /* max allowable for big enclosures */ |
| #define MX_ELEM_HDR 1024 |
| #define MX_DATA_IN 2048 |
| #define MX_JOIN_ROWS 260 |
| #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 */ |
| #define DPC_SUPPORTED 0x0 |
| #define DPC_CONFIGURATION 0x1 |
| #define DPC_ENC_CONTROL 0x2 |
| #define DPC_ENC_STATUS 0x2 |
| #define DPC_HELP_TEXT 0x3 |
| #define DPC_STRING 0x4 |
| #define DPC_THRESHOLD 0x5 |
| #define DPC_ARRAY_CONTROL 0x6 /* obsolete */ |
| #define DPC_ARRAY_STATUS 0x6 /* obsolete */ |
| #define DPC_ELEM_DESC 0x7 |
| #define DPC_SHORT_ENC_STATUS 0x8 |
| #define DPC_ENC_BUSY 0x9 |
| #define DPC_ADD_ELEM_STATUS 0xa |
| #define DPC_SUBENC_HELP_TEXT 0xb |
| #define DPC_SUBENC_STRING 0xc |
| #define DPC_SUPPORTED_SES 0xd |
| #define DPC_DOWNLOAD_MICROCODE 0xe |
| #define DPC_SUBENC_NICKNAME 0xf |
| |
| /* 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_ELECTRONICS_ETC 0x7 |
| #define SCC_CELECTR_ETC 0x8 |
| #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 NUM_ETC (LAST_ETC + 1) |
| |
| |
| struct element_type_t { |
| int elem_type_code; |
| const char * abbrev; |
| const char * desc; |
| }; |
| |
| struct opts_t { |
| int byte1; |
| int byte1_given; |
| int do_control; |
| int do_data; |
| int dev_slot_num; |
| int enumerate; |
| int eiioe_auto; |
| int eiioe_force; |
| int do_filter; |
| int do_help; |
| int do_hex; |
| int ind_given; |
| int ind_th; /* type header index */ |
| int ind_indiv; /* individual element index; -1 for overall */ |
| int ind_et_inst; /* ETs can have multiple type header instances */ |
| int inner_hex; |
| int do_join; |
| int do_list; |
| int mask_ign; /* element read-mask-modify-write actions */ |
| int maxlen; |
| int seid; |
| int seid_given; |
| int page_code; |
| int page_code_given; |
| int do_raw; |
| int o_readonly; |
| int do_status; |
| int verbose; |
| int do_version; |
| int warn; |
| int num_cgs; |
| int arr_len; |
| unsigned char sas_addr[8]; |
| unsigned char data_arr[MX_DATA_IN + 16]; |
| const char * clear_str; |
| const char * desc_name; |
| const char * get_str; |
| const char * set_str; |
| const char * dev_name; |
| const char * index_str; |
| const char * nickname_str; |
| const struct element_type_t * ind_etp; |
| }; |
| |
| 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 { |
| unsigned char etype; /* element type code (0: unspecified) */ |
| unsigned char num_elements; /* number of possible elements, excluding |
| * overall element */ |
| unsigned char se_id; /* subenclosure id (0 for primary enclosure) */ |
| unsigned char 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. */ |
| struct join_row_t { |
| int el_ind_th; /* type header index (origin 0) */ |
| int el_ind_indiv; /* individual element index, -1 for overall |
| * instance, otherwise origin 0 */ |
| unsigned char etype; /* element type */ |
| unsigned char se_id; /* subenclosure id (0 for primary enclosure) */ |
| int ei_asc; /* element index used by Additional Element |
| * Status page, -1 for not applicable */ |
| int ei_asc2; /* some vendors get ei_asc wrong, this is |
| * their broken version */ |
| /* following point into Element Descriptor, Enclosure Status, Threshold |
| * In and Additional element status diagnostic pages. enc_statp only |
| * NULL past last, other pointers can be NULL . */ |
| unsigned char * elem_descp; |
| unsigned char * enc_statp; /* NULL indicates past last */ |
| unsigned char * thresh_inp; |
| unsigned char * add_elem_statp; |
| int dev_slot_num; /* if not available, set to -1 */ |
| unsigned char sas_addr[8]; /* if not available, set to 0 */ |
| }; |
| |
| /* Representation of <acronym>[=<value>] or |
| * <start_byte>:<start_bit>[:<num_bits>][=<value>]. */ |
| struct tuple_acronym_val { |
| const char * acron; |
| const char * val_str; |
| 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. */ |
| 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 (MSB or rightmost in SES drafts) to 0 (LSB) */ |
| int num_bits; /* usually 1 */ |
| 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 */ |
| unsigned char enc_log_id[8]; /* 8 byte NAA */ |
| unsigned char enc_vendor_id[8]; /* may differ from INQUIRY response */ |
| unsigned char product_id[16]; /* may differ from INQUIRY response */ |
| unsigned char product_rev_level[4]; /* may differ from INQUIRY response */ |
| }; |
| |
| |
| static struct type_desc_hdr_t type_desc_hdr_arr[MX_ELEM_HDR]; |
| |
| static struct join_row_t join_arr[MX_JOIN_ROWS]; |
| static struct join_row_t * join_arr_lastp = join_arr + MX_JOIN_ROWS - 1; |
| |
| #ifdef SG_LIB_FREEBSD |
| |
| #include <sys/param.h> /* contains PAGE_SIZE */ |
| |
| static unsigned char enc_stat_rsp[MX_ALLOC_LEN] |
| __attribute__ ((aligned (PAGE_SIZE))); |
| static unsigned char elem_desc_rsp[MX_ALLOC_LEN] |
| __attribute__ ((aligned (PAGE_SIZE))); |
| static unsigned char add_elem_rsp[MX_ALLOC_LEN] |
| __attribute__ ((aligned (PAGE_SIZE))); |
| static unsigned char threshold_rsp[MX_ALLOC_LEN] |
| __attribute__ ((aligned (PAGE_SIZE))); |
| |
| #else |
| |
| static unsigned char enc_stat_rsp[MX_ALLOC_LEN]; |
| static unsigned char elem_desc_rsp[MX_ALLOC_LEN]; |
| static unsigned char add_elem_rsp[MX_ALLOC_LEN]; |
| static unsigned char threshold_rsp[MX_ALLOC_LEN]; |
| |
| #endif |
| |
| 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[] = { |
| {DPC_SUPPORTED, "Supported Diagnostic Pages"}, /* 0 */ |
| {DPC_CONFIGURATION, "Configuration (SES)"}, |
| {DPC_ENC_STATUS, "Enclosure Status/Control (SES)"}, |
| {DPC_HELP_TEXT, "Help Text (SES)"}, |
| {DPC_STRING, "String In/Out (SES)"}, |
| {DPC_THRESHOLD, "Threshold In/Out (SES)"}, |
| {DPC_ARRAY_STATUS, "Array Status/Control (SES, obsolete)"}, |
| {DPC_ELEM_DESC, "Element Descriptor (SES)"}, |
| {DPC_SHORT_ENC_STATUS, "Short Enclosure Status (SES)"}, /* 8 */ |
| {DPC_ENC_BUSY, "Enclosure Busy (SES-2)"}, |
| {DPC_ADD_ELEM_STATUS, "Additional Element Status (SES-2)"}, |
| {DPC_SUBENC_HELP_TEXT, "Subenclosure Help Text (SES-2)"}, |
| {DPC_SUBENC_STRING, "Subenclosure String In/Out (SES-2)"}, |
| {DPC_SUPPORTED_SES, "Supported SES Diagnostic Pages (SES-2)"}, |
| {DPC_DOWNLOAD_MICROCODE, "Download Microcode (SES-2)"}, |
| {DPC_SUBENC_NICKNAME, "Subenclosure Nickname (SES-2)"}, |
| {0x3f, "Protocol Specific (SAS transport)"}, |
| {0x40, "Translate Address (SBC)"}, |
| {0x41, "Device Status (SBC)"}, |
| {0x42, "Rebuild Assist (SBC)"}, /* sbc3r31 */ |
| {-1, NULL}, |
| }; |
| |
| /* Diagnostic page names, for status (or in) pages */ |
| static struct diag_page_code in_dpc_arr[] = { |
| {DPC_SUPPORTED, "Supported Diagnostic Pages"}, /* 0 */ |
| {DPC_CONFIGURATION, "Configuration (SES)"}, |
| {DPC_ENC_STATUS, "Enclosure Status (SES)"}, |
| {DPC_HELP_TEXT, "Help Text (SES)"}, |
| {DPC_STRING, "String In (SES)"}, |
| {DPC_THRESHOLD, "Threshold In (SES)"}, |
| {DPC_ARRAY_STATUS, "Array Status (SES, obsolete)"}, |
| {DPC_ELEM_DESC, "Element Descriptor (SES)"}, |
| {DPC_SHORT_ENC_STATUS, "Short Enclosure Status (SES)"}, /* 8 */ |
| {DPC_ENC_BUSY, "Enclosure Busy (SES-2)"}, |
| {DPC_ADD_ELEM_STATUS, "Additional Element Status (SES-2)"}, |
| {DPC_SUBENC_HELP_TEXT, "Subenclosure Help Text (SES-2)"}, |
| {DPC_SUBENC_STRING, "Subenclosure String In (SES-2)"}, |
| {DPC_SUPPORTED_SES, "Supported SES Diagnostic Pages (SES-2)"}, |
| {DPC_DOWNLOAD_MICROCODE, "Download Microcode (SES-2)"}, |
| {DPC_SUBENC_NICKNAME, "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[] = { |
| {DPC_SUPPORTED, "?? [Supported Diagnostic Pages]"}, /* 0 */ |
| {DPC_CONFIGURATION, "?? [Configuration (SES)]"}, |
| {DPC_ENC_CONTROL, "Enclosure Control (SES)"}, |
| {DPC_HELP_TEXT, "Help Text (SES)"}, |
| {DPC_STRING, "String Out (SES)"}, |
| {DPC_THRESHOLD, "Threshold Out (SES)"}, |
| {DPC_ARRAY_CONTROL, "Array Control (SES, obsolete)"}, |
| {DPC_ELEM_DESC, "?? [Element Descriptor (SES)]"}, |
| {DPC_SHORT_ENC_STATUS, "?? [Short Enclosure Status (SES)]"}, /* 8 */ |
| {DPC_ENC_BUSY, "?? [Enclosure Busy (SES-2)]"}, |
| {DPC_ADD_ELEM_STATUS, "?? [Additional Element Status (SES-2)]"}, |
| {DPC_SUBENC_HELP_TEXT, "?? [Subenclosure Help Text (SES-2)]"}, |
| {DPC_SUBENC_STRING, "Subenclosure String Out (SES-2)"}, |
| {DPC_SUPPORTED_SES, "?? [Supported SES Diagnostic Pages (SES-2)]"}, |
| {DPC_DOWNLOAD_MICROCODE, "Download Microcode (SES-2)"}, |
| {DPC_SUBENC_NICKNAME, "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", DPC_ARRAY_CONTROL}, |
| {"aes", DPC_ADD_ELEM_STATUS}, |
| {"as", DPC_ARRAY_STATUS}, |
| {"cf", DPC_CONFIGURATION}, |
| {"dm", DPC_DOWNLOAD_MICROCODE}, |
| {"eb", DPC_ENC_BUSY}, |
| {"ec", DPC_ENC_CONTROL}, |
| {"ed", DPC_ELEM_DESC}, |
| {"es", DPC_ENC_STATUS}, |
| {"ht", DPC_HELP_TEXT}, |
| {"sdp", DPC_SUPPORTED}, |
| {"ses", DPC_SHORT_ENC_STATUS}, |
| {"sht", DPC_SUBENC_HELP_TEXT}, |
| {"snic", DPC_SUBENC_NICKNAME}, |
| {"ssp", DPC_SUPPORTED_SES}, |
| {"sstr", DPC_SUBENC_STRING}, |
| {"str", DPC_STRING}, |
| {"th", DPC_THRESHOLD}, |
| {NULL, -1}, |
| }; |
| |
| /* 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_ELECTRONICS_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[] = { |
| {"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_ELECTRONICS_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_ELECTRONICS_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_ELECTRONICS_ETC, 3, 7, 1, NULL}, |
| {"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_ELECTRONICS_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_ELECTRONICS_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"}, |
| {"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"}, |
| {"remind", AUD_ALARM_ETC, 3, 4, 1, NULL}, |
| {"report", ENC_ELECTRONICS_ETC, 2, 0, 1, NULL}, |
| {"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"}, |
| {"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"}, |
| {"pow_c_duration", ENCLOSURE_ETC, 3, 7, 6, NULL}, |
| {"pow_c_time", ENCLOSURE_ETC, 2, 7, 6, |
| "time in minutes remaining until starting power cycle"}, |
| {"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}, |
| {"rrabort", ARRAY_DEV_ETC, 1, 0, 1, "rebuild/remap abort"}, |
| {"rsvddevice", ARRAY_DEV_ETC, 1, 6, 1, "reserved device"}, |
| {"select_element", ENC_ELECTRONICS_ETC, 2, 0, 1, NULL}, |
| {"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 */ |
| {"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}, |
| {"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 ... */ |
| {"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 int active_et_aesp_arr[NUM_ACTIVE_ET_AESP_ARR] = { |
| 0, 1 /* dev */, 0, 0, 0, 0, 0, 1 /* esce */, |
| 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 1 /* starg */, 1 /* sinit */, 0, 1 /* arr */, |
| 1 /* sas exp */, 0, 0, 0, 0, 0, 0, 0, |
| }; |
| |
| /* Command line long option names with corresponding short letter. */ |
| static struct option long_options[] = { |
| {"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'}, |
| {"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'}, |
| {"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'}, |
| {"raw", no_argument, 0, 'r'}, |
| {"readonly", no_argument, 0, 'R'}, |
| {"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'}, |
| {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_ELECTRONICS */ |
| {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, unsigned char * arr, int * arr_len, |
| int verb); |
| static int strcase_eq(const char * s1p, const char * s2p); |
| static void enumerate_diag_pages(void); |
| static int saddr_non_zero(const unsigned char * ucp); |
| |
| |
| static void |
| usage(int help_num) |
| { |
| if (1 == help_num) { |
| pr2serr( |
| "Usage: sg_ses [--descriptor=DN] [--dev-slot-num=SN] " |
| "[--eiioe=A_F]\n" |
| " [--filter] [--get=STR] [--hex] " |
| "[--index=IIA | =TIA,II]\n" |
| " [--inner-hex] [--join] [--maxlen=LEN] " |
| "[--page=PG]\n" |
| " [--raw] [--sas-addr=SA] [--status] [--verbose] " |
| "[--warn]\n" |
| " DEVICE\n\n" |
| " sg_ses [--byte1=B1] [--clear=STR] [--control] " |
| "[--data=H,H...]\n" |
| " [--descriptor=DN] [--dev-slot-num=SN] " |
| "[--index=IIA | =TIA,II]\n" |
| " [--mask] [--maxlen=LEN] [--nickname=SEN] " |
| "[--nickid=SEID]\n" |
| " [--page=PG] [--sas-addr=SA] [--set=STR] " |
| "[--verbose]\n" |
| " DEVICE\n\n" |
| " sg_ses [--enumerate] [--help] [--list] [--version]\n\n" |
| " where the main options are:\n" |
| " --clear=STR|-C STR clear field by acronym or position\n" |
| " --control|-c send control information (def: fetch " |
| "status)\n" |
| " --descriptor=DN|-D DN 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')\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. Use\n'-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" |
| " --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 for " |
| "control pages\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 where A_F is either 'auto' or 'force'." |
| "'force'\n" |
| " acts as if EIIOE is set, '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" |
| " --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" |
| " --nickname=SEN|-n SEN SEN is new subenclosure nickname\n" |
| " --nickid=SEID|-N SEID SEID is subenclosure identifier " |
| "(def: 0)\n" |
| " used to specify which nickname to " |
| "change\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; |
| const char * cp; |
| char * mallcp; |
| char * c2p; |
| const struct element_type_t * etp; |
| char b[64]; |
| |
| op->ind_given = 1; |
| if ((cp = strchr(op->index_str, ','))) { |
| if (0 == strcmp("-1", cp + 1)) |
| n = -1; |
| else { |
| n = sg_get_num(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; |
| } |
| } |
| op->ind_indiv = n; |
| n = cp - op->index_str; |
| if (n >= (int)sizeof(b)) { |
| pr2serr("bad argument to '--index', string prior to comma too " |
| "long\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } else { |
| n = strlen(op->index_str); |
| if (n >= (int)sizeof(b)) { |
| pr2serr("bad argument to '--index', string too long\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| strncpy(b, op->index_str, n); |
| b[n] = '\0'; |
| 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(b[0])) { |
| n = sg_get_num(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) |
| op->ind_th = n; |
| else { |
| op->ind_th = 0; |
| op->ind_indiv = n; |
| } |
| } else if ('_' == b[0]) { |
| if ((c2p = strchr(b + 1, '_'))) |
| *c2p = '\0'; |
| n = sg_get_num(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 */ |
| mallcp[0] = '_'; |
| snprintf(mallcp + 1, 6, "%d", n); |
| element_type_by_code.abbrev = mallcp; |
| if (c2p) { |
| n = sg_get_num(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 blen = strlen(b); |
| |
| for (etp = element_type_arr; etp->desc; ++etp) { |
| n = strlen(etp->abbrev); |
| if ((n == blen) && (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 ((int)strlen(b) > n) { |
| n = sg_get_num(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; |
| } |
| |
| |
| /* process command line options and argument. Returns 0 if ok. */ |
| static int |
| cl_process(struct opts_t *op, int argc, char *argv[]) |
| { |
| int c, j, ret, ff; |
| const char * data_arg = NULL; |
| uint64_t saddr; |
| const char * cp; |
| |
| op->dev_slot_num = -1; |
| while (1) { |
| int option_index = 0; |
| |
| c = getopt_long(argc, argv, "A:b:cC:d:D:eE:fG:hHiI:jln:N:m:Mp:rRsS:v" |
| "Vwx:", long_options, &option_index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'A': /* SAS address, assumed to be hex */ |
| cp = optarg; |
| if ((strlen(optarg) > 2) && ('X' == toupper(optarg[1]))) |
| cp = optarg + 2; |
| if (1 != sscanf(cp, "%" SCNx64 "", &saddr)) { |
| pr2serr("bad argument to '--sas-addr'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| for (j = 7, ff = 1; j >= 0; --j) { |
| if (ff & (0xff != (saddr & 0xff))) |
| ff = 0; |
| op->sas_addr[j] = (saddr & 0xff); |
| saddr >>= 8; |
| } |
| if (ff) { |
| pr2serr("decode error from argument to '--sas-addr'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'b': |
| op->byte1 = sg_get_num(optarg); |
| if ((op->byte1 < 0) || (op->byte1 > 255)) { |
| pr2serr("bad argument to '--byte1' (0 to 255 inclusive)\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| ++op->byte1_given; |
| break; |
| case 'c': |
| ++op->do_control; |
| break; |
| case 'C': |
| op->clear_str = optarg; |
| ++op->num_cgs; |
| break; |
| case 'd': |
| data_arg = optarg; |
| op->do_data = 1; |
| break; |
| case 'D': |
| op->desc_name = optarg; |
| break; |
| case 'e': |
| ++op->enumerate; |
| break; |
| case 'E': |
| if (0 == strcmp("auto", optarg)) |
| ++op->eiioe_auto; |
| else if (0 == strcmp("force", optarg)) |
| ++op->eiioe_force; |
| else { |
| pr2serr("--eiioe option expects 'auto' or 'force' as an " |
| "argument\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'f': |
| ++op->do_filter; |
| break; |
| case 'G': |
| op->get_str = optarg; |
| ++op->num_cgs; |
| break; |
| case 'h': |
| case '?': |
| ++op->do_help; |
| break; |
| case 'H': |
| ++op->do_hex; |
| break; |
| case 'i': |
| ++op->inner_hex; |
| break; |
| case 'I': |
| op->index_str = optarg; |
| break; |
| case 'j': |
| ++op->do_join; |
| break; |
| case 'l': |
| ++op->do_list; |
| break; |
| case 'n': |
| op->nickname_str = optarg; |
| break; |
| case 'N': |
| op->seid = sg_get_num(optarg); |
| if ((op->seid < 0) || (op->seid > 255)) { |
| pr2serr("bad argument to '--nick_id' (0 to 255 inclusive)\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| ++op->seid_given; |
| break; |
| case 'm': |
| op->maxlen = sg_get_num(optarg); |
| if ((op->maxlen < 0) || (op->maxlen > 65535)) { |
| pr2serr("bad argument to '--maxlen' (0 to 65535 " |
| "inclusive expected)\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'M': |
| ++op->mask_ign; |
| break; |
| case 'p': |
| if (isdigit(optarg[0])) { |
| op->page_code = sg_get_num(optarg); |
| if ((op->page_code < 0) || (op->page_code > 255)) { |
| pr2serr("bad argument to '--page' (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' abbreviation %s not found\nHere are " |
| "the choices:\n", optarg); |
| enumerate_diag_pages(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| ++op->page_code_given; |
| break; |
| case 'r': |
| ++op->do_raw; |
| break; |
| case 'R': |
| ++op->o_readonly; |
| break; |
| case 's': |
| ++op->do_status; |
| break; |
| case 'S': |
| op->set_str = optarg; |
| ++op->num_cgs; |
| break; |
| case 'v': |
| ++op->verbose; |
| break; |
| case 'V': |
| ++op->do_version; |
| return 0; |
| case 'w': |
| ++op->warn; |
| break; |
| case 'x': |
| op->dev_slot_num = sg_get_num(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; |
| 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; |
| } |
| } |
| if (data_arg) { |
| memset(op->data_arr, 0, sizeof(op->data_arr)); |
| if (read_hex(data_arg, op->data_arr + 4, &op->arr_len, op->verbose)) { |
| pr2serr("bad argument to '--data'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| if (op->maxlen <= 0) |
| op->maxlen = MX_ALLOC_LEN; |
| if (op->do_join && (op->do_control)) { |
| pr2serr("cannot have '--join' and '--control'\n"); |
| goto err_help; |
| } |
| if (op->num_cgs > 1) { |
| pr2serr("can only be one of '--clear', '--get' and '--set'\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"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if ((0 == op->do_join) && (0 == op->do_control) && |
| (0 == op->num_cgs) && (0 == op->page_code_given)) { |
| ++op->do_join; /* implicit --join */ |
| if (op->verbose) |
| pr2serr("assume --join option is set\n"); |
| } |
| } |
| if (op->ind_given) { |
| if ((0 == op->do_join) && (0 == op->do_control) && |
| (0 == op->num_cgs) && (0 == op->page_code_given)) { |
| ++op->page_code_given; |
| op->page_code = 2; /* 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 (0 == op->do_status) |
| op->do_status = 1; /* default to receiving status pages */ |
| |
| if (op->nickname_str) { |
| if (! op->do_control) { |
| pr2serr("since '--nickname=' implies control mode, require " |
| "'--control' as well\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (op->page_code_given) { |
| if (DPC_SUBENC_NICKNAME != op->page_code) { |
| pr2serr("since '--nickname=' assume or expect " |
| "'--page=snic'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } else |
| op->page_code = DPC_SUBENC_NICKNAME; |
| } else if (op->seid_given) { |
| pr2serr("'--nickid=' must be used together with '--nickname='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| |
| } |
| 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 (NULL == op->dev_name) { |
| pr2serr("missing DEVICE name!\n"); |
| goto err_help; |
| } |
| return 0; |
| |
| err_help: |
| pr2serr(" For more information use '--help'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| |
| /* Returns 64 bit signed integer given in either decimal or in hex. The |
| * hex number is either preceded by "0x" or followed by "h". Returns -1 |
| * on error (so check for "-1" string before using this function). */ |
| static int64_t |
| get_llnum(const char * buf) |
| { |
| int res, len; |
| int64_t num; |
| uint64_t unum; |
| |
| if ((NULL == buf) || ('\0' == buf[0])) |
| return -1; |
| len = strlen(buf); |
| if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { |
| res = sscanf(buf + 2, "%" SCNx64 "", &unum); |
| num = unum; |
| } else if ('H' == toupper(buf[len - 1])) { |
| res = sscanf(buf, "%" SCNx64 "", &unum); |
| num = unum; |
| } else |
| res = sscanf(buf, "%" SCNd64 "", &num); |
| return (1 == res) ? num : -1; |
| } |
| |
| /* Parse clear/get/set string. 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; |
| char * cp; |
| 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 = get_llnum(esp + 1); |
| if (-1 == tavp->val) { |
| pr2serr("unable to decode: %s value\n", esp + 1); |
| pr2serr(" expected: <acronym>[=<val>]\n"); |
| return -1; |
| } |
| } |
| } |
| if (isalpha(buff[0])) |
| tavp->acron = buff; |
| else { |
| colp = strchr(buff, ':'); |
| if ((NULL == colp) || (buff == colp)) |
| return -1; |
| *colp = '\0'; |
| if (('0' == buff[0]) && ('X' == toupper(buff[1]))) { |
| if (1 != sscanf(buff + 2, "%x", &ui)) |
| return -1; |
| tavp->start_byte = ui; |
| } else if ('H' == toupper(*(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; |
| } |
| |
| /* Return of 0 -> success, SG_LIB_CAT_* positive values or -1 -> other |
| * failures */ |
| static int |
| do_senddiag(int sg_fd, int pf_bit, void * outgoing_pg, int outgoing_len, |
| int noisy, int verbose) |
| { |
| const char * cp; |
| int page_num; |
| |
| if (outgoing_pg && (verbose > 2)) { |
| page_num = ((const char *)outgoing_pg)[0]; |
| cp = find_out_diag_page_desc(page_num); |
| if (cp) |
| pr2serr(" Send diagnostic cmd name: %s\n", cp); |
| else |
| pr2serr(" Send diagnostic cmd number: 0x%x\n", page_num); |
| } |
| return sg_ll_send_diag(sg_fd, 0 /* sf_code */, pf_bit, 0 /* sf_bit */, |
| 0 /* devofl_bit */, 0 /* unitofl_bit */, |
| 0 /* long_duration */, outgoing_pg, outgoing_len, |
| noisy, verbose); |
| } |
| |
| /* 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 * |
| find_element_tname(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 1 if el_type (element type) is of interest to the Additional |
| * Element Status page. Otherwise return 0. */ |
| static int |
| active_et_aesp(int el_type) |
| { |
| if ((el_type >= 0) && (el_type < NUM_ACTIVE_ET_AESP_ARR)) |
| return active_et_aesp_arr[el_type]; |
| else |
| return 0; |
| } |
| |
| /* Return of 0 -> success, SG_LIB_CAT_* positive values or -1 -> other |
| * failures */ |
| static int |
| do_rec_diag(int sg_fd, int page_code, unsigned char * rsp_buff, |
| int rsp_buff_size, const struct opts_t * op, int * rsp_lenp) |
| { |
| int rsp_len, res; |
| const char * cp; |
| char b[80]; |
| |
| memset(rsp_buff, 0, rsp_buff_size); |
| if (rsp_lenp) |
| *rsp_lenp = 0; |
| cp = find_in_diag_page_desc(page_code); |
| if (op->verbose > 1) { |
| if (cp) |
| pr2serr(" Receive diagnostic results cmd for %s page\n", cp); |
| else |
| pr2serr(" Receive diagnostic results cmd for page 0x%x\n", |
| page_code); |
| } |
| res = sg_ll_receive_diag(sg_fd, 1 /* pcv */, page_code, rsp_buff, |
| rsp_buff_size, 1, op->verbose); |
| 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 [%d but need " |
| "%d]>>>\n", rsp_buff_size, rsp_len); |
| rsp_len = rsp_buff_size; |
| } |
| if (rsp_lenp) |
| *rsp_lenp = rsp_len; |
| if (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) |
| dStrHexErr((const char *)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]); |
| dStrHexErr((const char *)rsp_buff, rsp_len, 0); |
| } |
| return -2; |
| } |
| return 0; |
| } else if (op->verbose) { |
| if (cp) |
| pr2serr("Attempt to fetch %s diagnostic page failed\n", cp); |
| else |
| pr2serr("Attempt to fetch status diagnostic page [0x%x] failed\n", |
| page_code); |
| sg_get_category_sense_str(res, sizeof(b), b, op->verbose); |
| pr2serr(" %s\n", b); |
| } |
| return res; |
| } |
| |
| static void |
| dStrRaw(const char* str, int len) |
| { |
| int k; |
| |
| for (k = 0 ; k < len; ++k) |
| printf("%c", str[k]); |
| } |
| |
| /* DPC_CONFIGURATION [0x1] |
| * Display Configuration diagnostic page. */ |
| static void |
| ses_configuration_sdg(const unsigned char * resp, int resp_len) |
| { |
| int j, k, el, num_subs, sum_elem_types; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| const unsigned char * text_ucp; |
| 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_ucp = 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); |
| printf(" enclosure descriptor list\n"); |
| ucp = resp + 8; |
| for (k = 0; k < num_subs; ++k, ucp += el) { |
| if ((ucp + 3) > last_ucp) |
| goto truncated; |
| el = ucp[3] + 4; |
| sum_elem_types += ucp[2]; |
| printf(" Subenclosure identifier: %d%s\n", ucp[1], |
| (ucp[1] ? "" : " [primary]")); |
| printf(" relative ES process id: %d, number of ES processes" |
| ": %d\n", ((ucp[0] & 0x70) >> 4), (ucp[0] & 0x7)); |
| printf(" number of type descriptor headers: %d\n", ucp[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", ucp[4 + j]); |
| printf("\n enclosure vendor: %.8s product: %.16s rev: %.4s\n", |
| ucp + 12, ucp + 20, ucp + 36); |
| if (el > 40) { |
| printf(" vendor-specific data:\n"); |
| /* dStrHex((const char *)(ucp + 40), el - 40, 0); */ |
| printf(" "); |
| for (j = 0; j < (el - 40); ++j) { |
| if ((j > 0) && (0 == (j % 16))) |
| printf("\n "); |
| printf("%02x ", *(ucp + 40 + j)); |
| } |
| printf("\n"); |
| } |
| } |
| /* printf("\n"); */ |
| printf(" type descriptor header/text list\n"); |
| text_ucp = ucp + (sum_elem_types * 4); |
| for (k = 0; k < sum_elem_types; ++k, ucp += 4) { |
| if ((ucp + 3) > last_ucp) |
| goto truncated; |
| printf(" Element type: %s, subenclosure id: %d\n", |
| find_element_tname(ucp[0], b, sizeof(b)), ucp[2]); |
| printf(" number of possible elements: %d\n", ucp[1]); |
| if (ucp[3] > 0) { |
| if (text_ucp > last_ucp) |
| goto truncated; |
| printf(" text: %.*s\n", ucp[3], text_ucp); |
| text_ucp += ucp[3]; |
| } |
| } |
| return; |
| truncated: |
| pr2serr(" <<<ses_configuration_sdg: response too short>>>\n"); |
| return; |
| } |
| |
| /* DPC_CONFIGURATION |
| * Returns total number of type descriptor headers written to 'tdhp' or -1 |
| * if there is a problem */ |
| static int |
| populate_type_desc_hdr_arr(int fd, struct type_desc_hdr_t * tdhp, |
| 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; |
| unsigned char * resp; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| |
| resp = (unsigned char *)calloc(op->maxlen, 1); |
| if (NULL == resp) { |
| pr2serr("%s: unable to allocate %d bytes on heap\n", __func__, |
| op->maxlen); |
| ret = -1; |
| goto the_end; |
| } |
| res = do_rec_diag(fd, DPC_CONFIGURATION, resp, op->maxlen, op, &resp_len); |
| if (res) { |
| pr2serr("%s: couldn't read config page, res=%d\n", __func__, res); |
| ret = -1; |
| goto the_end; |
| } |
| if (resp_len < 4) { |
| ret = -1; |
| goto the_end; |
| } |
| num_subs = resp[1] + 1; |
| sum_type_dheaders = 0; |
| last_ucp = resp + resp_len - 1; |
| gen_code = sg_get_unaligned_be32(resp + 4); |
| if (generationp) |
| *generationp = gen_code; |
| ucp = resp + 8; |
| for (k = 0; k < num_subs; ++k, ucp += el) { |
| if ((ucp + 3) > last_ucp) |
| goto p_truncated; |
| el = ucp[3] + 4; |
| sum_type_dheaders += ucp[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 = (ucp[0] & 0x70) >> 4; |
| primary_ip->num_esp = (ucp[0] & 0x7); |
| memcpy(primary_ip->enc_log_id, ucp + 4, 8); |
| memcpy(primary_ip->enc_vendor_id, ucp + 12, 8); |
| memcpy(primary_ip->product_id, ucp + 20, 16); |
| memcpy(primary_ip->product_rev_level, ucp + 36, 4); |
| } |
| } |
| for (k = 0; k < sum_type_dheaders; ++k, ucp += 4) { |
| if ((ucp + 3) > last_ucp) |
| goto p_truncated; |
| if (k >= MX_ELEM_HDR) { |
| pr2serr("%s: too many elements\n", __func__); |
| ret = -1; |
| goto the_end; |
| } |
| tdhp[k].etype = ucp[0]; |
| tdhp[k].num_elements = ucp[1]; |
| tdhp[k].se_id = ucp[2]; |
| tdhp[k].txt_len = ucp[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 (resp) |
| free(resp); |
| return ret; |
| } |
| |
| static char * |
| find_sas_connector_type(int conn_type, char * buff, int buff_len) |
| { |
| switch (conn_type) { |
| case 0x0: |
| snprintf(buff, buff_len, "No information"); |
| break; |
| case 0x1: |
| snprintf(buff, buff_len, "SAS 4x receptacle (SFF-8470) " |
| "[max 4 phys]"); |
| break; |
| case 0x2: |
| snprintf(buff, buff_len, "Mini SAS 4x receptacle (SFF-8088) " |
| "[max 4 phys]"); |
| break; |
| case 0x3: |
| snprintf(buff, buff_len, "QSFP+ receptacle (SFF-8436) " |
| "[max 4 phys]"); |
| break; |
| case 0x4: |
| snprintf(buff, buff_len, "Mini SAS 4x active receptacle (SFF-8088) " |
| "[max 4 phys]"); |
| break; |
| case 0x5: |
| snprintf(buff, buff_len, "Mini SAS HD 4x receptacle (SFF-8644) " |
| "[max 4 phys]"); |
| break; |
| case 0x6: |
| snprintf(buff, buff_len, "Mini SAS HD 8x receptacle (SFF-8644) " |
| "[max 8 phys]"); |
| break; |
| case 0x7: |
| snprintf(buff, buff_len, "Mini SAS HD 16x receptacle (SFF-8644) " |
| "[max 16 phys]"); |
| break; |
| case 0xf: |
| snprintf(buff, buff_len, "Vendor specific external connector"); |
| break; |
| case 0x10: |
| snprintf(buff, buff_len, "SAS 4i plug (SFF-8484) [max 4 phys]"); |
| break; |
| case 0x11: |
| snprintf(buff, buff_len, "Mini SAS 4i receptacle (SFF-8087) " |
| "[max 4 phys]"); |
| break; |
| case 0x12: |
| snprintf(buff, buff_len, "Mini SAS HD 4i receptacle (SFF-8643) " |
| "[max 4 phys]"); |
| break; |
| case 0x13: |
| snprintf(buff, buff_len, "Mini SAS HD 8i receptacle (SFF-8643) " |
| "[max 8 phys]"); |
| break; |
| case 0x20: |
| snprintf(buff, buff_len, "SAS Drive backplane receptacle (SFF-8482) " |
| "[max 2 phys]"); |
| break; |
| case 0x21: |
| snprintf(buff, buff_len, "SATA host plug [max 1 phy]"); |
| break; |
| case 0x22: |
| snprintf(buff, buff_len, "SAS Drive plug (SFF-8482) [max 2 phys]"); |
| break; |
| case 0x23: |
| snprintf(buff, buff_len, "SATA device plug [max 1 phy]"); |
| break; |
| case 0x24: |
| snprintf(buff, buff_len, "Micro SAS receptacle [max 2 phys]"); |
| break; |
| case 0x25: |
| snprintf(buff, buff_len, "Micro SATA device plug [max 1 phy]"); |
| break; |
| case 0x26: |
| snprintf(buff, buff_len, "Micro SAS plug (SFF-8486) [max 2 phys]"); |
| break; |
| case 0x27: |
| snprintf(buff, buff_len, "Micro SAS/SATA plug (SFF-8486) " |
| "[max 2 phys]"); |
| break; |
| case 0x28: |
| snprintf(buff, buff_len, "12 Gb/s SAS drive backplane receptacle " |
| "(SFF-8680) [max 2 phys]"); |
| break; |
| case 0x29: |
| snprintf(buff, buff_len, "12 Gb/s SAS drive plug (SFF-8680) [max 2 " |
| "phys]"); |
| break; |
| case 0x2a: |
| snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x unshielded " |
| "receptacle (SFF-8639)"); |
| break; |
| case 0x2b: |
| snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x unshielded plug " |
| "(SFF-8639)"); |
| break; |
| case 0x2f: |
| snprintf(buff, buff_len, "SAS virtual connector [max 1 phy]"); |
| break; |
| case 0x3f: |
| snprintf(buff, buff_len, "Vendor specific internal connector"); |
| 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 < 0x30) |
| snprintf(buff, buff_len, "unknown internal connector to end " |
| "device, 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; |
| } |
| |
| 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 unsigned char * statp, int etype, |
| const struct opts_t * op) |
| { |
| int res, a, b; |
| char bb[128]; |
| int nofilter = ! op->do_filter; |
| |
| |
| if (op->inner_hex) { |
| printf("%s%02x %02x %02x %02x\n", pad, statp[0], statp[1], statp[2], |
| statp[3]); |
| return; |
| } |
| 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: |
| 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]) || (0xe & 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)); |
| printf("%s DC overcurrent=%d\n", pad, !!(statp[2] & 0x2)); |
| if (nofilter || (0xf8 & statp[3])) |
| printf("%sHot swap=%d, Fail=%d, Requested on=%d, Off=%d, " |
| "Overtmp fail=%d\n", pad, !!(statp[3] & 0x80), |
| !!(statp[3] & 0x40), !!(statp[3] & 0x20), |
| !!(statp[3] & 0x10), !!(statp[3] & 0x8)); |
| if (nofilter || (0x7 & statp[3])) |
| printf("%sTemperature warn=%d, AC fail=%d, DC fail=%d\n", |
| pad, !!(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), (((0x7 & statp[1]) << 8) + statp[2]) * 10, |
| actual_speed_desc[7 & statp[3]]); |
| 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_ELECTRONICS_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; |
| default: |
| 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; |
| uint16_t dcs; |
| |
| printf("%sIdent=%d, Fail=%d, Display mode status=%d", pad, |
| !!(statp[1] & 0x80), !!(statp[1] & 0x40), dms); |
| if ((1 == dms) || (2 == dms)) { |
| 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 */ |
| printf("%sIdent=%d, %s\n", pad, !!(statp[1] & 0x80), |
| find_sas_connector_type((statp[1] & 0x7f), bb, sizeof(bb))); |
| 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; |
| } |
| } |
| |
| /* DPC_ENC_STATUS [0x2] |
| * Display enclosure status diagnostic page. */ |
| static void |
| ses_enc_status_dp(const struct type_desc_hdr_t * tdhp, int num_telems, |
| uint32_t ref_gen_code, const unsigned char * resp, |
| int resp_len, const struct opts_t * op) |
| { |
| int j, k, elem_ind, match_ind_th, got1; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| 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_ucp = 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"); |
| ucp = resp + 8; |
| for (k = 0, got1 = 0; k < num_telems; ++k, ++tdhp) { |
| if ((ucp + 3) > last_ucp) |
| 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", |
| find_element_tname(tdhp->etype, b, sizeof(b)), |
| tdhp->se_id, k); |
| printf(" Overall descriptor:\n"); |
| enc_status_helper(" ", ucp, tdhp->etype, op); |
| ++got1; |
| } |
| for (ucp += 4, j = 0, elem_ind = 0; j < tdhp->num_elements; |
| ++j, ucp += 4, ++elem_ind) { |
| if (op->ind_given) { |
| if ((! match_ind_th) || (-1 == op->ind_indiv) || |
| (elem_ind != op->ind_indiv)) |
| continue; |
| } |
| printf(" Element %d descriptor:\n", elem_ind); |
| enc_status_helper(" ", ucp, tdhp->etype, op); |
| ++got1; |
| } |
| } |
| if (op->ind_given && (0 == got1)) |
| printf(" >>> no match on --index=%d,%d\n", op->ind_th, |
| op->ind_indiv); |
| return; |
| truncated: |
| pr2serr(" <<<enc: 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 |
| ses_threshold_helper(const char * pad, const unsigned char *tp, int etype, |
| const struct opts_t * op) |
| { |
| char b[128]; |
| char b2[128]; |
| |
| if (op->inner_hex) { |
| printf("%s%02x %02x %02x %02x\n", pad, tp[0], tp[1], tp[2], tp[3]); |
| return; |
| } |
| switch (etype) { |
| case 0x4: /*temperature */ |
| printf("%shigh critical=%s, high warning=%s\n", pad, |
| reserved_or_num(b, 128, tp[0] - TEMPERAT_OFF, -TEMPERAT_OFF), |
| reserved_or_num(b2, 128, tp[1] - TEMPERAT_OFF, -TEMPERAT_OFF)); |
| printf("%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 (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 */ |
| #ifdef SG_LIB_MINGW |
| printf("%shigh critical=%g %%, high warning=%g %%\n", pad, |
| 0.5 * tp[0], 0.5 * tp[1]); |
| printf("%slow warning=%g %%, low critical=%g %% (from nominal " |
| "voltage)\n", pad, 0.5 * tp[2], 0.5 * tp[3]); |
| #else |
| printf("%shigh critical=%.1f %%, high warning=%.1f %%\n", pad, |
| 0.5 * tp[0], 0.5 * tp[1]); |
| printf("%slow warning=%.1f %%, low critical=%.1f %% (from nominal " |
| "voltage)\n", pad, 0.5 * tp[2], 0.5 * tp[3]); |
| #endif |
| break; |
| case 0x13: /* current */ |
| #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) |
| printf("%s<< no thresholds for this element type >>\n", pad); |
| break; |
| } |
| } |
| |
| /* DPC_THRESHOLD [0x5] */ |
| static void |
| ses_threshold_sdg(const struct type_desc_hdr_t * tdhp, int num_telems, |
| uint32_t ref_gen_code, const unsigned char * resp, |
| int resp_len, const struct opts_t * op) |
| { |
| int j, k, elem_ind, match_ind_th, got1; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| char b[64]; |
| |
| printf("Threshold In diagnostic page:\n"); |
| if (resp_len < 4) |
| goto truncated; |
| printf(" INVOP=%d\n", !!(resp[1] & 0x10)); |
| last_ucp = 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"); |
| ucp = resp + 8; |
| for (k = 0, got1 = 0; k < num_telems; ++k, ++tdhp) { |
| if ((ucp + 3) > last_ucp) |
| 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", |
| find_element_tname(tdhp->etype, b, sizeof(b)), |
| tdhp->se_id, k); |
| printf(" Overall descriptor:\n"); |
| ses_threshold_helper(" ", ucp, tdhp->etype, op); |
| ++got1; |
| } |
| for (ucp += 4, j = 0, elem_ind = 0; j < tdhp->num_elements; |
| ++j, ucp += 4, ++elem_ind) { |
| if (op->ind_given) { |
| if ((! match_ind_th) || (-1 == op->ind_indiv) || |
| (elem_ind != op->ind_indiv)) |
| continue; |
| } |
| printf(" Element %d descriptor:\n", elem_ind); |
| ses_threshold_helper(" ", ucp, tdhp->etype, op); |
| ++got1; |
| } |
| } |
| if (op->ind_given && (0 == got1)) |
| printf(" >>> no match on --index=%d,%d\n", op->ind_th, |
| op->ind_indiv); |
| return; |
| truncated: |
| pr2serr(" <<<thresh: response too short>>>\n"); |
| return; |
| } |
| |
| /* DPC_ELEM_DESC [0x7] |
| * This page essentially contains names of overall and individual |
| * elements. */ |
| static void |
| ses_element_desc_sdg(const struct type_desc_hdr_t * tdhp, int num_telems, |
| uint32_t ref_gen_code, const unsigned char * resp, |
| int resp_len, const struct opts_t * op) |
| { |
| int j, k, desc_len, elem_ind, match_ind_th, got1; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| const struct type_desc_hdr_t * tp; |
| char b[64]; |
| |
| printf("Element Descriptor In diagnostic page:\n"); |
| if (resp_len < 4) |
| goto truncated; |
| last_ucp = 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"); |
| ucp = resp + 8; |
| for (k = 0, got1 = 0, tp = tdhp; k < num_telems; ++k, ++tp) { |
| if ((ucp + 3) > last_ucp) |
| goto truncated; |
| desc_len = sg_get_unaligned_be16(ucp + 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", |
| find_element_tname(tp->etype, b, sizeof(b)), tp->se_id, k); |
| if (desc_len > 4) |
| printf(" Overall descriptor: %.*s\n", desc_len - 4, |
| ucp + 4); |
| else |
| printf(" Overall descriptor: <empty>\n"); |
| ++got1; |
| } |
| for (ucp += desc_len, j = 0, elem_ind = 0; j < tp->num_elements; |
| ++j, ucp += desc_len, ++elem_ind) { |
| desc_len = sg_get_unaligned_be16(ucp + 2) + 4; |
| if (op->ind_given) { |
| if ((! match_ind_th) || (-1 == op->ind_indiv) || |
| (elem_ind != op->ind_indiv)) |
| continue; |
| } |
| if (desc_len > 4) |
| printf(" Element %d descriptor: %.*s\n", j, |
| desc_len - 4, ucp + 4); |
| else |
| printf(" Element %d descriptor: <empty>\n", j); |
| ++got1; |
| } |
| } |
| if (op->ind_given && (0 == got1)) |
| printf(" >>> no match on --index=%d,%d\n", op->ind_th, |
| op->ind_indiv); |
| return; |
| truncated: |
| pr2serr(" <<<element: response too short>>>\n"); |
| return; |
| } |
| |
| static int |
| saddr_non_zero(const unsigned char * ucp) |
| { |
| int k; |
| |
| for (k = 0; k < 8; ++k) { |
| if (ucp[k]) |
| return 1; |
| } |
| return 0; |
| } |
| |
| 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_helper(const char * pad, const unsigned char * ucp, int len, |
| int elem_type, const struct opts_t * op) |
| { |
| int ports, phys, j, m, desc_type, eip_offset, print_sas_addr, saddr_nz; |
| int nofilter = ! op->do_filter; |
| uint16_t pcie_vid; |
| int pcie_pt, psn_valid, bdf_valid, cid_valid; |
| const unsigned char * per_ucp; |
| 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 ", ucp[j]); |
| } |
| printf("\n"); |
| return; |
| } |
| eip_offset = (0x10 & ucp[0]) ? 2 : 0; |
| switch (0xf & ucp[0]) { |
| case TPROTO_FCP: |
| printf("%sTransport protocol: FCP\n", pad); |
| if (len < (12 + eip_offset)) |
| break; |
| ports = ucp[2 + eip_offset]; |
| printf("%snumber of ports: %d\n", pad, ports); |
| printf("%snode_name: ", pad); |
| for (m = 0; m < 8; ++m) |
| printf("%02x", ucp[6 + eip_offset + m]); |
| if (eip_offset) |
| printf(", device slot number: %d", ucp[5 + eip_offset]); |
| printf("\n"); |
| per_ucp = ucp + 14 + eip_offset; |
| for (j = 0; j < ports; ++j, per_ucp += 16) { |
| printf("%s port index: %d, port loop position: %d, port " |
| "bypass reason: 0x%x\n", pad, j, per_ucp[0], per_ucp[1]); |
| printf("%srequested hard address: %d, n_port identifier: " |
| "%02x%02x%02x\n", pad, per_ucp[4], per_ucp[5], |
| per_ucp[6], per_ucp[7]); |
| printf("%s n_port name: ", pad); |
| for (m = 0; m < 8; ++m) |
| printf("%02x", per_ucp[8 + m]); |
| printf("\n"); |
| } |
| break; |
| case TPROTO_SAS: |
| printf("%sTransport protocol: SAS\n", pad); |
| if (len < (4 + eip_offset)) |
| break; |
| desc_type = (ucp[3 + eip_offset] >> 6) & 0x3; |
| if (op->verbose > 1) |
| printf("%sdescriptor_type: %d\n", pad, desc_type); |
| if (0 == desc_type) { |
| phys = ucp[2 + eip_offset]; |
| printf("%snumber of phys: %d, not all phys: %d", pad, phys, |
| ucp[3 + eip_offset] & 1); |
| if (eip_offset) |
| printf(", device slot number: %d", ucp[5 + eip_offset]); |
| printf("\n"); |
| per_ucp = ucp + 4 + eip_offset + eip_offset; |
| for (j = 0; j < phys; ++j, per_ucp += 28) { |
| printf("%sphy index: %d\n", pad, j); |
| printf("%s SAS device type: %s\n", pad, |
| sas_device_type[(0x70 & per_ucp[0]) >> 4]); |
| if (nofilter || (0xe & per_ucp[2])) |
| printf("%s initiator port for:%s%s%s\n", pad, |
| ((per_ucp[2] & 8) ? " SSP" : ""), |
| ((per_ucp[2] & 4) ? " STP" : ""), |
| ((per_ucp[2] & 2) ? " SMP" : "")); |
| if (nofilter || (0x8f & per_ucp[3])) |
| printf("%s target port for:%s%s%s%s%s\n", pad, |
| ((per_ucp[3] & 0x80) ? " SATA_port_selector" : ""), |
| ((per_ucp[3] & 8) ? " SSP" : ""), |
| ((per_ucp[3] & 4) ? " STP" : ""), |
| ((per_ucp[3] & 2) ? " SMP" : ""), |
| ((per_ucp[3] & 1) ? " SATA_device" : "")); |
| print_sas_addr = 0; |
| saddr_nz = saddr_non_zero(per_ucp + 4); |
| if (nofilter || saddr_nz) { |
| ++print_sas_addr; |
| printf("%s attached SAS address: 0x", pad); |
| if (saddr_nz) { |
| for (m = 0; m < 8; ++m) |
| printf("%02x", per_ucp[4 + m]); |
| } else |
| printf("0"); |
| } |
| saddr_nz = saddr_non_zero(per_ucp + 12); |
| if (nofilter || saddr_nz) { |
| ++print_sas_addr; |
| printf("\n%s SAS address: 0x", pad); |
| if (saddr_nz) { |
| for (m = 0; m < 8; ++m) |
| printf("%02x", per_ucp[12 + m]); |
| } else |
| printf("0"); |
| } |
| if (print_sas_addr) |
| printf("\n%s phy identifier: 0x%x\n", pad, per_ucp[20]); |
| } |
| } else if (1 == desc_type) { |
| phys = ucp[2 + eip_offset]; |
| if (SAS_EXPANDER_ETC == elem_type) { |
| printf("%snumber of phys: %d\n", pad, phys); |
| printf("%sSAS address: 0x", pad); |
| for (m = 0; m < 8; ++m) |
| printf("%02x", ucp[6 + eip_offset + m]); |
| printf("\n"); |
| per_ucp = ucp + 14 + eip_offset; |
| for (j = 0; j < phys; ++j, per_ucp += 2) { |
| printf("%s [%d] ", pad, j); |
| if (0xff == per_ucp[0]) |
| printf("no attached connector"); |
| else |
| printf("connector element index: %d", per_ucp[0]); |
| if (0xff != per_ucp[1]) |
| printf(", other element index: %d", per_ucp[1]); |
| printf("\n"); |
| } |
| } else if ((SCSI_TPORT_ETC == elem_type) || |
| (SCSI_IPORT_ETC == elem_type) || |
| (ENC_ELECTRONICS_ETC == elem_type)) { |
| printf("%snumber of phys: %d\n", pad, phys); |
| per_ucp = ucp + 6 + eip_offset; |
| for (j = 0; j < phys; ++j, per_ucp += 12) { |
| printf("%sphy index: %d\n", pad, j); |
| printf("%s phy identifier: 0x%x\n", pad, per_ucp[0]); |
| if (0xff == per_ucp[2]) |
| printf("%s no attached connector", pad); |
| else |
| printf("%s connector element index: %d", pad, |
| per_ucp[2]); |
| if (0xff != per_ucp[3]) |
| printf(", other element index: %d", per_ucp[3]); |
| printf("\n"); |
| printf("%s SAS address: 0x", pad); |
| for (m = 0; m < 8; ++m) |
| printf("%02x", per_ucp[4 + m]); |
| printf("\n"); |
| } |
| } else |
| printf("%sunrecognised element type [%d] for desc_type " |
| "1\n", pad, elem_type); |
| } else |
| printf("%sunrecognised descriptor type [%d]\n", pad, desc_type); |
| break; |
| case TPROTO_PCIE: /* added in ses3r08 */ |
| 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 = (ucp[5] >> 5) & 0x7; |
| if (1 == pcie_pt) |
| printf("%sPCIe protocol type: NVMe\n", pad); |
| else { |
| printf("%sTransport protocol: PCIe subprotocol=0x%x not " |
| "decoded\n", pad, pcie_pt); |
| if (op->verbose) |
| dStrHex((const char *)ucp, len, 0); |
| break; |
| } |
| phys = ucp[4]; |
| printf("%snumber of ports: %d, not all ports: %d", pad, phys, |
| ucp[5] & 1); |
| printf(", device slot number: %d\n", ucp[7]); |
| |
| pcie_vid = sg_get_unaligned_le16(ucp + 10); |
| printf("%svendor id: 0x%" PRIx16 "%s\n", pad, pcie_vid, |
| (0xffff == pcie_vid) ? " (not reported)" : ""); |
| printf("%sserial number: %.20s\n", pad, ucp + 12); |
| printf("%smodel number: %.40s\n", pad, ucp + 32); |
| per_ucp = ucp + 72; |
| for (j = 0; j < phys; ++j, per_ucp += 8) { |
| printf("%sport index: %d\n", pad, j); |
| psn_valid = !!(0x4 & per_ucp[0]); |
| bdf_valid = !!(0x2 & per_ucp[0]); |
| cid_valid = !!(0x1 & per_ucp[0]); |
| printf("%s PSN_VALID=%d, BDF_VALID=%d, CID_VALID=%d\n", pad, |
| psn_valid, bdf_valid, cid_valid); |
| if (cid_valid) |
| printf("%s controller id: 0x%" PRIx16 "\n", pad, |
| sg_get_unaligned_le16(per_ucp + 1)); |
| if (bdf_valid) |
| printf("%s bus number: 0x%x, device number: 0x%x, " |
| "function number: 0x%x\n", pad, per_ucp[4], |
| (per_ucp[5] >> 3) & 0x1f, 0x7 & per_ucp[5]); |
| if (psn_valid) |
| printf("%s physical slot number: 0x%" PRIx16 "\n", pad, |
| 0x1fff & sg_get_unaligned_le16(per_ucp + 6)); |
| } |
| break; |
| default: |
| printf("%sTransport protocol: %s not decoded\n", pad, |
| sg_get_trans_proto_str((0xf & ucp[0]), sizeof(b), b)); |
| if (op->verbose) |
| dStrHex((const char *)ucp, len, 0); |
| break; |
| } |
| } |
| |
| /* DPC_ADD_ELEM_STATUS [0xa] |
| * Previously called "Device element status descriptor". Changed "device" |
| * to "additional" to allow for SAS expander and SATA devices */ |
| static void |
| ses_additional_elem_sdg(const struct type_desc_hdr_t * tdhp, int num_telems, |
| uint32_t ref_gen_code, const unsigned char * resp, |
| int resp_len, const struct opts_t * op) |
| { |
| int j, k, desc_len, elem_type, invalid, el_num, eip, ind, match_ind_th; |
| int elem_count, ei, eiioe, my_eiioe_force, num_elems, skip; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| const struct type_desc_hdr_t * tp; |
| char b[64]; |
| |
| printf("Additional element status diagnostic page:\n"); |
| if (resp_len < 4) |
| goto truncated; |
| last_ucp = 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"); |
| ucp = resp + 8; |
| my_eiioe_force = op->eiioe_force; |
| for (k = 0, tp = tdhp, elem_count = 0; k < num_telems; ++k, ++tp) { |
| elem_type = tp->etype; |
| num_elems = tp->num_elements; |
| if (! active_et_aesp(elem_type)) { |
| elem_count += num_elems; |
| continue; /* skip if not element type of interest */ |
| } |
| if ((ucp + 1) > last_ucp) |
| goto truncated; |
| |
| /* if eip is 1, do bounds check on the element index */ |
| if (ucp[0] & 0x10) /* eip=1 */ { |
| ei = ucp[3]; |
| skip = 0; |
| if ((0 == k) && op->eiioe_auto && (1 == ei)) { |
| /* heuristic: if first element index in this page is 1 |
| * then act as if the EIIOE bit is set. */ |
| my_eiioe_force = 1; |
| } |
| eiioe = my_eiioe_force ? 1 : (ucp[2] & 1); |
| if (eiioe) { |
| if ((ei < (elem_count + k)) || |
| (ei > (elem_count + k + num_elems))) { |
| elem_count += num_elems; |
| skip = 1; |
| } |
| } else { |
| if ((ei < elem_count) || (ei > elem_count + num_elems)) { |
| elem_count += num_elems; |
| skip = 1; |
| } |
| } |
| if (skip) { |
| if (op->verbose > 2) |
| pr2serr("skipping elem_type=0x%x, k=%d due to " |
| "element_index=%d bounds\n effective eiioe=%d, " |
| "elem_count=%d, num_elems=%d\n", elem_type, 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", |
| find_element_tname(elem_type, b, sizeof(b)), tp->se_id, k); |
| } |
| el_num = 0; |
| for (j = 0; j < num_elems; ++j, ucp += desc_len, ++el_num) { |
| invalid = !!(ucp[0] & 0x80); |
| desc_len = ucp[1] + 2; |
| eip = ucp[0] & 0x10; |
| eiioe = eip ? (ucp[2] & 1) : 0; |
| ind = eip ? ucp[3] : el_num; |
| if (op->ind_given) { |
| if ((! match_ind_th) || (-1 == op->ind_indiv) || |
| (el_num != op->ind_indiv)) |
| continue; |
| } |
| if (eip) |
| printf(" Element index: %d eiioe=%d%s\n", ind, eiioe, |
| (((! eiioe) && my_eiioe_force) ? |
| " but overridden" : "")); |
| else |
| printf(" Element %d descriptor\n", ind); |
| if (invalid && (0 == op->inner_hex)) |
| printf(" flagged as invalid (no further " |
| "information)\n"); |
| else |
| additional_elem_helper(" ", ucp, desc_len, elem_type, |
| op); |
| } |
| elem_count += tp->num_elements; |
| } |
| return; |
| truncated: |
| pr2serr(" <<<additional: response too short>>>\n"); |
| return; |
| } |
| |
| /* DPC_SUBENC_HELP_TEXT [0xb] */ |
| static void |
| ses_subenc_help_sdg(const unsigned char * resp, int resp_len) |
| { |
| int k, el, num_subs; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| |
| 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_ucp = 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); |
| ucp = resp + 8; |
| for (k = 0; k < num_subs; ++k, ucp += el) { |
| if ((ucp + 3) > last_ucp) |
| goto truncated; |
| el = sg_get_unaligned_be16(ucp + 2) + 4; |
| printf(" subenclosure identifier: %d\n", ucp[1]); |
| if (el > 4) |
| printf(" %.*s\n", el - 4, ucp + 4); |
| else |
| printf(" <empty>\n"); |
| } |
| return; |
| truncated: |
| pr2serr(" <<<subenc: response too short>>>\n"); |
| return; |
| } |
| |
| /* DPC_SUBENC_STRING [0xc] */ |
| static void |
| ses_subenc_string_sdg(const unsigned char * resp, int resp_len) |
| { |
| int k, j, el, num_subs; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| |
| 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_ucp = 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); |
| ucp = resp + 8; |
| for (k = 0; k < num_subs; ++k, ucp += el) { |
| if ((ucp + 3) > last_ucp) |
| goto truncated; |
| el = sg_get_unaligned_be16(ucp + 2) + 4; |
| printf(" subenclosure identifier: %d\n", ucp[1]); |
| if (el > 4) { |
| /* dStrHex((const char *)(ucp + 4), el - 4, 0); */ |
| printf(" "); |
| for (j = 0; j < (el - 4); ++j) { |
| if ((j > 0) && (0 == (j % 16))) |
| printf("\n "); |
| printf("%02x ", *(ucp + 4 + j)); |
| } |
| printf("\n"); |
| } else |
| printf(" <empty>\n"); |
| } |
| return; |
| truncated: |
| pr2serr(" <<<subence str: response too short>>>\n"); |
| return; |
| } |
| |
| /* DPC_SUBENC_NICKNAME [0xf] */ |
| static void |
| ses_subenc_nickname_sdg(const unsigned char * resp, int resp_len) |
| { |
| int k, el, num_subs; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| |
| 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_ucp = 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); |
| ucp = resp + 8; |
| el = 40; |
| for (k = 0; k < num_subs; ++k, ucp += el) { |
| if ((ucp + el - 1) > last_ucp) |
| goto truncated; |
| printf(" subenclosure identifier: %d\n", ucp[1]); |
| printf(" nickname status: 0x%x\n", ucp[2]); |
| printf(" nickname additional status: 0x%x\n", ucp[3]); |
| printf(" nickname language code: %.2s\n", ucp + 6); |
| printf(" nickname: %.*s\n", 32, ucp + 8); |
| } |
| return; |
| truncated: |
| pr2serr(" <<<subence str: response too short>>>\n"); |
| return; |
| } |
| |
| /* DPC_SUPPORTED_SES [0xd] */ |
| static void |
| ses_supported_pages_sdg(const char * leadin, const unsigned char * resp, |
| int resp_len) |
| { |
| int k, code, prev, got1; |
| const char * cp; |
| const struct diag_page_abbrev * ap; |
| |
| printf("%s:\n", leadin); |
| for (k = 0, prev = 0; k < (resp_len - 4); ++k, prev = code) { |
| 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 = 0; ap->abbrev; ++ap) { |
| if (ap->page_code == code) { |
| printf("%s%s", (got1 ? "," : ""), ap->abbrev); |
| ++got1; |
| } |
| } |
| 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 storage"}, |
| {0x3, "Updating 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(unsigned char 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 ""; |
| } |
| |
| /* DPC_DOWNLOAD_MICROCODE [0xe] */ |
| static void |
| ses_download_code_sdg(const unsigned char * resp, int resp_len) |
| { |
| int k, num_subs; |
| uint32_t gen_code; |
| const unsigned char * ucp; |
| const unsigned char * last_ucp; |
| 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_ucp = 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); |
| ucp = resp + 8; |
| for (k = 0; k < num_subs; ++k, ucp += 16) { |
| if ((ucp + 3) > last_ucp) |
| goto truncated; |
| cp = (0 == ucp[1]) ? " [primary]" : ""; |
| printf(" subenclosure identifier: %d%s\n", ucp[1], cp); |
| cp = get_mc_status(ucp[2]); |
| if (strlen(cp) > 0) { |
| printf(" download microcode status: %s [0x%x]\n", cp, ucp[2]); |
| printf(" download microcode additional status: 0x%x\n", |
| ucp[3]); |
| } else |
| printf(" download microcode status: 0x%x [additional " |
| "status: 0x%x]\n", ucp[2], ucp[3]); |
| printf(" download microcode maximum size: %d bytes\n", |
| sg_get_unaligned_be32(ucp + 4)); |
| printf(" download microcode expected buffer id: 0x%x\n", ucp[11]); |
| printf(" download microcode expected buffer id offset: %d\n", |
| sg_get_unaligned_be32(ucp + 12)); |
| } |
| return; |
| truncated: |
| pr2serr(" <<<download: response too short>>>\n"); |
| return; |
| } |
| |
| /* Reads hex data from command line, stdin or a file. Returns 0 on success, |
| * 1 otherwise. */ |
| static int |
| read_hex(const char * inp, unsigned char * arr, int * arr_len, int verb) |
| { |
| int in_len, k, j, m, off, split_line; |
| 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; |
| lcp = inp; |
| in_len = strlen(inp); |
| if (0 == in_len) |
| *arr_len = 0; |
| if (('-' == inp[0]) || ('@' == inp[0])) { /* read from stdin or file */ |
| if ('-' == inp[0]) |
| fp = stdin; |
| else { |
| fp = fopen(inp + 1, "r"); |
| if (NULL == fp) { |
| pr2serr("%s: unable to open file: %s\n", __func__, inp + 1); |
| return 1; |
| } |
| } |
| carry_over[0] = 0; |
| for (j = 0, off = 0; j < MX_DATA_IN; ++j) { |
| /* limit lines read to MX_DATA_IN */ |
| 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 = 0; |
| } else |
| split_line = 1; |
| } |
| if (in_len < 1) { |
| carry_over[0] = 0; |
| continue; |
| } |
| if (carry_over[0]) { |
| if (isxdigit(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); |
| goto err_with_fp; |
| } |
| for (k = 0; k < (MX_DATA_IN - 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)); |
| 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)); |
| goto err_with_fp; |
| } |
| } |
| off += k + 1; |
| if (off >= MX_DATA_IN) |
| 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_DATA_IN; ++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 (verb > 3) |
| dStrHex((const char *)arr, *arr_len, 0); |
| if (fp && (fp != stdin)) |
| fclose(fp); |
| return 0; |
| |
| err_with_fp: |
| if (fp && (fp != stdin)) |
| fclose(fp); |
| return 1; |
| } |
| |
| /* Display "status" page (op->page_code). Return 0 for success. */ |
| static int |
| ses_process_status_page(int sg_fd, struct opts_t * op) |
| { |
| int j, resp_len, res; |
| int ret = 0; |
| uint32_t ref_gen_code; |
| unsigned char * resp; |
| const char * cp; |
| struct enclosure_info primary_info; |
| |
| resp = (unsigned char *)calloc(op->maxlen, 1); |
| if (NULL == resp) { |
| pr2serr("%s: unable to allocate %d bytes on heap\n", __func__, |
| op->maxlen); |
| ret = -1; |
| goto fini; |
| } |
| cp = find_in_diag_page_desc(op->page_code); |
| ret = do_rec_diag(sg_fd, op->page_code, resp, op->maxlen, op, &resp_len); |
| if (ret) |
| goto fini; |
| if (op->do_raw) { |
| if (1 == op->do_raw) |
| dStrHex((const char *)resp + 4, resp_len - 4, -1); |
| else { |
| if (sg_set_binary_mode(STDOUT_FILENO) < 0) |
| perror("sg_set_binary_mode"); |
| dStrRaw((const char *)resp, resp_len); |
| } |
| } else if (op->do_hex) { |
| if (op->do_hex > 2) |
| dStrHex((const char *)resp, resp_len, -1); |
| else { |
| if (cp) |
| printf("Response in hex from diagnostic page: %s\n", cp); |
| else |
| printf("Response in hex from unknown diagnostic page " |
| "[0x%x]\n", op->page_code); |
| dStrHex((const char *)resp, resp_len, (2 == op->do_hex)); |
| } |
| } else { |
| memset(&primary_info, 0, sizeof(primary_info)); |
| switch (op->page_code) { |
| case DPC_SUPPORTED: |
| ses_supported_pages_sdg("Supported diagnostic pages", |
| resp, resp_len); |
| break; |
| case DPC_CONFIGURATION: |
| ses_configuration_sdg(resp, resp_len); |
| break; |
| case DPC_ENC_STATUS: |
| res = populate_type_desc_hdr_arr(sg_fd, type_desc_hdr_arr, |
| &ref_gen_code, &primary_info, |
| op); |
| if (res < 0) { |
| ret = res; |
| 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"); |
| } |
| ses_enc_status_dp(type_desc_hdr_arr, res, ref_gen_code, |
| resp, resp_len, op); |
| break; |
| case DPC_HELP_TEXT: |
| 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 DPC_STRING: |
| printf("String In diagnostic page (for primary " |
| "subenclosure):\n"); |
| if (resp_len > 4) |
| dStrHex((const char *)(resp + 4), resp_len - 4, 0); |
| else |
| printf(" <empty>\n"); |
| break; |
| case DPC_THRESHOLD: |
| res = populate_type_desc_hdr_arr(sg_fd, type_desc_hdr_arr, |
| &ref_gen_code, &primary_info, |
| op); |
| if (res < 0) { |
| ret = res; |
| 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"); |
| } |
| ses_threshold_sdg(type_desc_hdr_arr, res, ref_gen_code, |
| resp, resp_len, op); |
| break; |
| case DPC_ELEM_DESC: |
| res = populate_type_desc_hdr_arr(sg_fd, type_desc_hdr_arr, |
| &ref_gen_code, &primary_info, |
| op); |
| if (res < 0) { |
| ret = res; |
| 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"); |
| } |
| ses_element_desc_sdg(type_desc_hdr_arr, res, ref_gen_code, |
| resp, resp_len, op); |
| break; |
| case DPC_SHORT_ENC_STATUS: |
| printf("Short enclosure status diagnostic page, " |
| "status=0x%x\n", resp[1]); |
| break; |
| case DPC_ENC_BUSY: |
| printf("Enclosure Busy diagnostic page, " |
| "busy=%d [vendor specific=0x%x]\n", |
| resp[1] & 1, (resp[1] >> 1) & 0xff); |
| break; |
| case DPC_ADD_ELEM_STATUS: |
| res = populate_type_desc_hdr_arr(sg_fd, type_desc_hdr_arr, |
| &ref_gen_code, &primary_info, |
| op); |
| if (res < 0) { |
| ret = res; |
| 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"); |
| } |
| ses_additional_elem_sdg(type_desc_hdr_arr, res, ref_gen_code, |
| resp, resp_len, op); |
| break; |
| case DPC_SUBENC_HELP_TEXT: |
| ses_subenc_help_sdg(resp, resp_len); |
| break; |
| case DPC_SUBENC_STRING: |
| ses_subenc_string_sdg(resp, resp_len); |
| break; |
| case DPC_SUPPORTED_SES: |
| ses_supported_pages_sdg("Supported SES diagnostic pages", |
| resp, resp_len); |
| break; |
| case DPC_DOWNLOAD_MICROCODE: |
| ses_download_code_sdg(resp, resp_len); |
| break; |
| case DPC_SUBENC_NICKNAME: |
| ses_subenc_nickname_sdg(resp, resp_len); |
| break; |
| default: |
| printf("Cannot decode response from diagnostic " |
| "page: %s\n", (cp ? cp : "<unknown>")); |
| dStrHex((const char *)resp, resp_len, 0); |
| } |
| } |
| ret = 0; |
| |
| fini: |
| if (resp) |
| free(resp); |
| return ret; |
| } |
| |
| static void |
| devslotnum_and_sasaddr(struct join_row_t * jrp, unsigned char * ae_ucp) |
| { |
| int m; |
| |
| if ((0 == jrp) || (0 == ae_ucp) || (0 == (0x10 & ae_ucp[0]))) |
| return; /* sanity and expect EIP=1 */ |
| switch (0xf & ae_ucp[0]) { |
| case TPROTO_FCP: |
| jrp->dev_slot_num = ae_ucp[7]; |
| break; |
| case TPROTO_SAS: |
| if (0 == (0xc0 & ae_ucp[5])) { |
| /* only for device slot and array device slot elements */ |
| jrp->dev_slot_num = ae_ucp[7]; |
| if (ae_ucp[4] > 0) { /* number of phys */ |
| /* Use the first phy's "SAS ADDRESS" field */ |
| for (m = 0; m < 8; ++m) |
| jrp->sas_addr[m] = ae_ucp[(4 + 4 + 12) + m]; |
| } |
| } |
| break; |
| case TPROTO_PCIE: |
| jrp->dev_slot_num = ae_ucp[7]; |
| break; |
| default: |
| ; |
| } |
| } |
| |
| /* 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[]. |
| * Returns 0 for success, any other return value is an error. */ |
| static int |
| join_work(int sg_fd, struct opts_t * op, int display) |
| { |
| int k, j, res, num_t_hdrs, elem_ind, ei, desc_len, dn_len; |
| int et4aes, broken_ei, ei2, got1, jr_max_ind, mlen; |
| uint32_t ref_gen_code, gen_code; |
| struct join_row_t * jrp; |
| struct join_row_t * jr2p; |
| unsigned char * es_ucp; |
| unsigned char * ed_ucp; |
| unsigned char * ae_ucp; |
| unsigned char * t_ucp; |
| /* const unsigned char * es_last_ucp; */ |
| /* const unsigned char * ed_last_ucp; */ |
| const unsigned char * ae_last_ucp; |
| /* const unsigned char * t_last_ucp; */ |
| const char * cp; |
| const char * enc_state_changed = " <<state of enclosure changed, " |
| "please try again>>\n"; |
| const struct type_desc_hdr_t * tdhp; |
| struct enclosure_info primary_info; |
| char b[64]; |
| |
| memset(&primary_info, 0, sizeof(primary_info)); |
| num_t_hdrs = populate_type_desc_hdr_arr(sg_fd, type_desc_hdr_arr, |
| &ref_gen_code, &primary_info, |
| op); |
| if (num_t_hdrs < 0) |
| return num_t_hdrs; |
| if (display && 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"); |
| } |
| mlen = sizeof(enc_stat_rsp); |
| if (mlen > op->maxlen) |
| mlen = op->maxlen; |
| res = do_rec_diag(sg_fd, DPC_ENC_STATUS, 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_ucp = enc_stat_rsp + 8; |
| /* es_last_ucp = enc_stat_rsp + enc_stat_rsp_len - 1; */ |
| |
| mlen = sizeof(elem_desc_rsp); |
| if (mlen > op->maxlen) |
| mlen = op->maxlen; |
| res = do_rec_diag(sg_fd, DPC_ELEM_DESC, 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_ucp = elem_desc_rsp + 8; |
| /* ed_last_ucp = elem_desc_rsp + elem_desc_rsp_len - 1; */ |
| } else { |
| elem_desc_rsp_len = 0; |
| ed_ucp = NULL; |
| res = 0; |
| if (op->verbose) |
| pr2serr(" Element Descriptor page not available\n"); |
| } |
| |
| if (display || (DPC_ADD_ELEM_STATUS == op->page_code) || |
| (op->dev_slot_num >= 0) || saddr_non_zero(op->sas_addr)) { |
| mlen = sizeof(add_elem_rsp); |
| if (mlen > op->maxlen) |
| mlen = op->maxlen; |
| res = do_rec_diag(sg_fd, DPC_ADD_ELEM_STATUS, 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_ucp = add_elem_rsp + 8; |
| ae_last_ucp = add_elem_rsp + add_elem_rsp_len - 1; |
| if (op->eiioe_auto && (add_elem_rsp_len > 11)) { |
| /* heuristic: if first element index in this page is 1 |
| * then act as if the EIIOE bit is set. */ |
| if ((ae_ucp[0] & 0x10) && (1 == ae_ucp[3])) |
| op->eiioe_force = 1; |
| } |
| } else { |
| add_elem_rsp_len = 0; |
| ae_ucp = NULL; |
| ae_last_ucp = NULL; |
| res = 0; |
| if (op->verbose) |
| pr2serr(" Additional Element Status page not available\n"); |
| } |
| } else { |
| ae_ucp = NULL; |
| ae_last_ucp = NULL; |
| } |
| |
| if ((op->do_join > 1) || |
| ((0 == display) && (DPC_THRESHOLD == op->page_code))) { |
| mlen = sizeof(threshold_rsp); |
| if (mlen > op->maxlen) |
| mlen = op->maxlen; |
| res = do_rec_diag(sg_fd, DPC_THRESHOLD, 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_ucp = threshold_rsp + 8; |
| /* t_last_ucp = threshold_rsp + threshold_rsp_len - 1; */ |
| } else { |
| threshold_rsp_len = 0; |
| t_ucp = NULL; |
| res = 0; |
| if (op->verbose) |
| pr2serr(" Threshold In page not available\n"); |
| } |
| } else { |
| threshold_rsp_len = 0; |
| t_ucp = NULL; |
| } |
| |
| jrp = join_arr; |
| tdhp = type_desc_hdr_arr; |
| jr_max_ind = 0; |
| for (k = 0, ei = 0, ei2 = 0; k < num_t_hdrs; ++k, ++tdhp) { |
| jrp->el_ind_th = k; |
| jrp->el_ind_indiv = -1; |
| jrp->etype = tdhp->etype; |
| jrp->ei_asc = -1; |
| et4aes = active_et_aesp(tdhp->etype); |
| jrp->ei_asc2 = -1; |
| jrp->se_id = tdhp->se_id; |
| /* check es_ucp < es_last_ucp still in range */ |
| jrp->enc_statp = es_ucp; |
| es_ucp += 4; |
| jrp->elem_descp = ed_ucp; |
| if (ed_ucp) |
| ed_ucp += sg_get_unaligned_be16(ed_ucp + 2) + 4; |
| jrp->add_elem_statp = NULL; |
| jrp->thresh_inp = t_ucp; |
| jrp->dev_slot_num = -1; |
| /* assume sas_addr[8] zeroed since it's static file scope */ |
| if (t_ucp) |
| t_ucp += 4; |
| ++jrp; |
| for (j = 0, elem_ind = 0; j < tdhp->num_elements; |
| ++j, ++jrp, ++elem_ind) { |
| if (jrp >= join_arr_lastp) |
| break; |
| jrp->el_ind_th = k; |
| jrp->el_ind_indiv = elem_ind; |
| jrp->ei_asc = ei++; |
| if (et4aes) |
| jrp->ei_asc2 = ei2++; |
| else |
| jrp->ei_asc2 = -1; |
| jrp->etype = tdhp->etype; |
| jrp->se_id = tdhp->se_id; |
| jrp->enc_statp = es_ucp; |
| es_ucp += 4; |
| jrp->elem_descp = ed_ucp; |
| if (ed_ucp) |
| ed_ucp += sg_get_unaligned_be16(ed_ucp + 2) + 4; |
| jrp->thresh_inp = t_ucp; |
| jrp->dev_slot_num = -1; |
| /* assume sas_addr[8] zeroed since it's static file scope */ |
| if (t_ucp) |
| t_ucp += 4; |
| jrp->add_elem_statp = NULL; |
| ++jr_max_ind; |
| } |
| if (jrp >= join_arr_lastp) { |
| ++k; |
| break; /* leave last row all zeros */ |
| } |
| } |
| |
| broken_ei = 0; |
| if (ae_ucp) { |
| int eip, eiioe; |
| int aes_i = 0; |
| int get_out = 0; |
| |
| jrp = join_arr; |
| tdhp = type_desc_hdr_arr; |
| for (k = 0; k < num_t_hdrs; ++k, ++tdhp) { |
| if (active_et_aesp(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) { |
| if ((ae_ucp + 1) > ae_last_ucp) { |
| get_out = 1; |
| if (op->verbose || op->warn) |
| pr2serr("warning: %s: off end of ae page\n", |
| __func__); |
| break; |
| } |
| eip = !!(ae_ucp[0] & 0x10); /* element index present */ |
| if (eip) |
| eiioe = op->eiioe_force ? 1 : (ae_ucp[2] & 1); |
| else |
| eiioe = 0; |
| if (eip && eiioe) { |
| ei = ae_ucp[3]; |
| jr2p = join_arr + ei; |
| if ((ei >= jr_max_ind) || (NULL == jr2p->enc_statp)) { |
| get_out = 1; |
| pr2serr("%s: oi=%d, ei=%d [max_ind=%d], eiioe=1 " |
| "not in join_arr\n", __func__, k, ei, |
| jr_max_ind); |
| break; |
| } |
| devslotnum_and_sasaddr(jr2p, ae_ucp); |
| if (jr2p->add_elem_statp) { |
| if (op->warn || op->verbose) |
| pr2serr("warning: aes slot busy [oi=%d, " |
| "ei=%d, aes_i=%d]\n", k, ei, aes_i); |
| } else |
| jr2p->add_elem_statp = ae_ucp; |
| } else if (eip) { /* and EIIOE=0 */ |
| ei = ae_ucp[3]; |
| try_again: |
| for (jr2p = join_arr; jr2p->enc_statp; ++jr2p) { |
| if (broken_ei) { |
| if (ei == jr2p->ei_asc2) |
| break; |
| } else { |
| if (ei == jr2p->ei_asc) |
| break; |
| } |
| } |
| if (NULL == jr2p->enc_statp) { |
| get_out = 1; |
| pr2serr("warning: %s: oi=%d, ei=%d (broken_ei=%d) " |
| "not in join_arr\n", __func__, k, ei, |
| broken_ei); |
| break; |
| } |
| if (! active_et_aesp(jr2p->etype)) { |
| /* broken_ei must be 0 for that to be false */ |
| ++broken_ei; |
| goto try_again; |
| } |
| devslotnum_and_sasaddr(jr2p, ae_ucp); |
| if (jr2p->add_elem_statp) { |
| if (op->warn || op->verbose) |
| pr2serr("warning: aes slot busy [oi=%d, " |
| "ei=%d, aes_i=%d]\n", k, ei, aes_i); |
| } else |
| jr2p->add_elem_statp = ae_ucp; |
| } else { /* EIP=0 */ |
| while (jrp->enc_statp && ((-1 == jrp->el_ind_indiv) || |
| jrp->add_elem_statp)) |
| ++jrp; |
| if (NULL == jrp->enc_statp) { |
| get_out = 1; |
| pr2serr("warning: %s: join_arr has no space for " |
| "ae\n", __func__); |
| break; |
| } |
| jrp->add_elem_statp = ae_ucp; |
| ++jrp; |
| } |
| ae_ucp += ae_ucp[1] + 2; |
| ++aes_i; |
| } |
| } else { /* element type not relevant to ae status */ |
| /* step over overall and individual elements */ |
| for (j = 0; j <= tdhp->num_elements; ++j, ++jrp) { |
| if (NULL == jrp->enc_statp) { |
| get_out = 1; |
| pr2serr("warning: %s: join_arr has no space\n", |
| __func__); |
| break; |
| } |
| } |
| } |
| if (get_out) |
| break; |
| } |
| } |
| |
| if (op->verbose > 3) { |
| jrp = join_arr; |
| for (k = 0; ((k < MX_JOIN_ROWS) && jrp->enc_statp); ++k, ++jrp) { |
| pr2serr("el_ind_th=%d el_ind_indiv=%d etype=%d se_id=%d ei=%d " |
| "ei2=%d dsn=%d sa=0x", jrp->el_ind_th, jrp->el_ind_indiv, |
| jrp->etype, jrp->se_id, jrp->ei_asc, jrp->ei_asc2, |
| jrp->dev_slot_num); |
| if (saddr_non_zero(jrp->sas_addr)) { |
| for (j = 0; j < 8; ++j) |
| pr2serr("%02x", jrp->sas_addr[j]); |
| } else |
| pr2serr("0"); |
| pr2serr(" %s %s %s %s\n", (jrp->enc_statp ? "ES" : ""), |
| (jrp->elem_descp ? "ED" : ""), |
| (jrp->add_elem_statp ? "AES" : ""), |
| (jrp->thresh_inp ? "TI" : "")); |
| } |
| pr2serr(">> elements in join_arr: %d, broken_ei=%d\n", k, broken_ei); |
| } |
| |
| if (! display) /* probably wanted join_arr[] built only */ |
| return 0; |
| |
| /* Display contents of join_arr */ |
| dn_len = op->desc_name ? (int)strlen(op->desc_name) : 0; |
| for (k = 0, jrp = join_arr, got1 = 0; |
| ((k < MX_JOIN_ROWS) && jrp->enc_statp); ++k, ++jrp) { |
| if (op->ind_given) { |
| if (op->ind_th != jrp->el_ind_th) |
| continue; |
| if (op->ind_indiv != jrp->el_ind_indiv) |
| continue; |
| } |
| ed_ucp = jrp->elem_descp; |
| if (op->desc_name) { |
| if (NULL == ed_ucp) |
| continue; |
| desc_len = sg_get_unaligned_be16(ed_ucp + 2); |
| /* some element descriptor strings have trailing NULLs and |
| * count them in their length; adjust */ |
| while (desc_len && ('\0' == ed_ucp[4 + desc_len - 1])) |
| --desc_len; |
| if (desc_len != dn_len) |
| continue; |
| if (0 != strncmp(op->desc_name, (const char *)(ed_ucp + 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; |
| if ((op->do_filter > 1) && (1 != (0xf & jrp->enc_statp[0]))) |
| continue; /* when '-ff' and status!=OK, skip */ |
| cp = find_element_tname(jrp->etype, b, sizeof(b)); |
| if (ed_ucp) { |
| desc_len = sg_get_unaligned_be16(ed_ucp + 2) + 4; |
| if (desc_len > 4) |
| printf("%.*s [%d,%d] Element type: %s\n", desc_len - 4, |
| (const char *)(ed_ucp + 4), jrp->el_ind_th, |
| jrp->el_ind_indiv, cp); |
| else |
| printf("[%d,%d] Element type: %s\n", jrp->el_ind_th, |
| jrp->el_ind_indiv, cp); |
| } else |
| printf("[%d,%d] Element type: %s\n", jrp->el_ind_th, |
| jrp->el_ind_indiv, cp); |
| printf(" Enclosure Status:\n"); |
| enc_status_helper(" ", jrp->enc_statp, jrp->etype, op); |
| if (jrp->add_elem_statp) { |
| printf(" Additional Element Status:\n"); |
| ae_ucp = jrp->add_elem_statp; |
| desc_len = ae_ucp[1] + 2; |
| additional_elem_helper(" ", ae_ucp, desc_len, jrp->etype, op); |
| } |
| if (jrp->thresh_inp) { |
| printf(" Threshold In:\n"); |
| t_ucp = jrp->thresh_inp; |
| ses_threshold_helper(" ", t_ucp, jrp->etype, op); |
| } |
| } |
| if (0 == got1) { |
| if (op->ind_given) |
| printf(" >>> no match on --index=%d,%d\n", op->ind_th, |
| op->ind_indiv); |
| 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"); |
| } |
| } |
| return res; |
| } |
| |
| static uint64_t |
| get_big_endian(const unsigned char * from, int start_bit, int num_bits) |
| { |
| uint64_t res; |
| int sbit_o1 = start_bit + 1; |
| |
| res = (*from++ & ((1 << sbit_o1) - 1)); |
| num_bits -= sbit_o1; |
| while (num_bits > 0) { |
| res <<= 8; |
| res |= *from++; |
| num_bits -= 8; |
| } |
| if (num_bits < 0) |
| res >>= (-num_bits); |
| return res; |
| } |
| |
| static void |
| set_big_endian(uint64_t val, unsigned char * to, int start_bit, int num_bits) |
| { |
| int sbit_o1 = start_bit + 1; |
| int mask, num, k, x; |
| |
| mask = (8 != sbit_o1) ? ((1 << sbit_o1) - 1) : 0xff; |
| k = start_bit - ((num_bits - 1) % 8); |
| if (0 != k) |
| val <<= ((k > 0) ? k : (8 + k)); |
| num = (num_bits + 15 - sbit_o1) / 8; |
| for (k = 0; k < num; ++k) { |
| if ((sbit_o1 - num_bits) > 0) |
| mask &= ~((1 << (sbit_o1 - num_bits)) - 1); |
| if (k < (num - 1)) |
| x = (val >> ((num - k - 1) * 8)) & 0xff; |
| else |
| x = val & 0xff; |
| to[k] = (to[k] & ~mask) | (x & mask); |
| mask = 0xff; |
| num_bits -= sbit_o1; |
| sbit_o1 = 8; |
| } |
| } |
| |
| /* 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, c2; |
| |
| do { |
| 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 int |
| 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 ? 1 : 0); |
| } |
| |
| static int |
| 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 ? 1 : 0); |
| } |
| |
| static int |
| 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 ? 1 : 0); |
| } |
| |
| /* DPC_ENC_STATUS DPC_ENC_CONTROL |
| * 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(int sg_fd, const struct join_row_t * jrp, |
| const struct tuple_acronym_val * tavp, |
| const struct opts_t * op) |
| { |
| int ret, len, s_byte, s_bit, n_bits, k; |
| uint64_t ui; |
| 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 (op->get_str) { |
| ui = 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 */ |
| if ((0 == op->mask_ign) && (jrp->etype < NUM_ETC)) { |
| 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) */ |
| 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; |
| ret = do_senddiag(sg_fd, 1, enc_stat_rsp, len, 1, op->verbose); |
| if (ret) { |
| pr2serr("couldn't send Enclosure Control page\n"); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| /* DPC_THRESHOLD |
| * 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(int sg_fd, const struct join_row_t * jrp, |
| const struct tuple_acronym_val * tavp, |
| const struct opts_t * op) |
| { |
| int ret, len, s_byte, s_bit, n_bits; |
| uint64_t ui; |
| 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 (op->get_str) { |
| ui = 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 { |
| 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; |
| ret = do_senddiag(sg_fd, 1, threshold_rsp, len, 1, op->verbose); |
| if (ret) { |
| pr2serr("couldn't send Threshold Out page\n"); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| /* DPC_ADD_ELEM_STATUS |
| * 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; |
| uint64_t ui; |
| const struct acronym2tuple * ap; |
| |
| if (NULL == jrp->add_elem_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 (op->get_str) { |
| ui = get_big_endian(jrp->add_elem_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(int sg_fd, const struct tuple_acronym_val * tavp, |
| struct opts_t * op) |
| { |
| int ret, k, j, desc_len, dn_len, found; |
| const struct join_row_t * jrp; |
| const unsigned char * ed_ucp; |
| char b[64]; |
| |
| found = 0; |
| if (NULL == tavp->acron) { |
| if (! op->page_code_given) |
| op->page_code = DPC_ENC_CONTROL; |
| ++found; |
| } else if (is_acronym_in_status_ctl(tavp)) { |
| op->page_code = DPC_ENC_CONTROL; |
| ++found; |
| } else if (is_acronym_in_threshold(tavp)) { |
| op->page_code = DPC_THRESHOLD; |
| ++found; |
| } else if (is_acronym_in_additional(tavp)) { |
| op->page_code = DPC_ADD_ELEM_STATUS; |
| ++found; |
| } |
| if (! found) { |
| pr2serr("acroynm %s not found (try '-ee' option)\n", tavp->acron); |
| return -1; |
| } |
| ret = join_work(sg_fd, op, 0); |
| 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->el_ind_th) |
| continue; |
| if (op->ind_indiv != jrp->el_ind_indiv) |
| continue; |
| } else if (op->desc_name) { |
| ed_ucp = jrp->elem_descp; |
| if (NULL == ed_ucp) |
| continue; |
| desc_len = sg_get_unaligned_be16(ed_ucp + 2); |
| /* some element descriptor strings have trailing NULLs and |
| * count them; adjust */ |
| while (desc_len && ('\0' == ed_ucp[4 + desc_len - 1])) |
| --desc_len; |
| if (desc_len != dn_len) |
| continue; |
| if (0 != strncmp(op->desc_name, (const char *)(ed_ucp + 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 (DPC_ENC_CONTROL == op->page_code) |
| ret = cgs_enc_ctl_stat(sg_fd, jrp, tavp, op); |
| else if (DPC_THRESHOLD == op->page_code) |
| ret = cgs_threshold(sg_fd, jrp, tavp, op); |
| else if (DPC_ADD_ELEM_STATUS == op->page_code) |
| ret = cgs_additional_el(jrp, tavp, op); |
| else { |
| pr2serr("page %s not supported for cgs\n", |
| find_element_tname(op->page_code, b, sizeof(b))); |
| ret = -1; |
| } |
| if (ret) |
| return ret; |
| break; |
| } |
| if ((NULL == jrp->enc_statp) || (k >= MX_JOIN_ROWS)) { |
| 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 not found\n", op->ind_th, op->ind_indiv); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* 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(int sg_fd, struct opts_t * op) |
| { |
| int res, len; |
| int resp_len = 0; |
| unsigned char b[64]; |
| const int control_plen = 0x24; |
| |
| memset(b, 0, sizeof(b)); |
| /* Only after the generation code, offset 4 for 4 bytes */ |
| res = do_rec_diag(sg_fd, DPC_SUBENC_NICKNAME, 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] = (unsigned char)DPC_SUBENC_NICKNAME; /* just in case */ |
| b[1] = (unsigned char)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(sg_fd, 1, b, control_plen + 4, 1, op->verbose); |
| } |
| |
| static void |
| enumerate_diag_pages(void) |
| { |
| const struct diag_page_code * pcdp; |
| const struct diag_page_abbrev * ap; |
| int got1; |
| |
| 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 = 0; ap->abbrev; ++ap) { |
| if (ap->page_code == pcdp->page_code) { |
| printf("%s%s", (got1 ? "," : ""), ap->abbrev); |
| ++got1; |
| } |
| } |
| 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; |
| const struct element_type_t * etp; |
| const struct acronym2tuple * ap; |
| char b[64]; |
| char a[160]; |
| const char * cp; |
| |
| if (op->dev_name) |
| printf(">>> DEVICE %s ignored when --%s option given.\n", |
| op->dev_name, (op->do_list ? "list" : "enumerate")); |
| num = op->enumerate + op->do_list; |
| if (num < 2) { |
| 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 { |
| /* command line has multiple --enumerate and/or --list options */ |
| printf("--clear, --get, --set acronyms for Enclosure Status/Control " |
| "['es' or 'ec'] page:\n"); |
| for (ap = ecs_a2t_arr; ap->acron; ++ap) { |
| cp = (ap->etype < 0) ? |
| "*" : find_element_tname(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); |
| } |
| 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) ? "*" : |
| find_element_tname(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) ? "*" : |
| find_element_tname(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[]) |
| { |
| int sg_fd, res; |
| char buff[128]; |
| char b[80]; |
| int pd_type = 0; |
| int have_cgs = 0; |
| int ret = 0; |
| struct sg_simple_inquiry_resp inq_resp; |
| const char * cp; |
| struct opts_t opts; |
| struct opts_t * op; |
| struct tuple_acronym_val tav; |
| |
| op = &opts; |
| memset(op, 0, sizeof(*op)); |
| res = cl_process(op, argc, argv); |
| if (res) |
| return SG_LIB_SYNTAX_ERROR; |
| if (op->do_version) { |
| pr2serr("version: %s\n", version_str); |
| return 0; |
| } |
| if (op->do_help) { |
| usage(op->do_help); |
| return 0; |
| } |
| if (op->enumerate || op->do_list) { |
| enumerate_work(op); |
| return 0; |
| } |
| if (op->num_cgs) { |
| have_cgs = 1; |
| cp = op->clear_str ? op->clear_str : |
| (op->get_str ? op->get_str : op->set_str); |
| strncpy(buff, cp, sizeof(buff) - 1); |
| buff[sizeof(buff) - 1] = '\0'; |
| if (parse_cgs_str(buff, &tav)) { |
| pr2serr("unable to decode STR argument to --clear, --get or " |
| "--set\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (op->get_str && tav.val_str) |
| pr2serr("--get option ignoring =<val> at the end of STR " |
| "argument\n"); |
| 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"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (NULL == tav.val_str) { |
| if (op->clear_str) |
| tav.val = 0; |
| if (op->set_str) |
| tav.val = 1; |
| } |
| if (op->page_code_given && (DPC_ENC_STATUS != op->page_code) && |
| (DPC_THRESHOLD != op->page_code) && |
| (DPC_ADD_ELEM_STATUS != 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"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| |
| #ifdef SG_LIB_WIN32 |
| #ifdef SG_LIB_WIN32_DIRECT |
| if (op->verbose > 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 |
| sg_fd = sg_cmds_open_device(op->dev_name, op->o_readonly, op->verbose); |
| if (sg_fd < 0) { |
| pr2serr("open error: %s: %s\n", op->dev_name, |
| safe_strerror(-sg_fd)); |
| return SG_LIB_FILE_ERROR; |
| } |
| if (! (op->do_raw || have_cgs || (op->do_hex > 2))) { |
| if (sg_simple_inquiry(sg_fd, &inq_resp, 1, op->verbose)) { |
| pr2serr("%s doesn't respond to a SCSI INQUIRY\n", op->dev_name); |
| ret = SG_LIB_CAT_OTHER; |
| goto err_out; |
| } else { |
| printf(" %.8s %.16s %.4s\n", inq_resp.vendor, |
| inq_resp.product, inq_resp.revision); |
| pd_type = inq_resp.peripheral_type; |
| cp = sg_get_pdt_str(pd_type, sizeof(buff), buff); |
| if (0xd == pd_type) { |
| if (op->verbose) |
| printf(" enclosure services device\n"); |
| } else if (0x40 & inq_resp.byte_6) |
| printf(" %s device has EncServ bit set\n", cp); |
| else |
| printf(" %s device (not an enclosure)\n", cp); |
| } |
| } |
| |
| if (op->nickname_str) |
| ret = ses_set_nickname(sg_fd, op); |
| else if (have_cgs) |
| ret = ses_cgs(sg_fd, &tav, op); |
| else if (op->do_join) |
| ret = join_work(sg_fd, op, 1); |
| else if (op->do_status) |
| ret = ses_process_status_page(sg_fd, op); |
| else { /* control page requested */ |
| op->data_arr[0] = op->page_code; |
| op->data_arr[1] = op->byte1; |
| sg_put_unaligned_be16((uint16_t)op->arr_len, op->data_arr + 2); |
| switch (op->page_code) { |
| case DPC_ENC_CONTROL: /* 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(sg_fd, 1, op->data_arr, op->arr_len + 4, 1, |
| op->verbose); |
| if (ret) { |
| pr2serr("couldn't send Enclosure Control page\n"); |
| goto err_out; |
| } |
| break; |
| case DPC_STRING: /* 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(sg_fd, 1, op->data_arr, op->arr_len + 4, 1, |
| op->verbose); |
| if (ret) { |
| pr2serr("couldn't send String Out page\n"); |
| goto err_out; |
| } |
| break; |
| case DPC_THRESHOLD: /* 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(sg_fd, 1, op->data_arr, op->arr_len + 4, 1, |
| op->verbose); |
| if (ret) { |
| pr2serr("couldn't send Threshold Out page\n"); |
| goto err_out; |
| } |
| break; |
| case DPC_ARRAY_CONTROL: /* 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(sg_fd, 1, op->data_arr, op->arr_len + 4, 1, |
| op->verbose); |
| if (ret) { |
| pr2serr("couldn't send Array Control page\n"); |
| goto err_out; |
| } |
| break; |
| case DPC_SUBENC_STRING: /* 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(sg_fd, 1, op->data_arr, op->arr_len + 4, 1, |
| op->verbose); |
| if (ret) { |
| pr2serr("couldn't send Subenclosure String Out page\n"); |
| goto err_out; |
| } |
| break; |
| case DPC_DOWNLOAD_MICROCODE: /* Download Microcode Control [0xe] */ |
| printf("Sending Download Microcode Control [0x%x] page, with " |
| "page length=%d bytes\n", op->page_code, op->arr_len); |
| printf(" Perhaps it would be better to use the sg_ses_microcode " |
| "utility\n"); |
| ret = do_senddiag(sg_fd, 1, op->data_arr, op->arr_len + 4, 1, |
| op->verbose); |
| if (ret) { |
| pr2serr("couldn't send Download Microcode Control page\n"); |
| goto err_out; |
| } |
| break; |
| case DPC_SUBENC_NICKNAME: /* Subenclosure Nickname Control [0xf] */ |
| printf("Sending Subenclosure Nickname Control [0x%x] page, with " |
| "page length=%d bytes\n", op->page_code, op->arr_len); |
| ret = do_senddiag(sg_fd, 1, op->data_arr, op->arr_len + 4, 1, |
| op->verbose); |
| 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 (0 == op->do_status) { |
| sg_get_category_sense_str(ret, sizeof(b), b, op->verbose); |
| pr2serr(" %s\n", b); |
| } |
| if (ret && (0 == op->verbose)) |
| pr2serr("Problem detected, try again with --verbose option for more " |
| "information\n"); |
| res = sg_cmds_close_device(sg_fd); |
| if (res < 0) { |
| pr2serr("close error: %s\n", safe_strerror(-res)); |
| if (0 == ret) |
| return SG_LIB_FILE_ERROR; |
| } |
| return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; |
| } |