| /* A utility program originally written for the Linux OS SCSI subsystem. |
| * Copyright (C) 2000-2016 D. Gilbert |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This program outputs information provided by a SCSI LOG SENSE command |
| * and in some cases issues a LOG SELECT command. |
| * |
| */ |
| |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <getopt.h> |
| #define __STDC_FORMAT_MACROS 1 |
| #include <inttypes.h> |
| #include <errno.h> |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| #include "sg_lib.h" |
| #include "sg_cmds_basic.h" |
| #include "sg_pt.h" /* needed for scsi_pt_win32_direct() */ |
| #include "sg_unaligned.h" |
| #include "sg_pr2serr.h" |
| |
| static const char * version_str = "1.39 20160203"; /* spc5r08 + sbc4r10 */ |
| |
| #define MX_ALLOC_LEN (0xfffc) |
| #define SHORT_RESP_LEN 128 |
| |
| #define SUPP_PAGES_LPAGE 0x0 |
| #define BUFF_OVER_UNDER_LPAGE 0x1 |
| #define WRITE_ERR_LPAGE 0x2 |
| #define READ_ERR_LPAGE 0x3 |
| #define READ_REV_ERR_LPAGE 0x4 |
| #define VERIFY_ERR_LPAGE 0x5 |
| #define NON_MEDIUM_LPAGE 0x6 |
| #define LAST_N_ERR_LPAGE 0x7 |
| #define FORMAT_STATUS_LPAGE 0x8 |
| #define LAST_N_DEFERRED_LPAGE 0xb |
| #define LB_PROV_LPAGE 0xc |
| #define TEMPERATURE_LPAGE 0xd |
| #define START_STOP_LPAGE 0xe |
| #define APP_CLIENT_LPAGE 0xf |
| #define SELF_TEST_LPAGE 0x10 |
| #define SOLID_STATE_MEDIA_LPAGE 0x11 |
| #define BACKGROUND_SCAN_LPAGE 0x15 |
| #define SAT_ATA_RESULTS_LPAGE 0x16 |
| #define PROTO_SPECIFIC_LPAGE 0x18 |
| #define STATS_LPAGE 0x19 |
| #define PCT_LPAGE 0x1a |
| #define TAPE_ALERT_LPAGE 0x2e |
| #define IE_LPAGE 0x2f |
| #define NOT_SPG_SUBPG 0x0 |
| #define SUPP_SPGS_SUBPG 0xff |
| #define LOW_GRP_STATS_SUBPG 0x1 |
| #define PENDING_DEFECTS_SUBPG 0x1 |
| #define BACKGROUND_OP_SUBPG 0x2 |
| #define HIGH_GRP_STATS_SUBPG 0x1f |
| #define CACHE_STATS_SUBPG 0x20 |
| #define ENV_REPORTING_SUBPG 0x1 |
| #define UTILIZATION_SUBPG 0x1 |
| #define ENV_LIMITS_SUBPG 0x2 |
| #define LPS_MISALIGNMENT_SUBPG 0x3 |
| |
| #define VENDOR_M 0x1000 |
| #define LTO5_M 0x2000 |
| |
| #define PCB_STR_LEN 128 |
| |
| #define LOG_SENSE_PROBE_ALLOC_LEN 4 |
| |
| static uint8_t rsp_buff[MX_ALLOC_LEN + 4]; |
| |
| static struct option long_options[] = { |
| {"All", no_argument, 0, 'A'}, /* equivalent to '-aa' */ |
| {"all", no_argument, 0, 'a'}, |
| {"brief", no_argument, 0, 'b'}, |
| {"control", required_argument, 0, 'c'}, |
| {"enumerate", no_argument, 0, 'e'}, |
| {"enum_vendor", no_argument, 0, 'E'}, |
| {"filter", required_argument, 0, 'f'}, |
| {"help", no_argument, 0, 'h'}, |
| {"hex", no_argument, 0, 'H'}, |
| {"in", required_argument, 0, 'i'}, |
| {"list", no_argument, 0, 'l'}, |
| {"maxlen", required_argument, 0, 'm'}, |
| {"name", no_argument, 0, 'n'}, |
| {"new", no_argument, 0, 'N'}, |
| {"no_inq", no_argument, 0, 'x'}, |
| {"old", no_argument, 0, 'O'}, |
| {"page", required_argument, 0, 'p'}, |
| {"paramp", required_argument, 0, 'P'}, |
| {"pcb", no_argument, 0, 'q'}, |
| {"ppc", no_argument, 0, 'Q'}, |
| {"raw", no_argument, 0, 'r'}, |
| {"readonly", no_argument, 0, 'X'}, |
| {"reset", no_argument, 0, 'R'}, |
| {"sp", no_argument, 0, 's'}, |
| {"select", no_argument, 0, 'S'}, |
| {"temperature", no_argument, 0, 't'}, |
| {"transport", no_argument, 0, 'T'}, |
| {"verbose", no_argument, 0, 'v'}, |
| {"version", no_argument, 0, 'V'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| struct opts_t { |
| int do_all; |
| int do_brief; |
| int do_enumerate; |
| int do_enum_vendor; |
| int do_help; |
| int do_hex; |
| int do_list; |
| int do_name; |
| int do_pcb; |
| int do_ppc; |
| int do_raw; |
| int o_readonly; |
| int do_pcreset; |
| int do_select; |
| int do_sp; |
| int do_temperature; |
| int do_transport; |
| int verbose; |
| int do_version; |
| int filter; |
| int filter_given; |
| int page_control; |
| int maxlen; |
| int pg_code; |
| int subpg_code; |
| int paramp; |
| int opt_new; |
| int no_inq; |
| int dev_pdt; |
| const char * device_name; |
| const char * in_fn; |
| const char * pg_arg; |
| const struct log_elem * lep; |
| }; |
| |
| |
| struct log_elem { |
| int pg_code; |
| int subpg_code; /* only unless subpg_high>0 then this is only */ |
| int subpg_high; /* when >0 this is high end of subpage range */ |
| int pdt; /* -1 for all */ |
| int flags; /* bit mask; only VENDOR_M to start with */ |
| const char * name; |
| const char * acron; |
| bool (*show_pagep)(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| /* Returns true if done */ |
| }; |
| |
| static bool show_supported_pgs_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_supported_pgs_sub_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_buffer_over_under_run_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_error_counter_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_non_medium_error_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_last_n_error_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_format_status_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_last_n_deferred_error_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_lb_provisioning_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_sequential_access_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_temperature_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_start_stop_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_utilization_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_app_client_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_self_test_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_solid_state_media_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_device_stats_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_media_stats_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_dt_device_status_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_background_scan_results_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_pending_defects_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_background_op_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_lps_misalignment_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_element_stats_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_ata_pt_results_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_tape_diag_data_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_mchanger_diag_data_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_non_volatile_cache_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_volume_stats_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_protocol_specific_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_stats_perform_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_cache_stats_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_power_condition_transitions_page(const uint8_t * resp, |
| int len, const struct opts_t * op); |
| static bool show_environmental_reporting_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_environmental_limits_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_data_compression_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_tape_alert_ssc_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_ie_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_tape_usage_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_tape_capacity_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_seagate_cache_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| static bool show_seagate_factory_page(const uint8_t * resp, int len, |
| const struct opts_t * op); |
| |
| /* elements in page_number/subpage_number order */ |
| static struct log_elem log_arr[] = { |
| {SUPP_PAGES_LPAGE, 0, 0, -1, 0, "Supported log pages", "sp", |
| show_supported_pgs_page}, /* 0, 0 */ |
| {SUPP_PAGES_LPAGE, SUPP_SPGS_SUBPG, 0, -1, 0, "Supported log pages and " |
| "subpages", "ssp", show_supported_pgs_sub_page}, /* 0, 0xff */ |
| {BUFF_OVER_UNDER_LPAGE, 0, 0, -1, 0, "Buffer over-run/under-run", "bou", |
| show_buffer_over_under_run_page}, /* 0x1, 0x0 */ |
| {WRITE_ERR_LPAGE, 0, 0, -1, 0, "Write error", "we", |
| show_error_counter_page}, /* 0x2, 0x0 */ |
| {READ_ERR_LPAGE, 0, 0, -1, 0, "Read error", "re", |
| show_error_counter_page}, /* 0x3, 0x0 */ |
| {READ_REV_ERR_LPAGE, 0, 0, -1, 0, "Read reverse error", "rre", |
| show_error_counter_page}, /* 0x4, 0x0 */ |
| {VERIFY_ERR_LPAGE, 0, 0, -1, 0, "Verify error", "ve", |
| show_error_counter_page}, /* 0x5, 0x0 */ |
| {NON_MEDIUM_LPAGE, 0, 0, -1, 0, "Non medium", "nm", |
| show_non_medium_error_page}, /* 0x6, 0x0 */ |
| {LAST_N_ERR_LPAGE, 0, 0, -1, 0, "Last n error", "lne", |
| show_last_n_error_page}, /* 0x7, 0x0 */ |
| {FORMAT_STATUS_LPAGE, 0, 0, 0, 0, "Format status", "fs", |
| show_format_status_page}, /* 0x8, 0x0 SBC */ |
| {LAST_N_DEFERRED_LPAGE, 0, 0, -1, 0, "Last n deferred error", "lnd", |
| show_last_n_deferred_error_page}, /* 0xb, 0x0 */ |
| {LB_PROV_LPAGE, 0, 0, 0, 0, "Logical block provisioning", "lbp", |
| show_lb_provisioning_page}, /* 0xc, 0x0 SBC */ |
| {0xc, 0, 0, PDT_TAPE, 0, "Sequential access device", "sad", |
| show_sequential_access_page}, /* 0xc, 0x0 SSC */ |
| {TEMPERATURE_LPAGE, 0, 0, -1, 0, "Temperature", "temp", |
| show_temperature_page}, /* 0xd, 0x0 */ |
| {TEMPERATURE_LPAGE, ENV_REPORTING_SUBPG, 0, -1, 0, /* 0xd, 0x1 */ |
| "Environmental reporting", "enr", show_environmental_reporting_page}, |
| {TEMPERATURE_LPAGE, ENV_LIMITS_SUBPG, 0, -1, 0, /* 0xd, 0x2 */ |
| "Environmental limits", "enl", show_environmental_limits_page}, |
| {START_STOP_LPAGE, 0, 0, -1, 0, "Start-stop cycle counter", "sscc", |
| show_start_stop_page}, /* 0xe, 0x0 */ |
| {START_STOP_LPAGE, UTILIZATION_SUBPG, 0, 0, 0, "Utilization", "util", |
| show_utilization_page}, /* 0xe, 0x1 SBC */ /* sbc4r04 */ |
| {APP_CLIENT_LPAGE, 0, 0, -1, 0, "Application client", "ac", |
| show_app_client_page}, /* 0xf, 0x0 */ |
| {SELF_TEST_LPAGE, 0, 0, -1, 0, "Self test results", "str", |
| show_self_test_page}, /* 0x10, 0x0 */ |
| {SOLID_STATE_MEDIA_LPAGE, 0, 0, 0, 0, "Solid state media", "ssm", |
| show_solid_state_media_page}, /* 0x11, 0x0 SBC */ |
| {0x11, 0, 0, PDT_TAPE, 0, "DT Device status", "dtds", |
| show_dt_device_status_page}, /* 0x11, 0x0 SSC,ADC */ |
| {0x12, 0, 0, PDT_TAPE, 0, "Tape alert response", "tar", |
| NULL}, /* 0x12, 0x0 SSC,ADC */ |
| {0x13, 0, 0, PDT_TAPE, 0, "Requested recovery", "rr", |
| NULL}, /* 0x13, 0x0 SSC,ADC */ |
| {0x14, 0, 0, PDT_TAPE, 0, "Device statistics", "ds", |
| show_device_stats_page}, /* 0x14, 0x0 SSC,ADC */ |
| {0x14, 0, 0, PDT_MCHANGER, 0, "Media changer statistics", "mcs", |
| show_media_stats_page}, /* 0x14, 0x0 SMC */ |
| {BACKGROUND_SCAN_LPAGE, 0, 0, 0, 0, "Background scan results", "bsr", |
| show_background_scan_results_page}, /* 0x15, 0x0 SBC */ |
| {BACKGROUND_SCAN_LPAGE, BACKGROUND_OP_SUBPG, 0, 0, 0, |
| "Background operation", "bop", show_background_op_page}, |
| /* 0x15, 0x2 SBC */ |
| {BACKGROUND_SCAN_LPAGE, LPS_MISALIGNMENT_SUBPG, 0, 0, 0, |
| "LPS misalignment", "lps", show_lps_misalignment_page}, |
| /* 0x15, 0x3 SBC-4 */ |
| {0x15, 0, 0, PDT_MCHANGER, 0, "Element statistics", "els", |
| show_element_stats_page}, /* 0x15, 0x0 SMC */ |
| {0x15, 0, 0, PDT_ADC, 0, "Service buffers information", "sbi", |
| NULL}, /* 0x15, 0x0 ADC */ |
| {BACKGROUND_SCAN_LPAGE, PENDING_DEFECTS_SUBPG, 0, 0, 0, |
| "Pending defects", "pd", show_pending_defects_page}, /* 0x15, 0x1 SBC */ |
| {SAT_ATA_RESULTS_LPAGE, 0, 0, 0, 0, "ATA pass-through results", "aptr", |
| show_ata_pt_results_page}, /* 0x16, 0x0 SAT */ |
| {0x16, 0, 0, PDT_TAPE, 0, "Tape diagnostic data", "tdd", |
| show_tape_diag_data_page}, /* 0x16, 0x0 SSC */ |
| {0x16, 0, 0, PDT_MCHANGER, 0, "Media changer diagnostic data", "mcdd", |
| show_mchanger_diag_data_page}, /* 0x16, 0x0 SMC */ |
| {0x17, 0, 0, 0, 0, "Non volatile cache", "nvc", |
| show_non_volatile_cache_page}, /* 0x17, 0x0 SBC */ |
| {0x17, 0, 0, PDT_TAPE, 0, "Volume statistics", "vs", |
| show_volume_stats_page}, /* 0x17, 0x0 SSC */ |
| {PROTO_SPECIFIC_LPAGE, 0, 0, -1, 0, "Protocol specific port", "psp", |
| show_protocol_specific_page}, /* 0x18, 0x0 */ |
| {STATS_LPAGE, 0, 0, -1, 0, "General Statistics and Performance", "gsp", |
| show_stats_perform_page}, /* 0x19, 0x0 */ |
| {STATS_LPAGE, 0x1, 0x1f, -1, 0, "Group Statistics and Performance", "grsp", |
| show_stats_perform_page}, /* 0x19, 0x1...0x1f */ |
| {STATS_LPAGE, 0x20, 0, -1, 0, "Cache memory statistics", "cms", |
| show_cache_stats_page}, /* 0x19, 0x20 */ |
| {PCT_LPAGE, 0, 0, -1, 0, "Power condition transitions", "pct", |
| show_power_condition_transitions_page}, /* 0x1a, 0 */ |
| {0x1b, 0, 0, PDT_TAPE, 0, "Data compression", "dc", |
| show_data_compression_page}, /* 0x1b, 0 SSC */ |
| {0x2d, 0, 0, PDT_TAPE, 0, "Current service information", "csi", |
| NULL}, /* 0x2d, 0 SSC */ |
| {TAPE_ALERT_LPAGE, 0, 0, PDT_TAPE, 0, "Tape alert", "ta", |
| show_tape_alert_ssc_page}, /* 0x2e, 0 SSC */ |
| {IE_LPAGE, 0, 0, -1, 0, "Informational exceptions", "ie", |
| show_ie_page}, /* 0x2f, 0 */ |
| /* vendor specific */ |
| {0x30, 0, 0, PDT_DISK, VENDOR_M, "Performance counters (Hitachi)", |
| "pc_hi", NULL}, /* 0x30, 0 SBC */ |
| {0x30, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Tape usage (lto-5, 6)", "tu_", |
| show_tape_usage_page}, /* 0x30, 0 SSC */ |
| {0x31, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Tape capacity (lto-5, 6)", |
| "tc_", show_tape_capacity_page}, /* 0x31, 0 SSC */ |
| {0x32, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Data compression (lto-5)", |
| "dc_", show_data_compression_page}, /* 0x32, 0 SSC; redirect to 0x1b */ |
| {0x33, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Write errors (lto-5)", "we_", |
| NULL}, /* 0x33, 0 SSC */ |
| {0x34, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Read forward errors (lto-5)", |
| "rfe_", NULL}, /* 0x34, 0 SSC */ |
| {0x35, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "DT Device Error (lto-5, 6)", |
| "dtde_", NULL}, /* 0x35, 0 SSC */ |
| {0x37, 0, 0, PDT_DISK, VENDOR_M, "Cache (seagate)", "c_se", |
| show_seagate_cache_page}, /* 0x37, 0 SBC */ |
| {0x37, 0, 0, PDT_DISK, VENDOR_M, "Miscellaneous (hitachi)", "mi_hi", |
| NULL}, /* 0x37, 0 SBC */ |
| {0x37, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Performance characteristics " |
| "(lto-5)", "pc_", NULL}, /* 0x37, 0 SSC */ |
| {0x38, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Blocks/bytes transferred " |
| "(lto-5)", "bbt_", NULL}, /* 0x38, 0 SSC */ |
| {0x39, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Host port 0 interface errors " |
| "(lto-5)", "hp0_", NULL}, /* 0x39, 0 SSC */ |
| {0x3a, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Drive control verification " |
| "(lto-5)", "dcv_", NULL}, /* 0x3a, 0 SSC */ |
| {0x3b, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Host port 1 interface errors " |
| "(lto-5)", "hp1_", NULL}, /* 0x3b, 0 SSC */ |
| {0x3c, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Drive usage information " |
| "(lto-5)", "dui_", NULL}, /* 0x3c, 0 SSC */ |
| {0x3d, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Subsystem statistics (lto-5)", |
| "ss_", NULL}, /* 0x3d, 0 SSC */ |
| {0x3e, 0, 0, PDT_DISK, VENDOR_M, "Factory (seagate)", "f_se", |
| show_seagate_factory_page}, /* 0x3e, 0 SBC */ |
| {0x3e, 0, 0, PDT_DISK, VENDOR_M, "Factory (hitachi)", "f_hi", |
| NULL}, /* 0x3e, 0 SBC */ |
| {0x3e, 0, 0, PDT_TAPE, VENDOR_M | LTO5_M, "Device Status (lto-5, 6)", |
| "ds_", NULL}, /* 0x3e, 0 SSC */ |
| |
| {-1, -1, -1, -1, -1, NULL, "zzzzz", NULL}, /* end sentinel */ |
| }; |
| |
| #ifdef SG_LIB_WIN32 |
| static int win32_spt_init_state = 0; |
| static int win32_spt_curr_state = 0; |
| #endif |
| |
| |
| static void |
| usage(int hval) |
| { |
| if (1 == hval) { |
| pr2serr( |
| "Usage: sg_logs [-All] [--all] [--brief] [--control=PC] " |
| "[--enumerate]\n" |
| " [--enum_vendor] [--filter=FL] [--help] [--hex] " |
| "[--in=FN]\n" |
| " [--list] [--no_inq] [--maxlen=LEN] [--name] " |
| "[--page=PG]\n" |
| " [--paramp=PP] [--pcb] [--ppc] [--raw] " |
| "[--readonly]\n" |
| " [--reset] [--select] [--sp] [--temperature] " |
| "[--transport]\n" |
| " [--verbose] [--version] DEVICE\n" |
| " where the main options are:\n" |
| " --All|-A fetch and decode all log pages and " |
| "subpages\n" |
| " --all|-a fetch and decode all log pages, but not " |
| "subpages; use\n" |
| " twice to fetch and decode all log pages " |
| "and subpages\n" |
| " --brief|-b shorten the output of some log pages\n" |
| " --enumerate|-e enumerate known pages, ignore DEVICE. " |
| "Sort order,\n" |
| " '-e': all by acronym; '-ee': non-vendor " |
| "by acronym;\n" |
| " '-eee': all numerically; '-eeee': " |
| "non-v numerically\n" |
| " --enum_vendor|-E enumerate known specific vendor pages " |
| "only\n" |
| " --filter=FL|-f FL FL is parameter code to display (def: " |
| "all);\n" |
| " with '-e' then FL>=0 enumerate that " |
| "pdt + spc\n" |
| " FL=-1 all (default), FL=-2 spc only\n" |
| " --help|-h print usage message then exit. Use twice " |
| "for more help\n" |
| " --hex|-H output response in hex (default: decode if " |
| "known)\n" |
| " --in=FN|-i FN FN is a filename containing a log page " |
| "in ASCII hex\n" |
| " or binary if --raw also given.\n" |
| " --page=PG|-p PG PG is either log page acronym, PGN or " |
| "PGN,SPGN\n" |
| " where (S)PGN is a (sub) page number\n"); |
| pr2serr( |
| " --raw|-r either output response in binary to stdout " |
| "or, if\n" |
| " '--in=FN' is given, FN is decoded as " |
| "binary\n" |
| " --temperature|-t decode temperature (log page 0xd or " |
| "0x2f)\n" |
| " --transport|-T decode transport (protocol specific port " |
| "0x18) page\n" |
| " --verbose|-v increase verbosity\n\n" |
| "Performs a SCSI LOG SENSE (or LOG SELECT) command and decodes " |
| "the response.\nIf only DEVICE is given then '-p sp' (supported " |
| "pages) is assumed. Use\n'-e' to see known pages and their " |
| "acronyms. For more help use '-hh'.\n"); |
| } else if (hval > 1) { |
| pr2serr( |
| " where sg_logs' lesser used options are:\n" |
| " --control=PC|-c PC page control(PC) (default: 1)\n" |
| " 0: current threshhold, 1: current " |
| "cumulative\n" |
| " 2: default threshhold, 3: default " |
| "cumulative\n" |
| " --list|-l list supported log page names (equivalent to " |
| "'-p sp')\n" |
| " use twice to list supported log page and " |
| "subpage names\n" |
| " --maxlen=LEN|-m LEN max response length (def: 0 " |
| "-> everything)\n" |
| " when > 1 will request LEN bytes\n" |
| " --name|-n decode some pages into multiple name=value " |
| "lines\n" |
| " --no_inq|-x no initial INQUIRY output (twice: no " |
| "INQUIRY call)\n" |
| " --old|-O use old interface (use as first option)\n" |
| " --paramp=PP|-P PP parameter pointer (decimal) (def: 0)\n" |
| " --pcb|-q show parameter control bytes in decoded " |
| "output\n" |
| " --ppc|-Q set the Parameter Pointer Control (PPC) bit " |
| "(def: 0)\n" |
| " --readonly|-X open DEVICE read-only (def: first " |
| "read-write then if\n" |
| " fails try open again read-only)\n" |
| " --reset|-R reset log parameters (takes PC and SP into " |
| "account)\n" |
| " (uses PCR bit in LOG SELECT)\n" |
| " --select|-S perform LOG SELECT (def: LOG SENSE)\n" |
| " --sp|-s set the Saving Parameters (SP) bit (def: " |
| "0)\n" |
| " --version|-V output version string then exit\n\n" |
| "If DEVICE and --select are given, a LOG SELECT command will be " |
| "issued. If\nDEVICE is not given and '--in=FN' is given then FN " |
| "will decoded as if it\nwere a log page. Pages defined in SPC " |
| "are common to all device types.\n"); |
| } |
| } |
| |
| static void |
| usage_old() |
| { |
| printf("Usage: sg_logs [-a] [-A] [-b] [-c=PC] [-e] [-E] [-f=FL] [-h] " |
| "[-H]\n" |
| " [-i=FN] [-l] [-L] [-m=LEN] [-n] [-p=PG] " |
| "[-paramp=PP]\n" |
| " [-pcb] [-ppc] [-r] [-select] [-sp] [-t] [-T] " |
| "[-v] [-V]\n" |
| " [-x] [-X] [-?] DEVICE\n" |
| " where:\n" |
| " -a fetch and decode all log pages\n" |
| " -A fetch and decode all log pages and subpages\n" |
| " -b shorten the output of some log pages\n" |
| " -c=PC page control(PC) (default: 1)\n" |
| " 0: current threshhold, 1: current cumulative\n" |
| " 2: default threshhold, 3: default cumulative\n" |
| " -e enumerate known log pages\n" |
| " -E enumerate known vendor specific log pages only\n" |
| " -f=FL filter match parameter code or pdt\n" |
| " -h output in hex (default: decode if known)\n" |
| " -H output in hex (same as '-h')\n" |
| " -i=FN FN is a filename containing a log page " |
| "in ASCII hex.\n" |
| " -l list supported log page names (equivalent to " |
| "'-p=0')\n" |
| " -L list supported log page and subpages names " |
| "(equivalent to\n" |
| " '-p=0,ff')\n" |
| " -m=LEN max response length (decimal) (def: 0 " |
| "-> everything)\n" |
| " -n decode some pages into multiple name=value " |
| "lines\n" |
| " -p=PG PG is an acronym (def: 'sp')\n" |
| " -p=PGN page code in hex (def: 0)\n" |
| " -p=PGN,SPGN page and subpage codes in hex, (defs: 0,0)\n" |
| " -paramp=PP (in hex) (def: 0)\n" |
| " -pcb show parameter control bytes in decoded " |
| "output\n"); |
| printf(" -ppc set the Parameter Pointer Control (PPC) bit " |
| "(def: 0)\n" |
| " -r reset log parameters (takes PC and SP into " |
| "account)\n" |
| " (uses PCR bit in LOG SELECT)\n" |
| " -select perform LOG SELECT (def: LOG SENSE)\n" |
| " -sp set the Saving Parameters (SP) bit (def: 0)\n" |
| " -t outputs temperature log page (0xd)\n" |
| " -T outputs transport (protocol specific port) log " |
| "page (0x18)\n" |
| " -v increase verbosity\n" |
| " -V output version string\n" |
| " -x no initial INQUIRY output (twice: no INQUIRY call)\n" |
| " -X open DEVICE read-only (def: first read-write then " |
| "if fails\n" |
| " try open again with read-only)\n" |
| " -? output this usage message\n\n" |
| "Performs a SCSI LOG SENSE (or LOG SELECT) command\n"); |
| } |
| |
| static int |
| asort_comp(const void * lp, const void * rp) |
| { |
| const struct log_elem * const * lepp = |
| (const struct log_elem * const *)lp; |
| const struct log_elem * const * repp = |
| (const struct log_elem * const *)rp; |
| |
| return strcmp((*lepp)->acron, (*repp)->acron); |
| } |
| |
| static void |
| enumerate_helper(const struct log_elem * lep, int pos, |
| const struct opts_t * op) |
| { |
| char b[80]; |
| char bb[80]; |
| const char * cp; |
| |
| if (0 == pos) { |
| if (1 == op->verbose) { |
| printf("acronym pg[,spg] name\n"); |
| printf("===============================================\n"); |
| } else if (2 == op->verbose) { |
| printf("acronym pg[,spg] pdt name\n"); |
| printf("===================================================\n"); |
| } |
| } |
| if ((op->do_enum_vendor > 0) && !(VENDOR_M & lep->flags)) |
| return; |
| if ((0 == (op->do_enumerate % 2)) && (VENDOR_M & lep->flags)) |
| return; /* if do_enumerate is even then skip vendor pages */ |
| else if ((! op->filter_given) || (-1 == op->filter)) |
| ; /* otherwise enumerate all lpages if no --filter= */ |
| else if (-2 == op->filter) { /* skip non-SPC pages */ |
| if (lep->pdt >= 0) |
| return; |
| } else if (-10 == op->filter) { /* skip non-disk like pages */ |
| if (sg_lib_pdt_decay(lep->pdt) != 0) |
| return; |
| } else if (-11 == op->filter) { /* skip tape like device pages */ |
| if (sg_lib_pdt_decay(lep->pdt) != 1) |
| return; |
| } else if ((op->filter >= 0) && (op->filter <= 0x1f)) { |
| if ((lep->pdt >= 0) && (lep->pdt != op->filter) && |
| (lep->pdt != sg_lib_pdt_decay(op->filter))) |
| return; |
| } |
| if (lep->subpg_high > 0) |
| snprintf(b, sizeof(b), "0x%x,0x%x->0x%x", lep->pg_code, |
| lep->subpg_code, lep->subpg_high); |
| else if (lep->subpg_code > 0) |
| snprintf(b, sizeof(b), "0x%x,0x%x", lep->pg_code, |
| lep->subpg_code); |
| else |
| snprintf(b, sizeof(b), "0x%x", lep->pg_code); |
| snprintf(bb, sizeof(bb), "%-16s", b); |
| cp = (op->verbose && (! lep->show_pagep)) ? " [hex only]" : ""; |
| if (op->verbose > 1) { |
| if (lep->pdt < 0) |
| printf(" %-8s%s- %s%s\n", lep->acron, bb, lep->name, cp); |
| else |
| printf(" %-8s%s0x%02x %s%s\n", lep->acron, bb, lep->pdt, |
| lep->name, cp); |
| } else |
| printf(" %-8s%s%s%s\n", lep->acron, bb, lep->name, cp); |
| } |
| |
| static void |
| enumerate_pages(const struct opts_t * op) |
| { |
| int k, j; |
| struct log_elem * lep; |
| struct log_elem ** lepp; |
| struct log_elem ** lep_arr; |
| |
| if (op->do_enumerate < 3) { /* -e, -ee: sort by acronym */ |
| for (k = 0, lep = log_arr; lep->pg_code >=0; ++lep, ++k) |
| ; |
| ++k; |
| lep_arr = (struct log_elem **)calloc(k, sizeof(struct log_elem *)); |
| if (NULL == lep_arr) { |
| pr2serr("%s: out of memory\n", __func__); |
| return; |
| } |
| for (k = 0, lep = log_arr; lep->pg_code >=0; ++lep, ++k) |
| lep_arr[k] = lep; |
| lep_arr[k++] = lep; /* put sentinel on end */ |
| qsort(lep_arr, k, sizeof(struct log_elem *), asort_comp); |
| printf("Known log pages in acronym order:\n"); |
| for (lepp = lep_arr, j = 0; (*lepp)->pg_code >=0; ++lepp, ++j) |
| enumerate_helper(*lepp, j, op); |
| free(lep_arr); |
| } else { /* -eee, -eeee numeric sort (as per table) */ |
| printf("Known log pages in numerical order:\n"); |
| for (lep = log_arr, j = 0; lep->pg_code >=0; ++lep, ++j) |
| enumerate_helper(lep, j, op); |
| } |
| } |
| |
| static const struct log_elem * |
| acron_search(const char * acron) |
| { |
| const struct log_elem * lep; |
| |
| for (lep = log_arr; lep->pg_code >=0; ++lep) { |
| if (0 == strcmp(acron, lep->acron)) |
| return lep; |
| } |
| return NULL; |
| } |
| |
| static const struct log_elem * |
| pg_subpg_pdt_search(int pg_code, int subpg_code, int pdt) |
| { |
| const struct log_elem * lep; |
| int d_pdt; |
| |
| d_pdt = sg_lib_pdt_decay(pdt); |
| for (lep = log_arr; lep->pg_code >=0; ++lep) { |
| if (pg_code == lep->pg_code) { |
| if (subpg_code == lep->subpg_code) { |
| if ((lep->pdt < 0) || (pdt == lep->pdt) || (pdt < 0)) |
| return lep; |
| else if (d_pdt == lep->pdt) |
| return lep; |
| else if (pdt == sg_lib_pdt_decay(lep->pdt)) |
| return lep; |
| } else if ((lep->subpg_high > 0) && |
| (subpg_code > lep->subpg_code) && |
| (subpg_code <= lep->subpg_high)) |
| return lep; |
| } |
| } |
| return NULL; |
| } |
| |
| static void |
| usage_for(int hval, const struct opts_t * op) |
| { |
| if (op->opt_new) |
| usage(hval); |
| else |
| usage_old(); |
| } |
| |
| /* Processes command line options according to new option format. Returns |
| * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */ |
| static int |
| process_cl_new(struct opts_t * op, int argc, char * argv[]) |
| { |
| int c, n; |
| |
| while (1) { |
| int option_index = 0; |
| |
| c = getopt_long(argc, argv, "aAbc:eEf:hHi:lLm:nNOp:P:qQrRsStTvVxX", |
| long_options, &option_index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'a': |
| ++op->do_all; |
| break; |
| case 'A': /* not documented: compatibility with old interface */ |
| op->do_all += 2; |
| break; |
| case 'b': |
| ++op->do_brief; |
| break; |
| case 'c': |
| n = sg_get_num(optarg); |
| if ((n < 0) || (n > 3)) { |
| pr2serr("bad argument to '--control='\n"); |
| usage(2); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->page_control = n; |
| break; |
| case 'e': |
| ++op->do_enumerate; |
| break; |
| case 'E': |
| ++op->do_enum_vendor; |
| break; |
| case 'f': |
| if ('-' == optarg[0]) { |
| n = sg_get_num(optarg + 1); |
| if ((n < 0) || (n > 0x30)) { |
| pr2serr("bad negated argument to '--filter='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->filter = -n; |
| } else { |
| n = sg_get_num(optarg); |
| if ((n < 0) || (n > 0xffff)) { |
| pr2serr("bad argument to '--filter='\n"); |
| usage(1); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->filter = n; |
| } |
| ++op->filter_given; |
| break; |
| case 'h': |
| case '?': |
| ++op->do_help; |
| break; |
| case 'H': |
| ++op->do_hex; |
| break; |
| case 'i': |
| op->in_fn = optarg; |
| break; |
| case 'l': |
| ++op->do_list; |
| break; |
| case 'L': |
| op->do_list += 2; |
| break; |
| case 'm': |
| n = sg_get_num(optarg); |
| if ((n < 0) || (1 == n) || (n > 0xffff)) { |
| pr2serr("bad argument to '--maxlen=', from 2 to 65535 " |
| "(inclusive) expected\n"); |
| usage(2); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->maxlen = n; |
| break; |
| case 'n': |
| ++op->do_name; |
| break; |
| case 'N': |
| break; /* ignore */ |
| case 'O': |
| op->opt_new = 0; |
| return 0; |
| case 'p': |
| op->pg_arg = optarg; |
| break; |
| case 'P': |
| n = sg_get_num(optarg); |
| if (n < 0) { |
| pr2serr("bad argument to '--paramp='\n"); |
| usage(2); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->paramp = n; |
| break; |
| case 'q': |
| ++op->do_pcb; |
| break; |
| case 'Q': /* N.B. PPC bit obsoleted in SPC-4 rev 18 */ |
| ++op->do_ppc; |
| break; |
| case 'r': |
| ++op->do_raw; |
| break; |
| case 'R': |
| ++op->do_pcreset; |
| ++op->do_select; |
| break; |
| case 's': |
| ++op->do_sp; |
| break; |
| case 'S': |
| ++op->do_select; |
| break; |
| case 't': |
| ++op->do_temperature; |
| break; |
| case 'T': |
| ++op->do_transport; |
| break; |
| case 'v': |
| ++op->verbose; |
| break; |
| case 'V': |
| ++op->do_version; |
| break; |
| case 'x': |
| ++op->no_inq; |
| break; |
| case 'X': |
| ++op->o_readonly; |
| break; |
| default: |
| pr2serr("unrecognised option code %c [0x%x]\n", c, c); |
| if (op->do_help) |
| break; |
| usage(1); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| if (optind < argc) { |
| if (NULL == op->device_name) { |
| op->device_name = argv[optind]; |
| ++optind; |
| } |
| if (optind < argc) { |
| for (; optind < argc; ++optind) |
| pr2serr("Unexpected extra argument: %s\n", argv[optind]); |
| usage(1); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| return 0; |
| } |
| |
| /* Processes command line options according to old option format. Returns |
| * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */ |
| static int |
| process_cl_old(struct opts_t * op, int argc, char * argv[]) |
| { |
| int k, jmp_out, plen, num, n; |
| unsigned int u, uu; |
| const char * cp; |
| |
| for (k = 1; k < argc; ++k) { |
| cp = argv[k]; |
| plen = strlen(cp); |
| if (plen <= 0) |
| continue; |
| if ('-' == *cp) { |
| for (--plen, ++cp, jmp_out = 0; plen > 0; --plen, ++cp) { |
| switch (*cp) { |
| case 'a': |
| ++op->do_all; |
| break; |
| case 'A': |
| op->do_all += 2; |
| break; |
| case 'b': |
| ++op->do_brief; |
| break; |
| case 'e': |
| ++op->do_enumerate; |
| break; |
| case 'E': |
| ++op->do_enum_vendor; |
| break; |
| case 'h': |
| case 'H': |
| ++op->do_hex; |
| break; |
| case 'l': |
| ++op->do_list; |
| break; |
| case 'L': |
| op->do_list += 2; |
| break; |
| case 'n': |
| ++op->do_name; |
| break; |
| case 'N': |
| op->opt_new = 1; |
| return 0; |
| case 'O': |
| break; |
| case 'r': |
| op->do_pcreset = 1; |
| op->do_select = 1; |
| break; |
| case 't': |
| ++op->do_temperature; |
| break; |
| case 'T': |
| ++op->do_transport; |
| break; |
| case 'v': |
| ++op->verbose; |
| break; |
| case 'V': |
| ++op->do_version; |
| break; |
| case 'x': |
| ++op->no_inq; |
| break; |
| case 'X': |
| ++op->o_readonly; |
| break; |
| case '?': |
| ++op->do_help; |
| break; |
| case '-': |
| ++cp; |
| jmp_out = 1; |
| break; |
| default: |
| jmp_out = 1; |
| break; |
| } |
| if (jmp_out) |
| break; |
| } |
| if (plen <= 0) |
| continue; |
| if (0 == strncmp("c=", cp, 2)) { |
| num = sscanf(cp + 2, "%6x", &u); |
| if ((1 != num) || (u > 3)) { |
| pr2serr("Bad page control after '-c=' option [0..3]\n"); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->page_control = u; |
| } else if (0 == strncmp("f=", cp, 2)) { |
| n = sg_get_num(cp + 2); |
| if ((n < 0) || (n > 0xffff)) { |
| pr2serr("Bad argument after '-f=' option\n"); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->filter = n; |
| ++op->filter_given; |
| } else if (0 == strncmp("i=", cp, 2)) |
| op->in_fn = cp + 2; |
| else if (0 == strncmp("m=", cp, 2)) { |
| num = sscanf(cp + 2, "%8d", &n); |
| if ((1 != num) || (n < 0) || (n > MX_ALLOC_LEN)) { |
| pr2serr("Bad maximum response length after '-m=' " |
| "option\n"); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->maxlen = n; |
| } else if (0 == strncmp("p=", cp, 2)) { |
| const char * ccp = cp + 2; |
| char * xp; |
| const struct log_elem * lep; |
| char b[80]; |
| |
| if (isalpha(ccp[0])) { |
| if (strlen(ccp) >= (sizeof(b) - 1)) { |
| pr2serr("argument to '-p=' is too long\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| strcpy(b, ccp); |
| xp = (char *)strchr(b, ','); |
| if (xp) |
| *xp = '\0'; |
| lep = acron_search(b); |
| if (NULL == lep) { |
| pr2serr("bad argument to '--page=' no acronyn match " |
| "to '%s'\n", b); |
| pr2serr(" Try using '-e' or'-ee' to see available " |
| "acronyns\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->lep = lep; |
| op->pg_code = lep->pg_code; |
| if (xp) { |
| n = sg_get_num_nomult(xp + 1); |
| if ((n < 0) || (n > 255)) { |
| pr2serr("Bad second value in argument to " |
| "'--page='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->subpg_code = n; |
| } else |
| op->subpg_code = lep->subpg_code; |
| } else { |
| /* numeric arg: either 'pg_num' or 'pg_num,subpg_num' */ |
| if (NULL == strchr(cp + 2, ',')) { |
| num = sscanf(cp + 2, "%6x", &u); |
| if ((1 != num) || (u > 63)) { |
| pr2serr("Bad page code value after '-p=' " |
| "option\n"); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->pg_code = u; |
| } else if (2 == sscanf(cp + 2, "%4x,%4x", &u, &uu)) { |
| if (uu > 255) { |
| pr2serr("Bad sub page code value after '-p=' " |
| "option\n"); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->pg_code = u; |
| op->subpg_code = uu; |
| } else { |
| pr2serr("Bad page code, subpage code sequence after " |
| "'-p=' option\n"); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| } else if (0 == strncmp("paramp=", cp, 7)) { |
| num = sscanf(cp + 7, "%8x", &u); |
| if ((1 != num) || (u > 0xffff)) { |
| pr2serr("Bad parameter pointer after '-paramp=' " |
| "option\n"); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->paramp = u; |
| } else if (0 == strncmp("pcb", cp, 3)) |
| op->do_pcb = 1; |
| else if (0 == strncmp("ppc", cp, 3)) |
| op->do_ppc = 1; |
| else if (0 == strncmp("select", cp, 6)) |
| op->do_select = 1; |
| else if (0 == strncmp("sp", cp, 2)) |
| op->do_sp = 1; |
| else if (0 == strncmp("old", cp, 3)) |
| ; |
| else if (jmp_out) { |
| pr2serr("Unrecognized option: %s\n", cp); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } else if (0 == op->device_name) |
| op->device_name = cp; |
| else { |
| pr2serr("too many arguments, got: %s, not expecting: %s\n", |
| op->device_name, cp); |
| usage_old(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| return 0; |
| } |
| |
| /* Process command line options. First check using new option format unless |
| * the SG3_UTILS_OLD_OPTS environment variable is defined which causes the |
| * old option format to be checked first. Both new and old format can be |
| * countermanded by a '-O' and '-N' options respectively. As soon as either |
| * of these options is detected (when processing the other format), processing |
| * stops and is restarted using the other format. Clear? */ |
| static int |
| process_cl(struct opts_t * op, int argc, char * argv[]) |
| { |
| int res; |
| char * cp; |
| |
| cp = getenv("SG3_UTILS_OLD_OPTS"); |
| if (cp) { |
| op->opt_new = 0; |
| res = process_cl_old(op, argc, argv); |
| if ((0 == res) && op->opt_new) |
| res = process_cl_new(op, argc, argv); |
| } else { |
| op->opt_new = 1; |
| res = process_cl_new(op, argc, argv); |
| if ((0 == res) && (0 == op->opt_new)) |
| res = process_cl_old(op, argc, argv); |
| } |
| return res; |
| } |
| |
| static void |
| dStrRaw(const char* str, int len) |
| { |
| int k; |
| |
| for (k = 0 ; k < len; ++k) |
| printf("%c", str[k]); |
| } |
| |
| /* Assumes an integral numbers of bytes pointed to by 'xp' of length |
| * 'num_bytes' given. [So the number of bits modulo 8 must be zero.] |
| * Returns true if all bytes are 0xff (which is the same as all bits |
| * being set), else returns false. */ |
| static bool |
| all_bits_set(const uint8_t * xp, int num_bytes) |
| { |
| for ( ; num_bytes > 0; ++xp, --num_bytes) { |
| if (0xff != *xp) |
| return false; |
| } |
| return true; |
| } |
| |
| static char * |
| num_or_unknown(const uint8_t * xp, int num_bytes, bool in_hex, char * b, |
| int blen) |
| { |
| if (all_bits_set(xp, num_bytes)) |
| snprintf(b, blen, "unknown"); |
| else { |
| uint64_t num = sg_get_unaligned_be(num_bytes, xp); |
| |
| if (in_hex) |
| snprintf(b, blen, "0x%" PRIx64, num); |
| else |
| snprintf(b, blen, "%" PRIu64, num); |
| } |
| return b; |
| } |
| |
| /* Read ASCII hex bytes or binary from fname (a file named '-' taken as |
| * stdin). If reading ASCII hex then there should be either one entry per |
| * line or a comma, space or tab separated list of bytes. If no_space is |
| * set then a string of ACSII hex digits is expected, 2 per byte. Everything |
| * from and including a '#' on a line is ignored. Returns 0 if ok, or 1 if |
| * error. */ |
| static int |
| f2hex_arr(const char * fname, int as_binary, int no_space, |
| uint8_t * mp_arr, int * mp_arr_len, int max_arr_len) |
| { |
| int fn_len, in_len, k, j, m, split_line, fd, has_stdin; |
| unsigned int h; |
| const char * lcp; |
| FILE * fp; |
| char line[512]; |
| char carry_over[4]; |
| int off = 0; |
| |
| if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) |
| return 1; |
| fn_len = strlen(fname); |
| if (0 == fn_len) |
| return 1; |
| has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */ |
| if (as_binary) { |
| if (has_stdin) { |
| fd = STDIN_FILENO; |
| if (sg_set_binary_mode(STDIN_FILENO) < 0) |
| perror("sg_set_binary_mode"); |
| } else { |
| fd = open(fname, O_RDONLY); |
| if (fd < 0) { |
| pr2serr("unable to open binary file %s: %s\n", fname, |
| safe_strerror(errno)); |
| return 1; |
| } else if (sg_set_binary_mode(fd) < 0) |
| perror("sg_set_binary_mode"); |
| } |
| k = read(fd, mp_arr, max_arr_len); |
| if (k <= 0) { |
| if (0 == k) |
| pr2serr("read 0 bytes from binary file %s\n", fname); |
| else |
| pr2serr("read from binary file %s: %s\n", fname, |
| safe_strerror(errno)); |
| if (! has_stdin) |
| close(fd); |
| return 1; |
| } |
| *mp_arr_len = k; |
| if (! has_stdin) |
| close(fd); |
| return 0; |
| } else { /* So read the file as ASCII hex */ |
| if (has_stdin) |
| fp = stdin; |
| else { |
| fp = fopen(fname, "r"); |
| if (NULL == fp) { |
| pr2serr("Unable to open %s for reading\n", fname); |
| return 1; |
| } |
| } |
| } |
| |
| carry_over[0] = 0; |
| for (j = 0; j < 512; ++j) { |
| if (NULL == fgets(line, sizeof(line), fp)) |
| break; |
| in_len = strlen(line); |
| if (in_len > 0) { |
| if ('\n' == line[in_len - 1]) { |
| --in_len; |
| line[in_len] = '\0'; |
| split_line = 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, "%4x", &h)) |
| mp_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 bad; |
| } |
| 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 ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) { |
| pr2serr("%s: syntax error at line %d, pos %d\n", __func__, |
| j + 1, m + k + 1); |
| goto bad; |
| } |
| if (no_space) { |
| for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1)); |
| ++k, lcp += 2) { |
| if (1 != sscanf(lcp, "%2x", &h)) { |
| pr2serr("%s: bad hex number in line %d, pos %d\n", |
| __func__, j + 1, (int)(lcp - line + 1)); |
| goto bad; |
| } |
| if ((off + k) >= max_arr_len) { |
| pr2serr("%s: array length exceeded\n", __func__); |
| goto bad; |
| } |
| mp_arr[off + k] = h; |
| } |
| if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1)))) |
| carry_over[0] = *lcp; |
| off += k; |
| } else { |
| for (k = 0; k < 1024; ++k) { |
| if (1 == sscanf(lcp, "%4x", &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 bad; |
| } |
| if (split_line && (1 == strlen(lcp))) { |
| /* single trailing hex digit might be a split pair */ |
| carry_over[0] = *lcp; |
| } |
| if ((off + k) >= max_arr_len) { |
| pr2serr("%s: array length exceeded\n", __func__); |
| goto bad; |
| } |
| mp_arr[off + k] = h; |
| lcp = strpbrk(lcp, " ,\t"); |
| if (NULL == lcp) |
| break; |
| lcp += strspn(lcp, " ,\t"); |
| if ('\0' == *lcp) |
| break; |
| } else { |
| if (('#' == *lcp) || ('\r' == *lcp)) { |
| --k; |
| break; |
| } |
| pr2serr("%s: error in line %d, at pos %d\n", __func__, |
| j + 1, (int)(lcp - line + 1)); |
| goto bad; |
| } |
| } |
| off += (k + 1); |
| } |
| } |
| *mp_arr_len = off; |
| if (stdin != fp) |
| fclose(fp); |
| return 0; |
| bad: |
| if (stdin != fp) |
| fclose(fp); |
| return 1; |
| } |
| |
| |
| /* Call LOG SENSE twice: the first time ask for 4 byte response to determine |
| actual length of response; then a second time requesting the |
| min(actual_len, mx_resp_len) bytes. If the calculated length for the |
| second fetch is odd then it is incremented (perhaps should be made modulo |
| 4 in the future for SAS). Returns 0 if ok, SG_LIB_CAT_INVALID_OP for |
| log_sense not supported, SG_LIB_CAT_ILLEGAL_REQ for bad field in log sense |
| command, SG_LIB_CAT_NOT_READY, SG_LIB_CAT_UNIT_ATTENTION, |
| SG_LIB_CAT_ABORTED_COMMAND and -1 for other errors. */ |
| static int |
| do_logs(int sg_fd, uint8_t * resp, int mx_resp_len, |
| const struct opts_t * op) |
| { |
| int actual_len, res, vb; |
| |
| #ifdef SG_LIB_WIN32 |
| #ifdef SG_LIB_WIN32_DIRECT |
| if (0 == win32_spt_init_state) { |
| if (win32_spt_curr_state) { |
| if (mx_resp_len < 16384) { |
| scsi_pt_win32_direct(0); |
| win32_spt_curr_state = 0; |
| } |
| } else { |
| if (mx_resp_len >= 16384) { |
| scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT direct */); |
| win32_spt_curr_state = 1; |
| } |
| } |
| } |
| #endif |
| #endif |
| memset(resp, 0, mx_resp_len); |
| vb = op->verbose; |
| if (op->maxlen > 1) |
| actual_len = mx_resp_len; |
| else { |
| if ((res = sg_ll_log_sense(sg_fd, op->do_ppc, op->do_sp, |
| op->page_control, op->pg_code, |
| op->subpg_code, op->paramp, |
| resp, LOG_SENSE_PROBE_ALLOC_LEN, |
| 1 /* noisy */, vb))) |
| return res; |
| actual_len = sg_get_unaligned_be16(resp + 2) + 4; |
| if ((0 == op->do_raw) && (vb > 1)) { |
| pr2serr(" Log sense (find length) response:\n"); |
| dStrHexErr((const char *)resp, LOG_SENSE_PROBE_ALLOC_LEN, 1); |
| pr2serr(" hence calculated response length=%d\n", actual_len); |
| } |
| if (op->pg_code != (0x3f & resp[0])) { |
| if (vb) |
| pr2serr("Page code does not appear in first byte of " |
| "response so it's suspect\n"); |
| if (actual_len > 0x40) { |
| actual_len = 0x40; |
| if (vb) |
| pr2serr("Trim response length to 64 bytes due to " |
| "suspect response format\n"); |
| } |
| } |
| /* Some HBAs don't like odd transfer lengths */ |
| if (actual_len % 2) |
| actual_len += 1; |
| if (actual_len > mx_resp_len) |
| actual_len = mx_resp_len; |
| } |
| if ((res = sg_ll_log_sense(sg_fd, op->do_ppc, op->do_sp, |
| op->page_control, op->pg_code, |
| op->subpg_code, op->paramp, |
| resp, actual_len, 1 /* noisy */, vb))) |
| return res; |
| if ((0 == op->do_raw) && (vb > 1)) { |
| pr2serr(" Log sense response:\n"); |
| dStrHexErr((const char *)resp, actual_len, 1); |
| } |
| return 0; |
| } |
| |
| /* DS made obsolete in spc4r03; TMC and ETC made obsolete in spc5r03. */ |
| static void |
| get_pcb_str(int pcb, char * outp, int maxoutlen) |
| { |
| char buff[PCB_STR_LEN]; |
| int n; |
| |
| n = sprintf(buff, "du=%d [ds=%d] tsd=%d [etc=%d] ", ((pcb & 0x80) ? 1 : 0), |
| ((pcb & 0x40) ? 1 : 0), ((pcb & 0x20) ? 1 : 0), |
| ((pcb & 0x10) ? 1 : 0)); |
| if (pcb & 0x10) |
| n += sprintf(buff + n, "[tmc=%d] ", ((pcb & 0xc) >> 2)); |
| #if 1 |
| n += sprintf(buff + n, "format+linking=%d [0x%.2x]", pcb & 3, |
| pcb); |
| #else |
| if (pcb & 0x1) |
| n += sprintf(buff + n, "lbin=%d ", ((pcb & 0x2) >> 1)); |
| n += sprintf(buff + n, "lp=%d [0x%.2x]", pcb & 0x1, pcb); |
| #endif |
| if (outp && (n < maxoutlen)) { |
| memcpy(outp, buff, n); |
| outp[n] = '\0'; |
| } else if (outp && (maxoutlen > 0)) |
| outp[0] = '\0'; |
| } |
| |
| /* SUPP_PAGES_LPAGE [0x0,0x0] */ |
| static bool |
| show_supported_pgs_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, k, pg_code; |
| const uint8_t * ucp; |
| const struct log_elem * lep; |
| char b[64]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Supported log pages [0x0]:\n"); /* introduced: SPC-2 */ |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| for (k = 0; k < num; ++k) { |
| pg_code = ucp[k]; |
| snprintf(b, sizeof(b) - 1, " 0x%02x ", pg_code); |
| lep = pg_subpg_pdt_search(pg_code, 0, op->dev_pdt); |
| if (lep) { |
| if (op->do_brief > 1) |
| printf(" %s\n", lep->name); |
| else if (op->do_brief) |
| printf("%s%s\n", b, lep->name); |
| else |
| printf("%s%s [%s]\n", b, lep->name, lep->acron); |
| } else |
| printf("%s\n", b); |
| } |
| return true; |
| } |
| |
| /* SUPP_PAGES_LPAGE,SUPP_SPGS_SUBPG [0x0,0xff] or all subpages of a given |
| * page code: [<pg_code>,0xff] */ |
| static bool |
| show_supported_pgs_sub_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, k, pg_code, subpg_code; |
| const uint8_t * ucp; |
| const struct log_elem * lep; |
| char b[64]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| if (op->pg_code > 0) |
| printf("Supported subpages [0x%x, 0xff]:\n", op->pg_code); |
| else |
| printf("Supported log pages and subpages [0x0, 0xff]:\n"); |
| } |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| for (k = 0; k < num; k += 2) { |
| pg_code = ucp[k]; |
| subpg_code = ucp[k + 1]; |
| if (NOT_SPG_SUBPG == subpg_code) |
| snprintf(b, sizeof(b) - 1, " 0x%02x ", pg_code); |
| else |
| snprintf(b, sizeof(b) - 1, " 0x%02x,0x%02x ", pg_code, |
| subpg_code); |
| lep = pg_subpg_pdt_search(pg_code, subpg_code, op->dev_pdt); |
| if (lep) { |
| if (op->do_brief > 1) |
| printf(" %s\n", lep->name); |
| else if (op->do_brief) |
| printf("%s%s\n", b, lep->name); |
| else |
| printf("%s%s [%s]\n", b, lep->name, lep->acron); |
| } else |
| printf("%s\n", b); |
| } |
| return true; |
| } |
| |
| /* BUFF_OVER_UNDER_LPAGE [0x1] introduced: SPC-2 */ |
| static bool |
| show_buffer_over_under_run_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pcb, pc; |
| uint64_t count; |
| const uint8_t * ucp; |
| const char * cp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Buffer over-run/under-run page [0x1]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| cp = NULL; |
| pl = ucp[3] + 4; |
| count = (pl > 4) ? sg_get_unaligned_be(pl - 4, ucp + 4) : 0; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0x0: |
| cp = "under-run"; |
| break; |
| case 0x1: |
| cp = "over-run"; |
| break; |
| case 0x2: |
| cp = "transport under-run"; |
| break; |
| case 0x3: |
| cp = "transport over-run"; |
| break; |
| case 0x4: |
| cp = "transfer too slow, under-run"; |
| break; |
| case 0x5: |
| cp = "transfer too slow, over-run"; |
| break; |
| case 0x20: |
| cp = "command, under-run"; |
| break; |
| case 0x21: |
| cp = "command, over-run"; |
| break; |
| case 0x22: |
| cp = "command, transport under-run"; |
| break; |
| case 0x23: |
| cp = "command, transport over-run"; |
| break; |
| case 0x24: |
| cp = "command, transfer too slow, under-run"; |
| break; |
| case 0x25: |
| cp = "command, transfer too slow, over-run"; |
| break; |
| case 0x40: |
| cp = "I_T nexus, under-run"; |
| break; |
| case 0x41: |
| cp = "I_T nexus, over-run"; |
| break; |
| case 0x42: |
| cp = "I_T nexus, transport under-run"; |
| break; |
| case 0x43: |
| cp = "I_T nexus, transport over-run"; |
| break; |
| case 0x44: |
| cp = "I_T nexus, transfer too slow, under-run"; |
| break; |
| case 0x45: |
| cp = "I_T nexus, transfer too slow, over-run"; |
| break; |
| case 0x80: |
| cp = "time, under-run"; |
| break; |
| case 0x81: |
| cp = "time, over-run"; |
| break; |
| case 0x82: |
| cp = "time, transport under-run"; |
| break; |
| case 0x83: |
| cp = "time, transport over-run"; |
| break; |
| case 0x84: |
| cp = "time, transfer too slow, under-run"; |
| break; |
| case 0x85: |
| cp = "time, transfer too slow, over-run"; |
| break; |
| default: |
| printf(" undefined parameter code [0x%x], count = %" PRIu64 "", |
| pc, count); |
| break; |
| } |
| if (cp) |
| printf(" %s = %" PRIu64 "", cp, count); |
| |
| if (op->do_pcb) { |
| pcb = ucp[2]; |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* WRITE_ERR_LPAGE; READ_ERR_LPAGE; READ_REV_ERR_LPAGE; VERIFY_ERR_LPAGE */ |
| /* [0x2, 0x3, 0x4, 0x5] introduced: SPC-3 */ |
| static bool |
| show_error_counter_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb, pg_code; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| pg_code = resp[0] & 0x3f; |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| switch(pg_code) { |
| case WRITE_ERR_LPAGE: |
| printf("Write error counter page [0x%x]\n", pg_code); |
| break; |
| case READ_ERR_LPAGE: |
| printf("Read error counter page [0x%x]\n", pg_code); |
| break; |
| case READ_REV_ERR_LPAGE: |
| printf("Read Reverse error counter page [0x%x]\n", |
| pg_code); |
| break; |
| case VERIFY_ERR_LPAGE: |
| printf("Verify error counter page [0x%x]\n", pg_code); |
| break; |
| default: |
| pr2serr("expecting error counter page, got page = 0x%x\n", |
| resp[0]); |
| return false; |
| } |
| } |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0: printf(" Errors corrected without substantial delay"); break; |
| case 1: printf(" Errors corrected with possible delays"); break; |
| case 2: printf(" Total rewrites or rereads"); break; |
| case 3: printf(" Total errors corrected"); break; |
| case 4: printf(" Total times correction algorithm processed"); break; |
| case 5: printf(" Total bytes processed"); break; |
| case 6: printf(" Total uncorrected errors"); break; |
| case 0x8009: printf(" Track following errors [Hitachi]"); break; |
| case 0x8015: printf(" Positioning errors [Hitachi]"); break; |
| default: printf(" Reserved or vendor specific [0x%x]", pc); break; |
| } |
| printf(" = %" PRIu64 "", sg_get_unaligned_be(pl - 4, ucp + 4)); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* NON_MEDIUM_LPAGE [0x6] introduced: SPC-2 */ |
| static bool |
| show_non_medium_error_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Non-medium error page [0x6]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0: |
| printf(" Non-medium error count"); |
| break; |
| default: |
| if (pc <= 0x7fff) |
| printf(" Reserved [0x%x]", pc); |
| else |
| printf(" Vendor specific [0x%x]", pc); |
| break; |
| } |
| printf(" = %" PRIu64 "", sg_get_unaligned_be(pl - 4, ucp + 4)); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* PCT_LPAGE [0x1a] introduced: SPC-4 */ |
| static bool |
| show_power_condition_transitions_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Power condition transitions page [0x1a]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0: |
| printf(" Accumulated transitions to active"); break; |
| case 1: |
| printf(" Accumulated transitions to idle_a"); break; |
| case 2: |
| printf(" Accumulated transitions to idle_b"); break; |
| case 3: |
| printf(" Accumulated transitions to idle_c"); break; |
| case 8: |
| printf(" Accumulated transitions to standby_z"); break; |
| case 9: |
| printf(" Accumulated transitions to standby_y"); break; |
| default: |
| printf(" Reserved [0x%x]", pc); |
| } |
| printf(" = %" PRIu64 "", sg_get_unaligned_be(pl - 4, ucp + 4)); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| static char * |
| temperature_str(int8_t t, bool reporting, char * b, int blen) |
| { |
| if (-128 == t) { |
| if (reporting) |
| snprintf(b, blen, "not available"); |
| else |
| snprintf(b, blen, "no limit"); |
| } else |
| snprintf(b, blen, "%d C", t); |
| return b; |
| } |
| |
| static char * |
| humidity_str(uint8_t h, bool reporting, char * b, int blen) |
| { |
| if (255 == h) { |
| if (reporting) |
| snprintf(b, blen, "not available"); |
| else |
| snprintf(b, blen, "no limit"); |
| } else if (h <= 100) |
| snprintf(b, blen, "%u %%", h); |
| else |
| snprintf(b, blen, "reserved value [%u]", h); |
| return b; |
| } |
| |
| /* ENV_REPORTING_SUBPG [0xd,0x1] introduced: SPC-5 (rev 02) */ |
| static bool |
| show_environmental_reporting_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb, blen; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| char b[32]; |
| |
| blen = sizeof(b); |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Environmental reporting page [0xd,0x1]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| if (pc < 0x100) { |
| if (pl < 12) { |
| printf(" <<expect parameter 0x%x to be at least 12 bytes " |
| "long, got %d, skip>>\n", pc, pl); |
| goto skip; |
| } |
| printf(" Temperature: %s\n", |
| temperature_str(ucp[5], true, b, blen)); |
| printf(" Lifetime maximum temperature: %s\n", |
| temperature_str(ucp[6], true, b, blen)); |
| printf(" Lifetime minimum temperature: %s\n", |
| temperature_str(ucp[7], true, b, blen)); |
| printf(" Maximum temperature since power on: %s\n", |
| temperature_str(ucp[8], true, b, blen)); |
| printf(" Minimum temperature since power on: %s\n", |
| temperature_str(ucp[9], true, b, blen)); |
| } else if (pc < 0x200) { |
| printf(" Relative humidity: %s\n", |
| humidity_str(ucp[5], true, b, blen)); |
| printf(" Lifetime maximum relative humidity: %s\n", |
| humidity_str(ucp[6], true, b, blen)); |
| printf(" Lifetime minimum relative humidity: %s\n", |
| humidity_str(ucp[7], true, b, blen)); |
| printf(" Maximum relative humidity since power on: %s\n", |
| humidity_str(ucp[8], true, b, blen)); |
| printf(" Minimum relative humidity since power on: %s\n", |
| humidity_str(ucp[9], true, b, blen)); |
| } else |
| printf(" <<unexpect parameter code 0x%x\n", pc); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* ENV_LIMITS_SUBPG [0xd,0x2] introduced: SPC-5 (rev 02) */ |
| static bool |
| show_environmental_limits_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb, blen; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| char b[32]; |
| |
| blen = sizeof(b); |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Environmental limits page [0xd,0x2]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| if (pc < 0x100) { |
| if (pl < 12) { |
| printf(" <<expect parameter 0x%x to be at least 12 bytes " |
| "long, got %d, skip>>\n", pc, pl); |
| goto skip; |
| } |
| printf(" High critical temperature limit trigger: %s\n", |
| temperature_str(ucp[4], false, b, blen)); |
| printf(" High critical temperature limit reset: %s\n", |
| temperature_str(ucp[5], false, b, blen)); |
| printf(" Low critical temperature limit reset: %s\n", |
| temperature_str(ucp[6], false, b, blen)); |
| printf(" Low critical temperature limit trigger: %s\n", |
| temperature_str(ucp[7], false, b, blen)); |
| printf(" High operating temperature limit trigger: %s\n", |
| temperature_str(ucp[8], false, b, blen)); |
| printf(" High operating temperature limit reset: %s\n", |
| temperature_str(ucp[9], false, b, blen)); |
| printf(" Low operating temperature limit reset: %s\n", |
| temperature_str(ucp[10], false, b, blen)); |
| printf(" Low operating temperature limit trigger: %s\n", |
| temperature_str(ucp[11], false, b, blen)); |
| } else if (pc < 0x200) { |
| printf(" High critical relative humidity limit trigger: %s\n", |
| humidity_str(ucp[4], false, b, blen)); |
| printf(" High critical relative humidity limit reset: %s\n", |
| humidity_str(ucp[5], false, b, blen)); |
| printf(" Low critical relative humidity limit reset: %s\n", |
| humidity_str(ucp[6], false, b, blen)); |
| printf(" Low critical relative humidity limit trigger: %s\n", |
| humidity_str(ucp[7], false, b, blen)); |
| printf(" High operating relative humidity limit trigger: %s\n", |
| humidity_str(ucp[8], false, b, blen)); |
| printf(" High operating relative humidity limit reset: %s\n", |
| humidity_str(ucp[9], false, b, blen)); |
| printf(" Low operating relative humidity limit reset: %s\n", |
| humidity_str(ucp[10], false, b, blen)); |
| printf(" Low operating relative humidity limit trigger: %s\n", |
| humidity_str(ucp[11], false, b, blen)); |
| } else |
| printf(" <<unexpect parameter code 0x%x\n", pc); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* Tape usage: Vendor specific (LTO-5 and LTO-6): 0x30 */ |
| static bool |
| show_tape_usage_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int k, num, extra, pc, pcb; |
| unsigned int n; |
| uint64_t ull; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| pr2serr("badly formed tape usage page\n"); |
| return false; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Tape usage page (LTO-5 and LTO-6 specific) [0x30]\n"); |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| extra = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| ull = n = 0; |
| switch (ucp[3]) { |
| case 2: |
| n = sg_get_unaligned_be16(ucp + 4); |
| break; |
| case 4: |
| n = sg_get_unaligned_be32(ucp + 4); |
| break; |
| case 8: |
| ull = sg_get_unaligned_be64(ucp + 4); |
| break; |
| } |
| switch (pc) { |
| case 0x01: |
| if (extra == 8) |
| printf(" Thread count: %u", n); |
| break; |
| case 0x02: |
| if (extra == 12) |
| printf(" Total data sets written: %" PRIu64, ull); |
| break; |
| case 0x03: |
| if (extra == 8) |
| printf(" Total write retries: %u", n); |
| break; |
| case 0x04: |
| if (extra == 6) |
| printf(" Total unrecovered write errors: %u", n); |
| break; |
| case 0x05: |
| if (extra == 6) |
| printf(" Total suspended writes: %u", n); |
| break; |
| case 0x06: |
| if (extra == 6) |
| printf(" Total fatal suspended writes: %u", n); |
| break; |
| case 0x07: |
| if (extra == 12) |
| printf(" Total data sets read: %" PRIu64, ull); |
| break; |
| case 0x08: |
| if (extra == 8) |
| printf(" Total read retries: %u", n); |
| break; |
| case 0x09: |
| if (extra == 6) |
| printf(" Total unrecovered read errors: %u", n); |
| break; |
| case 0x0a: |
| if (extra == 6) |
| printf(" Total suspended reads: %u", n); |
| break; |
| case 0x0b: |
| if (extra == 6) |
| printf(" Total fatal suspended reads: %u", n); |
| break; |
| default: |
| printf(" unknown parameter code = 0x%x, contents in " |
| "hex:\n", pc); |
| dStrHex((const char *)ucp, extra, 1); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* Tape capacity: vendor specific (LTO-5 and LTO-6 ?): 0x31 */ |
| static bool |
| show_tape_capacity_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int k, num, extra, pc, pcb; |
| unsigned int n; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| pr2serr("badly formed tape capacity page\n"); |
| return false; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Tape capacity page (LTO-5 and LTO-6 specific) [0x31]\n"); |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| extra = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| if (extra != 8) |
| continue; |
| n = sg_get_unaligned_be32(ucp + 4); |
| switch (pc) { |
| case 0x01: |
| printf(" Main partition remaining capacity (in MiB): %u", n); |
| break; |
| case 0x02: |
| printf(" Alternate partition remaining capacity (in MiB): %u", n); |
| break; |
| case 0x03: |
| printf(" Main partition maximum capacity (in MiB): %u", n); |
| break; |
| case 0x04: |
| printf(" Alternate partition maximum capacity (in MiB): %u", n); |
| break; |
| default: |
| printf(" unknown parameter code = 0x%x, contents in " |
| "hex:\n", pc); |
| dStrHex((const char *)ucp, extra, 1); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* Data compression: originally vendor specific 0x32 (LTO-5), then |
| * ssc-4 standardizes it at 0x1b */ |
| static bool |
| show_data_compression_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int k, j, pl, num, extra, pc, pcb, pg_code; |
| uint64_t n; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| pg_code = resp[0] & 0x3f; |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| pr2serr("badly formed data compression page\n"); |
| return false; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| if (0x1b == pg_code) |
| printf("Data compression page (ssc-4) [0x1b]\n"); |
| else |
| printf("Data compression page (LTO-5 specific) [0x%x]\n", |
| pg_code); |
| } |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3]; |
| extra = pl + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| if ((0 == pl) || (pl > 8)) { |
| printf("badly formed data compression log parameter\n"); |
| printf(" parameter code = 0x%x, contents in hex:\n", pc); |
| dStrHex((const char *)ucp, extra, 1); |
| goto skip_para; |
| } |
| /* variable length integer, max length 8 bytes */ |
| for (j = 0, n = 0; j < pl; ++j) { |
| if (j > 0) |
| n <<= 8; |
| n |= ucp[4 + j]; |
| } |
| switch (pc) { |
| case 0x00: |
| printf(" Read compression ratio x100: %" PRIu64 , n); |
| break; |
| case 0x01: |
| printf(" Write compression ratio x100: %" PRIu64 , n); |
| break; |
| case 0x02: |
| printf(" Megabytes transferred to server: %" PRIu64 , n); |
| break; |
| case 0x03: |
| printf(" Bytes transferred to server: %" PRIu64 , n); |
| break; |
| case 0x04: |
| printf(" Megabytes read from tape: %" PRIu64 , n); |
| break; |
| case 0x05: |
| printf(" Bytes read from tape: %" PRIu64 , n); |
| break; |
| case 0x06: |
| printf(" Megabytes transferred from server: %" PRIu64 , n); |
| break; |
| case 0x07: |
| printf(" Bytes transferred from server: %" PRIu64 , n); |
| break; |
| case 0x08: |
| printf(" Megabytes written to tape: %" PRIu64 , n); |
| break; |
| case 0x09: |
| printf(" Bytes written to tape: %" PRIu64 , n); |
| break; |
| case 0x100: |
| printf(" Data compression enabled: 0x%" PRIx64, n); |
| break; |
| default: |
| printf(" unknown parameter code = 0x%x, contents in " |
| "hex:\n", pc); |
| dStrHex((const char *)ucp, extra, 1); |
| break; |
| } |
| skip_para: |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* LAST_N_ERR_LPAGE [0x7] introduced: SPC-2 */ |
| static bool |
| show_last_n_error_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int k, num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| printf("No error events logged\n"); |
| return true; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Last n error events page [0x7]\n"); |
| for (k = num; k > 0; k -= pl, ucp += pl) { |
| if (k < 3) { |
| printf("short Last n error events page\n"); |
| return false; |
| } |
| pl = ucp[3] + 4; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| printf(" Error event %d:\n", pc); |
| if (pl > 4) { |
| if ((pcb & 0x1) && (pcb & 0x2)) { |
| printf(" [binary]:\n"); |
| dStrHex((const char *)ucp + 4, pl - 4, 1); |
| } else if (pcb & 0x1) |
| printf(" %.*s\n", pl - 4, (const char *)(ucp + 4)); |
| else { |
| printf(" [data counter?? (LP bit should be set)]:\n"); |
| dStrHex((const char *)ucp + 4, pl - 4, 1); |
| } |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* LAST_N_DEFERRED_LPAGE [0xb] introduced: SPC-2 */ |
| static bool |
| show_last_n_deferred_error_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int k, num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| printf("No deferred errors logged\n"); |
| return true; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Last n deferred errors page [0xb]\n"); |
| for (k = num; k > 0; k -= pl, ucp += pl) { |
| if (k < 3) { |
| printf("short Last n deferred errors page\n"); |
| return true; |
| } |
| pl = ucp[3] + 4; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| printf(" Deferred error %d:\n", pc); |
| dStrHex((const char *)ucp + 4, pl - 4, 1); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| static const char * self_test_code[] = { |
| "default", "background short", "background extended", "reserved", |
| "aborted background", "foreground short", "foreground extended", |
| "reserved"}; |
| |
| static const char * self_test_result[] = { |
| "completed without error", |
| "aborted by SEND DIAGNOSTIC", |
| "aborted other than by SEND DIAGNOSTIC", |
| "unknown error, unable to complete", |
| "self test completed with failure in test segment (which one unknown)", |
| "first segment in self test failed", |
| "second segment in self test failed", |
| "another segment in self test failed", |
| "reserved", "reserved", "reserved", "reserved", "reserved", "reserved", |
| "reserved", |
| "self test in progress"}; |
| |
| /* SELF_TEST_LPAGE [0x10] introduced: SPC-3 */ |
| static bool |
| show_self_test_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int k, num, n, res, pc, pl, pcb; |
| unsigned int v; |
| const uint8_t * ucp; |
| uint64_t ull; |
| char pcb_str[PCB_STR_LEN]; |
| char b[80]; |
| |
| num = len - 4; |
| if (num < 0x190) { |
| pr2serr("short self-test results page [length 0x%x rather than " |
| "0x190 bytes]\n", num); |
| return true; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Self-test results page [0x10]\n"); |
| for (k = 0, ucp = resp + 4; k < 20; ++k, ucp += 20 ) { |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| n = sg_get_unaligned_be16(ucp + 6); |
| if ((0 == n) && (0 == ucp[4])) |
| break; |
| printf(" Parameter code = %d, accumulated power-on hours = %d\n", |
| pc, n); |
| printf(" self-test code: %s [%d]\n", |
| self_test_code[(ucp[4] >> 5) & 0x7], (ucp[4] >> 5) & 0x7); |
| res = ucp[4] & 0xf; |
| printf(" self-test result: %s [%d]\n", self_test_result[res], res); |
| if (ucp[5]) |
| printf(" self-test number = %d\n", (int)ucp[5]); |
| if (! all_bits_set(ucp + 8, 8)) { |
| ull = sg_get_unaligned_be64(ucp + 8); |
| if ((res > 0) && ( res < 0xf)) |
| printf(" address of first error = 0x%" PRIx64 "\n", ull); |
| } |
| v = ucp[16] & 0xf; |
| if (v) { |
| printf(" sense key = 0x%x [%s] , asc = 0x%x, ascq = 0x%x", |
| v, sg_get_sense_key_str(v, sizeof(b), b), ucp[17], |
| ucp[18]); |
| if (ucp[17] || ucp[18]) |
| printf(" [%s]\n", sg_get_asc_ascq_str(ucp[17], ucp[18], |
| sizeof(b), b)); |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* TEMPERATURE_LPAGE [0xd] introduced: SPC-3 */ |
| static bool |
| show_temperature_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int k, num, extra, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| pr2serr("badly formed Temperature page\n"); |
| return false; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| if (! op->do_temperature) |
| printf("Temperature page [0xd]\n"); |
| } |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| if (k < 3) { |
| pr2serr("short Temperature page\n"); |
| return true; |
| } |
| extra = ucp[3] + 4; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0: |
| if ((extra > 5) && (k > 5)) { |
| if (ucp[5] < 0xff) |
| printf(" Current temperature = %d C", ucp[5]); |
| else |
| printf(" Current temperature = <not available>"); |
| } |
| break; |
| case 1: |
| if ((extra > 5) && (k > 5)) { |
| if (ucp[5] < 0xff) |
| printf(" Reference temperature = %d C", ucp[5]); |
| else |
| printf(" Reference temperature = <not available>"); |
| } |
| break; |
| default: |
| if (! op->do_temperature) { |
| printf(" unknown parameter code = 0x%x, contents in " |
| "hex:\n", pc); |
| dStrHex((const char *)ucp, extra, 1); |
| } else |
| continue; |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* START_STOP_LPAGE [0xe] introduced: SPC-3 */ |
| static bool |
| show_start_stop_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int k, num, extra, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| pr2serr("badly formed Start-stop cycle counter page\n"); |
| return false; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Start-stop cycle counter page [0xe]\n"); |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| if (k < 3) { |
| pr2serr("short Start-stop cycle counter page\n"); |
| return true; |
| } |
| extra = ucp[3] + 4; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 1: |
| if (10 == extra) |
| printf(" Date of manufacture, year: %.4s, week: %.2s", |
| &ucp[4], &ucp[8]); |
| else if (op->verbose) { |
| pr2serr(" Date of manufacture parameter length strange: " |
| "%d\n", extra - 4); |
| dStrHexErr((const char *)ucp, extra, 1); |
| } |
| break; |
| case 2: |
| if (10 == extra) |
| printf(" Accounting date, year: %.4s, week: %.2s", |
| &ucp[4], &ucp[8]); |
| else if (op->verbose) { |
| pr2serr(" Accounting date parameter length strange: %d\n", |
| extra - 4); |
| dStrHexErr((const char *)ucp, extra, 1); |
| } |
| break; |
| case 3: |
| if (extra > 7) { |
| if (all_bits_set(ucp + 4, 4)) |
| printf(" Specified cycle count over device lifetime " |
| "= -1"); |
| else |
| printf(" Specified cycle count over device lifetime " |
| "= %u", sg_get_unaligned_be32(ucp + 4)); |
| } |
| break; |
| case 4: |
| if (extra > 7) { |
| if (all_bits_set(ucp + 4, 4)) |
| printf(" Accumulated start-stop cycles = -1"); |
| else |
| printf(" Accumulated start-stop cycles = %u", |
| sg_get_unaligned_be32(ucp + 4)); |
| } |
| break; |
| case 5: |
| if (extra > 7) { |
| if (all_bits_set(ucp + 4, 4)) |
| printf(" Specified load-unload count over device " |
| "lifetime = -1"); |
| else |
| printf(" Specified load-unload count over device " |
| "lifetime = %u", sg_get_unaligned_be32(ucp + 4)); |
| } |
| break; |
| case 6: |
| if (extra > 7) { |
| if (all_bits_set(ucp + 4, 4)) |
| printf(" Accumulated load-unload cycles = -1"); |
| else |
| printf(" Accumulated load-unload cycles = %u", |
| sg_get_unaligned_be32(ucp + 4)); |
| } |
| break; |
| default: |
| printf(" unknown parameter code = 0x%x, contents in " |
| "hex:\n", pc); |
| dStrHex((const char *)ucp, extra, 1); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* APP_CLIENT_LPAGE [0xf] introduced: SPC-3 */ |
| static bool |
| show_app_client_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int k, num, extra, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| pr2serr("badly formed Application Client page\n"); |
| return false; |
| } |
| if (op->verbose || ((op->do_raw == 0) && (op->do_hex == 0))) |
| printf("Application client page [0xf]\n"); |
| if (0 == op->filter_given) { |
| if ((len > 128) && (0 == op->do_hex)) { |
| dStrHex((const char *)resp, 64, 1); |
| printf(" ..... [truncated after 64 of %d bytes (use '-H' to " |
| "see the rest)]\n", len); |
| } |
| else |
| dStrHex((const char *)resp, len, 1); |
| return true; |
| } |
| /* only here if filter_given set */ |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| if (k < 3) { |
| pr2serr("short Application client page\n"); |
| return true; |
| } |
| extra = ucp[3] + 4; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter != pc) |
| continue; |
| if (op->do_raw) |
| dStrRaw((const char *)ucp, extra); |
| else if (0 == op->do_hex) |
| dStrHex((const char *)ucp, extra, 0); |
| else if (1 == op->do_hex) |
| dStrHex((const char *)ucp, extra, 1); |
| else |
| dStrHex((const char *)ucp, extra, -1); |
| |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| break; |
| } |
| return true; |
| } |
| |
| /* IE_LPAGE [0x2f] introduced: SPC-3 */ |
| static bool |
| show_ie_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int k, num, extra, pc, pcb, full; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| char b[256]; |
| |
| full = ! op->do_temperature; |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| if (num < 4) { |
| pr2serr("badly formed Informational Exceptions page\n"); |
| return false; |
| } |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| if (full) |
| printf("Informational Exceptions page [0x2f]\n"); |
| } |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| if (k < 3) { |
| printf("short Informational Exceptions page\n"); |
| return false; |
| } |
| extra = ucp[3] + 4; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0: |
| if (extra > 5) { |
| if (full) { |
| printf(" IE asc = 0x%x, ascq = 0x%x", ucp[4], ucp[5]); |
| if (ucp[4] || ucp[5]) |
| if(sg_get_asc_ascq_str(ucp[4], ucp[5], sizeof(b), b)) |
| printf("\n [%s]", b); |
| } |
| if (extra > 6) { |
| if (ucp[6] < 0xff) |
| printf("\n Current temperature = %d C", ucp[6]); |
| else |
| printf("\n Current temperature = <not available>"); |
| if (extra > 7) { |
| if (ucp[7] < 0xff) |
| printf("\n Threshold temperature = %d C [IBM " |
| "extension]", ucp[7]); |
| else |
| printf("\n Threshold temperature = <not " |
| "available>"); |
| } |
| } |
| } |
| break; |
| default: |
| if (full) { |
| printf(" parameter code = 0x%x, contents in hex:\n", pc); |
| dStrHex((const char *)ucp, extra, 1); |
| } |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* called for SAS port of PROTO_SPECIFIC_LPAGE [0x18] */ |
| static void |
| show_sas_phy_event_info(int pes, unsigned int val, unsigned int thresh_val) |
| { |
| unsigned int u; |
| |
| switch (pes) { |
| case 0: |
| printf(" No event\n"); |
| break; |
| case 0x1: |
| printf(" Invalid word count: %u\n", val); |
| break; |
| case 0x2: |
| printf(" Running disparity error count: %u\n", val); |
| break; |
| case 0x3: |
| printf(" Loss of dword synchronization count: %u\n", val); |
| break; |
| case 0x4: |
| printf(" Phy reset problem count: %u\n", val); |
| break; |
| case 0x5: |
| printf(" Elasticity buffer overflow count: %u\n", val); |
| break; |
| case 0x6: |
| printf(" Received ERROR count: %u\n", val); |
| break; |
| case 0x7: |
| printf(" Invalid SPL packet count: %u\n", val); |
| break; |
| case 0x8: |
| printf(" Loss of SPL packet synchronization count: %u\n", val); |
| break; |
| case 0x20: |
| printf(" Received address frame error count: %u\n", val); |
| break; |
| case 0x21: |
| printf(" Transmitted abandon-class OPEN_REJECT count: %u\n", val); |
| break; |
| case 0x22: |
| printf(" Received abandon-class OPEN_REJECT count: %u\n", val); |
| break; |
| case 0x23: |
| printf(" Transmitted retry-class OPEN_REJECT count: %u\n", val); |
| break; |
| case 0x24: |
| printf(" Received retry-class OPEN_REJECT count: %u\n", val); |
| break; |
| case 0x25: |
| printf(" Received AIP (WATING ON PARTIAL) count: %u\n", val); |
| break; |
| case 0x26: |
| printf(" Received AIP (WAITING ON CONNECTION) count: %u\n", val); |
| break; |
| case 0x27: |
| printf(" Transmitted BREAK count: %u\n", val); |
| break; |
| case 0x28: |
| printf(" Received BREAK count: %u\n", val); |
| break; |
| case 0x29: |
| printf(" Break timeout count: %u\n", val); |
| break; |
| case 0x2a: |
| printf(" Connection count: %u\n", val); |
| break; |
| case 0x2b: |
| printf(" Peak transmitted pathway blocked count: %u\n", |
| val & 0xff); |
| printf(" Peak value detector threshold: %u\n", |
| thresh_val & 0xff); |
| break; |
| case 0x2c: |
| u = val & 0xffff; |
| if (u < 0x8000) |
| printf(" Peak transmitted arbitration wait time (us): " |
| "%u\n", u); |
| else |
| printf(" Peak transmitted arbitration wait time (ms): " |
| "%u\n", 33 + (u - 0x8000)); |
| u = thresh_val & 0xffff; |
| if (u < 0x8000) |
| printf(" Peak value detector threshold (us): %u\n", |
| u); |
| else |
| printf(" Peak value detector threshold (ms): %u\n", |
| 33 + (u - 0x8000)); |
| break; |
| case 0x2d: |
| printf(" Peak arbitration time (us): %u\n", val); |
| printf(" Peak value detector threshold: %u\n", thresh_val); |
| break; |
| case 0x2e: |
| printf(" Peak connection time (us): %u\n", val); |
| printf(" Peak value detector threshold: %u\n", thresh_val); |
| break; |
| case 0x2f: |
| printf(" Persistent connection count: %u\n", val); |
| break; |
| case 0x40: |
| printf(" Transmitted SSP frame count: %u\n", val); |
| break; |
| case 0x41: |
| printf(" Received SSP frame count: %u\n", val); |
| break; |
| case 0x42: |
| printf(" Transmitted SSP frame error count: %u\n", val); |
| break; |
| case 0x43: |
| printf(" Received SSP frame error count: %u\n", val); |
| break; |
| case 0x44: |
| printf(" Transmitted CREDIT_BLOCKED count: %u\n", val); |
| break; |
| case 0x45: |
| printf(" Received CREDIT_BLOCKED count: %u\n", val); |
| break; |
| case 0x50: |
| printf(" Transmitted SATA frame count: %u\n", val); |
| break; |
| case 0x51: |
| printf(" Received SATA frame count: %u\n", val); |
| break; |
| case 0x52: |
| printf(" SATA flow control buffer overflow count: %u\n", val); |
| break; |
| case 0x60: |
| printf(" Transmitted SMP frame count: %u\n", val); |
| break; |
| case 0x61: |
| printf(" Received SMP frame count: %u\n", val); |
| break; |
| case 0x63: |
| printf(" Received SMP frame error count: %u\n", val); |
| break; |
| default: |
| printf(" Unknown phy event source: %d, val=%u, thresh_val=%u\n", |
| pes, val, thresh_val); |
| break; |
| } |
| } |
| |
| static const char * sas_link_rate_arr[16] = { |
| "phy enabled; unknown rate", |
| "phy disabled", |
| "phy enabled; speed negotiation failed", |
| "phy enabled; SATA spinup hold state", |
| "phy enabled; port selector", |
| "phy enabled; reset in progress", |
| "phy enabled; unsupported phy attached", |
| "reserved [0x7]", |
| "1.5 Gbps", /* 0x8 */ |
| "3 Gbps", |
| "6 Gbps", |
| "12 Gbps", |
| "22.5 Gbps", |
| "reserved [0xd]", |
| "reserved [0xe]", |
| "reserved [0xf]", |
| }; |
| |
| static char * |
| sas_negot_link_rate(int lrate, char * b, int blen) |
| { |
| int mask = 0xf; |
| |
| if (~mask & lrate) |
| snprintf(b, blen, "bad link_rate value=0x%x\n", lrate); |
| else |
| snprintf(b, blen, "%s", sas_link_rate_arr[lrate]); |
| return b; |
| } |
| |
| /* helper for SAS port of PROTO_SPECIFIC_LPAGE [0x18] */ |
| static void |
| show_sas_port_param(const uint8_t * ucp, int param_len, |
| const struct opts_t * op) |
| { |
| int j, m, nphys, pcb, t, sz, spld_len; |
| const uint8_t * vcp; |
| uint64_t ull; |
| unsigned int ui; |
| char pcb_str[PCB_STR_LEN]; |
| char s[64]; |
| |
| sz = sizeof(s); |
| pcb = ucp[2]; |
| t = sg_get_unaligned_be16(ucp + 0); |
| if (op->do_name) |
| printf("rel_target_port=%d\n", t); |
| else |
| printf("relative target port id = %d\n", t); |
| if (op->do_name) |
| printf(" gen_code=%d\n", ucp[6]); |
| else |
| printf(" generation code = %d\n", ucp[6]); |
| nphys = ucp[7]; |
| if (op->do_name) |
| printf(" num_phys=%d\n", nphys); |
| else { |
| printf(" number of phys = %d", nphys); |
| if ((op->do_pcb) && (0 == op->do_name)) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| } |
| |
| for (j = 0, vcp = ucp + 8; j < (param_len - 8); |
| vcp += spld_len, j += spld_len) { |
| if (op->do_name) |
| printf(" phy_id=%d\n", vcp[1]); |
| else |
| printf(" phy identifier = %d\n", vcp[1]); |
| spld_len = vcp[3]; |
| if (spld_len < 44) |
| spld_len = 48; /* in SAS-1 and SAS-1.1 vcp[3]==0 */ |
| else |
| spld_len += 4; |
| if (op->do_name) { |
| t = ((0x70 & vcp[4]) >> 4); |
| printf(" att_dev_type=%d\n", t); |
| printf(" att_iport_mask=0x%x\n", vcp[6]); |
| printf(" att_phy_id=%d\n", vcp[24]); |
| printf(" att_reason=0x%x\n", (vcp[4] & 0xf)); |
| ull = sg_get_unaligned_be64(vcp + 16); |
| printf(" att_sas_addr=0x%" PRIx64 "\n", ull); |
| printf(" att_tport_mask=0x%x\n", vcp[7]); |
| ui = sg_get_unaligned_be32(vcp + 32); |
| printf(" inv_dwords=%u\n", ui); |
| ui = sg_get_unaligned_be32(vcp + 40); |
| printf(" loss_dword_sync=%u\n", ui); |
| printf(" neg_log_lrate=%d\n", 0xf & vcp[5]); |
| ui = sg_get_unaligned_be32(vcp + 44); |
| printf(" phy_reset_probs=%u\n", ui); |
| ui = sg_get_unaligned_be32(vcp + 36); |
| printf(" running_disparity=%u\n", ui); |
| printf(" reason=0x%x\n", (vcp[5] & 0xf0) >> 4); |
| ull = sg_get_unaligned_be64(vcp + 8); |
| printf(" sas_addr=0x%" PRIx64 "\n", ull); |
| } else { |
| t = ((0x70 & vcp[4]) >> 4); |
| /* attached SAS device type. In SAS-1.1 case 2 was an edge |
| * expander; in SAS-2 case 3 is marked as obsolete. */ |
| switch (t) { |
| case 0: snprintf(s, sz, "no device attached"); break; |
| case 1: snprintf(s, sz, "SAS or SATA device"); break; |
| case 2: snprintf(s, sz, "expander device"); break; |
| case 3: snprintf(s, sz, "expander device (fanout)"); break; |
| default: snprintf(s, sz, "reserved [%d]", t); break; |
| } |
| /* the word 'SAS' in following added in spl4r01 */ |
| printf(" attached SAS device type: %s\n", s); |
| t = 0xf & vcp[4]; |
| switch (t) { |
| case 0: snprintf(s, sz, "unknown"); break; |
| case 1: snprintf(s, sz, "power on"); break; |
| case 2: snprintf(s, sz, "hard reset"); break; |
| case 3: snprintf(s, sz, "SMP phy control function"); break; |
| case 4: snprintf(s, sz, "loss of dword synchronization"); break; |
| case 5: snprintf(s, sz, "mux mix up"); break; |
| case 6: snprintf(s, sz, "I_T nexus loss timeout for STP/SATA"); |
| break; |
| case 7: snprintf(s, sz, "break timeout timer expired"); break; |
| case 8: snprintf(s, sz, "phy test function stopped"); break; |
| case 9: snprintf(s, sz, "expander device reduced functionality"); |
| break; |
| default: snprintf(s, sz, "reserved [0x%x]", t); break; |
| } |
| printf(" attached reason: %s\n", s); |
| t = (vcp[5] & 0xf0) >> 4; |
| switch (t) { |
| case 0: snprintf(s, sz, "unknown"); break; |
| case 1: snprintf(s, sz, "power on"); break; |
| case 2: snprintf(s, sz, "hard reset"); break; |
| case 3: snprintf(s, sz, "SMP phy control function"); break; |
| case 4: snprintf(s, sz, "loss of dword synchronization"); break; |
| case 5: snprintf(s, sz, "mux mix up"); break; |
| case 6: snprintf(s, sz, "I_T nexus loss timeout for STP/SATA"); |
| break; |
| case 7: snprintf(s, sz, "break timeout timer expired"); break; |
| case 8: snprintf(s, sz, "phy test function stopped"); break; |
| case 9: snprintf(s, sz, "expander device reduced functionality"); |
| break; |
| default: snprintf(s, sz, "reserved [0x%x]", t); break; |
| } |
| printf(" reason: %s\n", s); |
| printf(" negotiated logical link rate: %s\n", |
| sas_negot_link_rate((0xf & vcp[5]), s, sz)); |
| printf(" attached initiator port: ssp=%d stp=%d smp=%d\n", |
| !! (vcp[6] & 8), !! (vcp[6] & 4), !! (vcp[6] & 2)); |
| printf(" attached target port: ssp=%d stp=%d smp=%d\n", |
| !! (vcp[7] & 8), !! (vcp[7] & 4), !! (vcp[7] & 2)); |
| ull = sg_get_unaligned_be64(vcp + 8); |
| printf(" SAS address = 0x%" PRIx64 "\n", ull); |
| ull = sg_get_unaligned_be64(vcp + 16); |
| printf(" attached SAS address = 0x%" PRIx64 "\n", ull); |
| printf(" attached phy identifier = %d\n", vcp[24]); |
| ui = sg_get_unaligned_be32(vcp + 32); |
| printf(" Invalid DWORD count = %u\n", ui); |
| ui = sg_get_unaligned_be32(vcp + 36); |
| printf(" Running disparity error count = %u\n", ui); |
| ui = sg_get_unaligned_be32(vcp + 40); |
| printf(" Loss of DWORD synchronization = %u\n", ui); |
| ui = sg_get_unaligned_be32(vcp + 44); |
| printf(" Phy reset problem = %u\n", ui); |
| } |
| if (spld_len > 51) { |
| int num_ped, pes; |
| const uint8_t * xcp; |
| unsigned int pvdt; |
| |
| num_ped = vcp[51]; |
| if (op->verbose > 1) |
| printf(" <<Phy event descriptors: %d, spld_len: %d, " |
| "calc_ped: %d>>\n", num_ped, spld_len, |
| (spld_len - 52) / 12); |
| if (num_ped > 0) { |
| if (op->do_name) { |
| printf(" phy_event_desc_num=%d\n", num_ped); |
| return; /* don't decode at this stage */ |
| } else |
| printf(" Phy event descriptors:\n"); |
| } |
| xcp = vcp + 52; |
| for (m = 0; m < (num_ped * 12); m += 12, xcp += 12) { |
| pes = xcp[3]; |
| ui = sg_get_unaligned_be32(xcp + 4); |
| pvdt = sg_get_unaligned_be32(xcp + 8); |
| show_sas_phy_event_info(pes, ui, pvdt); |
| } |
| } else if (op->verbose) |
| printf(" <<No phy event descriptors>>\n"); |
| } |
| } |
| |
| /* PROTO_SPECIFIC_LPAGE [0x18] */ |
| static bool |
| show_protocol_specific_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int k, num, pl, pc, pid; |
| const uint8_t * ucp; |
| |
| num = len - 4; |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| if (op->do_name) |
| printf("log_page=0x%x\n", PROTO_SPECIFIC_LPAGE); |
| } |
| for (k = 0, ucp = resp + 4; k < num; ) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| pid = 0xf & ucp[4]; |
| if (6 != pid) { |
| pr2serr("Protocol identifier: %d, only support SAS (SPL) which " |
| "is 6\n", pid); |
| return false; /* only decode SAS log page */ |
| } |
| if ((0 == k) && (0 == op->do_name)) |
| printf("Protocol Specific port page for SAS SSP (sas-2) " |
| "[0x18]\n"); |
| show_sas_port_param(ucp, pl, op); |
| if (op->filter_given) |
| break; |
| skip: |
| k += pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* Returns 1 if processed page, 0 otherwise */ |
| /* STATS_LPAGE [0x19], subpages: 0x0 to 0x1f introduced: SPC-4 */ |
| static bool |
| show_stats_perform_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int k, num, param_len, param_code, spf, subpg_code, extra; |
| int pcb, nam; |
| unsigned int ui; |
| const uint8_t * ucp; |
| const char * ccp; |
| uint64_t ull; |
| char pcb_str[PCB_STR_LEN]; |
| |
| nam = op->do_name; |
| num = len - 4; |
| ucp = resp + 4; |
| spf = !!(resp[0] & 0x40); |
| subpg_code = spf ? resp[1] : 0; |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| if (nam) { |
| printf("log_page=0x%x\n", STATS_LPAGE); |
| if (subpg_code > 0) |
| printf("log_subpage=0x%x\n", subpg_code); |
| } else { |
| if (0 == subpg_code) |
| printf("General Statistics and Performance [0x19]\n"); |
| else |
| printf("Group Statistics and Performance (%d) " |
| "[0x19,0x%x]\n", subpg_code, subpg_code); |
| } |
| } |
| if (subpg_code > 31) |
| return false; |
| if (0 == subpg_code) { /* General statistics and performance log page */ |
| if (num < 0x5c) |
| return false; |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| if (k < 3) |
| return false; |
| param_len = ucp[3]; |
| extra = param_len + 4; |
| param_code = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter_given) { |
| if (param_code != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (param_code) { |
| case 1: /* Statistics and performance log parameter */ |
| ccp = nam ? "parameter_code=1" : "Statistics and performance " |
| "log parameter"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "read_commands=" : "number of read commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 12); |
| ccp = nam ? "write_commands=" : "number of write commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 20); |
| ccp = nam ? "lb_received=" |
| : "number of logical blocks received = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 28); |
| ccp = nam ? "lb_transmitted=" |
| : "number of logical blocks transmitted = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 36); |
| ccp = nam ? "read_proc_intervals=" |
| : "read command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 44); |
| ccp = nam ? "write_proc_intervals=" |
| : "write command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 52); |
| ccp = nam ? "weight_rw_commands=" : "weighted number of " |
| "read commands plus write commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 60); |
| ccp = nam ? "weight_rw_processing=" : "weighted read command " |
| "processing plus write command processing = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 2: /* Idle time log parameter */ |
| ccp = nam ? "parameter_code=2" : "Idle time log parameter"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "idle_time_intervals=" : "idle time " |
| "intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 3: /* Time interval log parameter for general stats */ |
| ccp = nam ? "parameter_code=3" : "Time interval log " |
| "parameter for general stats"; |
| printf("%s\n", ccp); |
| ui = sg_get_unaligned_be32(ucp + 4); |
| ccp = nam ? "time_interval_neg_exp=" : "time interval " |
| "negative exponent = "; |
| printf(" %s%u\n", ccp, ui); |
| ui = sg_get_unaligned_be32(ucp + 8); |
| ccp = nam ? "time_interval_int=" : "time interval " |
| "integer = "; |
| printf(" %s%u\n", ccp, ui); |
| break; |
| case 4: /* FUA statistics and performance log parameter */ |
| ccp = nam ? "parameter_code=4" : "Force unit access " |
| "statistics and performance log parameter "; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "read_fua_commands=" : "number of read FUA " |
| "commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 12); |
| ccp = nam ? "write_fua_commands=" : "number of write FUA " |
| "commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 20); |
| ccp = nam ? "read_fua_nv_commands=" |
| : "number of read FUA_NV commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 28); |
| ccp = nam ? "write_fua_nv_commands=" |
| : "number of write FUA_NV commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 36); |
| ccp = nam ? "read_fua_proc_intervals=" |
| : "read FUA command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 44); |
| ccp = nam ? "write_fua_proc_intervals=" |
| : "write FUA command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 52); |
| ccp = nam ? "read_fua_nv_proc_intervals=" |
| : "read FUA_NV command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 60); |
| ccp = nam ? "write_fua_nv_proc_intervals=" |
| : "write FUA_NV command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 6: /* Time interval log parameter for cache stats */ |
| ccp = nam ? "parameter_code=6" : "Time interval log " |
| "parameter for cache stats"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "time_interval_neg_exp=" : "time interval " |
| "negative exponent = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 8); |
| ccp = nam ? "time_interval_int=" : "time interval " |
| "integer = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| default: |
| if (nam) { |
| printf("parameter_code=%d\n", param_code); |
| printf(" unknown=1\n"); |
| } else |
| pr2serr("show_performance... unknown parameter code " |
| "%d\n", param_code); |
| if (op->verbose) |
| dStrHexErr((const char *)ucp, extra, 1); |
| break; |
| } |
| if ((op->do_pcb) && (0 == op->do_name)) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| } |
| } else { /* Group statistics and performance (n) log page */ |
| if (num < 0x34) |
| return false; |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| if (k < 3) |
| return false; |
| param_len = ucp[3]; |
| extra = param_len + 4; |
| param_code = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter_given) { |
| if (param_code != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (param_code) { |
| case 1: /* Group n Statistics and performance log parameter */ |
| if (nam) |
| printf("parameter_code=1\n"); |
| else |
| printf("Group %d Statistics and performance log " |
| "parameter\n", subpg_code); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "gn_read_commands=" : "group n number of read " |
| "commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 12); |
| ccp = nam ? "gn_write_commands=" : "group n number of write " |
| "commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 20); |
| ccp = nam ? "gn_lb_received=" |
| : "group n number of logical blocks received = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 28); |
| ccp = nam ? "gn_lb_transmitted=" |
| : "group n number of logical blocks transmitted = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 36); |
| ccp = nam ? "gn_read_proc_intervals=" |
| : "group n read command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 44); |
| ccp = nam ? "gn_write_proc_intervals=" |
| : "group n write command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 4: /* Group n FUA statistics and performance log parameter */ |
| ccp = nam ? "parameter_code=4" : "Group n force unit access " |
| "statistics and performance log parameter"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "gn_read_fua_commands=" |
| : "group n number of read FUA commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 12); |
| ccp = nam ? "gn_write_fua_commands=" |
| : "group n number of write FUA commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 20); |
| ccp = nam ? "gn_read_fua_nv_commands=" |
| : "group n number of read FUA_NV commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 28); |
| ccp = nam ? "gn_write_fua_nv_commands=" |
| : "group n number of write FUA_NV commands = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 36); |
| ccp = nam ? "gn_read_fua_proc_intervals=" |
| : "group n read FUA command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 44); |
| ccp = nam ? "gn_write_fua_proc_intervals=" : "group n write " |
| "FUA command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 52); |
| ccp = nam ? "gn_read_fua_nv_proc_intervals=" : "group n " |
| "read FUA_NV command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| ull = sg_get_unaligned_be64(ucp + 60); |
| ccp = nam ? "gn_write_fua_nv_proc_intervals=" : "group n " |
| "write FUA_NV command processing intervals = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| default: |
| if (nam) { |
| printf("parameter_code=%d\n", param_code); |
| printf(" unknown=1\n"); |
| } else |
| pr2serr("show_performance... unknown parameter code " |
| "%d\n", param_code); |
| if (op->verbose) |
| dStrHexErr((const char *)ucp, extra, 1); |
| break; |
| } |
| if ((op->do_pcb) && (0 == op->do_name)) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| } |
| } |
| return true; |
| } |
| |
| /* Returns 1 if processed page, 0 otherwise */ |
| /* STATS_LPAGE [0x19], CACHE_STATS_SUBPG [0x20] introduced: SPC-4 */ |
| static bool |
| show_cache_stats_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int k, num, pc, spf, subpg_code, extra; |
| int pcb, nam; |
| unsigned int ui; |
| const uint8_t * ucp; |
| const char * ccp; |
| uint64_t ull; |
| char pcb_str[PCB_STR_LEN]; |
| |
| nam = op->do_name; |
| num = len - 4; |
| ucp = resp + 4; |
| if (num < 4) { |
| pr2serr("badly formed Cache memory statistics page\n"); |
| return false; |
| } |
| spf = !!(resp[0] & 0x40); |
| subpg_code = spf ? resp[1] : 0; |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| if (nam) { |
| printf("log_page=0x%x\n", STATS_LPAGE); |
| if (subpg_code > 0) |
| printf("log_subpage=0x%x\n", subpg_code); |
| } else |
| printf("Cache memory statistics page [0x19,0x20]\n"); |
| } |
| |
| for (k = num; k > 0; k -= extra, ucp += extra) { |
| if (k < 3) { |
| pr2serr("short Cache memory statistics page\n"); |
| return false; |
| } |
| if (8 != ucp[3]) { |
| printf("Cache memory statistics page parameter length not " |
| "8\n"); |
| return false; |
| } |
| extra = ucp[3] + 4; |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| continue; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, extra); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, extra, |
| ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 1: /* Read cache memory hits log parameter */ |
| ccp = nam ? "parameter_code=1" : |
| "Read cache memory hits log parameter"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "read_cache_memory_hits=" : |
| "read cache memory hits = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 2: /* Reads to cache memory log parameter */ |
| ccp = nam ? "parameter_code=2" : |
| "Reads to cache memory log parameter"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "reads_to_cache_memory=" : |
| "reads to cache memory = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 3: /* Write cache memory hits log parameter */ |
| ccp = nam ? "parameter_code=3" : |
| "Write cache memory hits log parameter"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "write_cache_memory_hits=" : |
| "write cache memory hits = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 4: /* Writes from cache memory log parameter */ |
| ccp = nam ? "parameter_code=4" : |
| "Writes from cache memory log parameter"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "writes_from_cache_memory=" : |
| "writes from cache memory = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 5: /* Time from last hard reset log parameter */ |
| ccp = nam ? "parameter_code=5" : |
| "Time from last hard reset log parameter"; |
| printf("%s\n", ccp); |
| ull = sg_get_unaligned_be64(ucp + 4); |
| ccp = nam ? "time_from_last_hard_reset=" : |
| "time from last hard reset = "; |
| printf(" %s%" PRIu64 "\n", ccp, ull); |
| break; |
| case 6: /* Time interval log parameter for cache stats */ |
| ccp = nam ? "parameter_code=6" : |
| "Time interval log parameter"; |
| printf("%s\n", ccp); |
| ui = sg_get_unaligned_be32(ucp + 4); |
| ccp = nam ? "time_interval_neg_exp=" : "time interval " |
| "negative exponent = "; |
| printf(" %s%u\n", ccp, ui); |
| ui = sg_get_unaligned_be32(ucp + 8); |
| ccp = nam ? "time_interval_int=" : "time interval " |
| "integer = "; |
| printf(" %s%u\n", ccp, ui); |
| break; |
| default: |
| if (nam) { |
| printf("parameter_code=%d\n", pc); |
| printf(" unknown=1\n"); |
| } else |
| pr2serr("show_performance... unknown parameter code %d\n", |
| pc); |
| if (op->verbose) |
| dStrHexErr((const char *)ucp, extra, 1); |
| break; |
| } |
| if ((op->do_pcb) && (0 == op->do_name)) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| } |
| return true; |
| } |
| |
| /* FORMAT_STATUS_LPAGE [0x8] introduced: SBC-2 */ |
| static bool |
| show_format_status_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int k, j, num, pl, pc, pcb, all_ff, counter; |
| const uint8_t * ucp; |
| const uint8_t * xp; |
| uint64_t ull; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Format status page [0x8]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| counter = 1; |
| switch (pc) { |
| case 0: |
| if (pl < 5) |
| printf(" Format data out: <empty>\n"); |
| else { |
| for (all_ff = 1, j = 4; j < pl; ++j) { |
| if (0xff != ucp[j]) { |
| all_ff = 0; |
| break; |
| } |
| } |
| if (all_ff) |
| printf(" Format data out: <not available>\n"); |
| else { |
| printf(" Format data out:\n"); |
| dStrHex((const char *)ucp + 4, pl - 4, 0); |
| } |
| } |
| counter = 0; |
| break; |
| case 1: |
| printf(" Grown defects during certification"); |
| break; |
| case 2: |
| printf(" Total blocks reassigned during format"); |
| break; |
| case 3: |
| printf(" Total new blocks reassigned"); |
| break; |
| case 4: |
| printf(" Power on minutes since format"); |
| break; |
| default: |
| printf(" Unknown Format status code = 0x%x\n", pc); |
| counter = 0; |
| dStrHex((const char *)ucp, pl, 0); |
| break; |
| } |
| if (counter) { |
| k = pl - 4; |
| xp = ucp + 4; |
| if (k > (int)sizeof(ull)) { |
| xp += (k - sizeof(ull)); |
| k = sizeof(ull); |
| } |
| ull = 0; |
| for (all_ff = 0, j = 0; j < k; ++j) { |
| if (j > 0) |
| ull <<= 8; |
| else |
| all_ff = 1; |
| ull |= xp[j]; |
| if (0xff != xp[j]) |
| all_ff = 0; |
| } |
| if (all_ff) |
| printf(" <not available>"); |
| else |
| printf(" = %" PRIu64 "", ull); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| } else { |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* Non-volatile cache page [0x17] introduced: SBC-2 */ |
| static bool |
| show_non_volatile_cache_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int j, num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Non-volatile cache page [0x17]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0: |
| printf(" Remaining non-volatile time: "); |
| if (3 == ucp[4]) { |
| j = sg_get_unaligned_be24(ucp + 5); |
| switch (j) { |
| case 0: |
| printf("0 (i.e. it is now volatile)\n"); |
| break; |
| case 1: |
| printf("<unknown>\n"); |
| break; |
| case 0xffffff: |
| printf("<indefinite>\n"); |
| break; |
| default: |
| printf("%d minutes [%d:%d]\n", j, (j / 60), (j % 60)); |
| break; |
| } |
| } else |
| printf("<unexpected parameter length=%d>\n", ucp[4]); |
| break; |
| case 1: |
| printf(" Maximum non-volatile time: "); |
| if (3 == ucp[4]) { |
| j = sg_get_unaligned_be24(ucp + 5); |
| switch (j) { |
| case 0: |
| printf("0 (i.e. it is now volatile)\n"); |
| break; |
| case 1: |
| printf("<reserved>\n"); |
| break; |
| case 0xffffff: |
| printf("<indefinite>\n"); |
| break; |
| default: |
| printf("%d minutes [%d:%d]\n", j, (j / 60), (j % 60)); |
| break; |
| } |
| } else |
| printf("<unexpected parameter length=%d>\n", ucp[4]); |
| break; |
| default: |
| printf(" Unknown Format status code = 0x%x\n", pc); |
| dStrHex((const char *)ucp, pl, 0); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* LB_PROV_LPAGE [0xc] introduced: SBC-3 */ |
| static bool |
| show_lb_provisioning_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| const char * cp; |
| char str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Logical block provisioning page [0xc]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0x1: |
| cp = " Available LBA mapping threshold"; |
| break; |
| case 0x2: |
| cp = " Used LBA mapping threshold"; |
| break; |
| case 0x3: |
| cp = " Available provisioning resource percentage"; |
| break; |
| case 0x100: |
| cp = " De-duplicated LBA"; |
| break; |
| case 0x101: |
| cp = " Compressed LBA"; |
| break; |
| case 0x102: |
| cp = " Total efficiency LBA"; |
| break; |
| default: |
| cp = NULL; |
| break; |
| } |
| if (cp) { |
| if ((pl < 8) || (num < 8)) { |
| if (num < 8) |
| pr2serr("\n truncated by response length, expected at " |
| "least 8 bytes\n"); |
| else |
| pr2serr("\n parameter length >= 8 expected, got %d\n", |
| pl); |
| break; |
| } |
| if (0x3 == pc) |
| printf(" %s: %u %%\n", cp, sg_get_unaligned_be16(ucp + 4)); |
| else { |
| printf(" %s resource count:", cp); |
| printf(" %u\n", sg_get_unaligned_be32(ucp + 4)); |
| } |
| if (pl > 8) { |
| switch (ucp[8] & 0x3) { |
| case 0: cp = "not reported"; break; |
| case 1: cp = "dedicated to lu"; break; |
| case 2: cp = "not dedicated to lu"; break; |
| case 3: cp = "reserved"; break; |
| } |
| printf(" Scope: %s\n", cp); |
| } |
| } else if ((pc >= 0xfff0) && (pc <= 0xffff)) { |
| printf(" Vendor specific [0x%x]:", pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| } else { |
| printf(" Reserved [parameter_code=0x%x]:\n", pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* UTILIZATION_SUBPG [0xe,0x1] introduced: SBC-4 */ |
| static bool |
| show_utilization_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int num, pl, pc, pcb, k; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Utilization page [0xe,0x1]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0x0: |
| printf(" Workload utilization:"); |
| if ((pl < 6) || (num < 6)) { |
| if (num < 6) |
| pr2serr("\n truncated by response length, expected " |
| "at least 6 bytes\n"); |
| else |
| pr2serr("\n parameter length >= 6 expected, got %d\n", |
| pl); |
| break; |
| } |
| k = sg_get_unaligned_be16(ucp + 4); |
| printf(" %d.%02d %%\n", k / 100, k % 100); |
| break; |
| case 0x1: |
| printf(" Utilization usage rate based on date and time:"); |
| if ((pl < 6) || (num < 6)) { |
| if (num < 6) |
| pr2serr("\n truncated by response length, expected " |
| "at least 6 bytes\n"); |
| else |
| pr2serr("\n parameter length >= 6 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" %d %%\n", ucp[4]); |
| break; |
| default: |
| printf(" Reserved [parameter_code=0x%x]:\n", pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* SOLID_STATE_MEDIA_LPAGE [0x11] introduced: SBC-3 */ |
| static bool |
| show_solid_state_media_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Solid state media page [0x11]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0x1: |
| printf(" Percentage used endurance indicator:"); |
| if ((pl < 8) || (num < 8)) { |
| if (num < 8) |
| pr2serr("\n truncated by response length, expected " |
| "at least 8 bytes\n"); |
| else |
| pr2serr("\n parameter length >= 8 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" %u %%\n", ucp[7]); |
| break; |
| default: |
| printf(" Reserved [parameter_code=0x%x]:\n", pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| static const char * dt_dev_activity[] = { |
| "No DT device activity", |
| "Cleaning operation in progress", |
| "Volume is being loaded", |
| "Volume is being unloaded", |
| "Other medium activity", |
| "Reading from medium", |
| "Writing to medium", |
| "Locating medium", |
| "Rewinding medium", /* 8 */ |
| "Erasing volume", |
| "Formatting volume", |
| "Calibrating", |
| "Other DT device activity", |
| "Microcode update in progress", |
| "Reading encrypted from medium", |
| "Writing encrypted to medium", |
| "Diagnostic operation in progress", /* 10 */ |
| }; |
| |
| /* DT device status [0x11] (ssc, adc) */ |
| static bool |
| show_dt_device_status_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb, j; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| char b[64]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("DT device status page (ssc-3, adc-3) [0x11]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0x0: |
| printf(" Very high frequency data:\n"); |
| if ((pl < 8) || (num < 8)) { |
| if (num < 8) |
| pr2serr(" truncated by response length, expected at " |
| "least 8 bytes\n"); |
| else |
| pr2serr(" parameter length >= 8 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" PAMR=%d HUI=%d MACC=%d CMPR=%d ", !!(0x80 & ucp[4]), |
| !!(0x40 & ucp[4]), !!(0x20 & ucp[4]), !!(0x10 & ucp[4])); |
| printf("WRTP=%d CRQST=%d CRQRD=%d DINIT=%d\n", !!(0x8 & ucp[4]), |
| !!(0x4 & ucp[4]), !!(0x2 & ucp[4]), !!(0x1 & ucp[4])); |
| printf(" INXTN=%d RAA=%d MPRSNT=%d ", !!(0x80 & ucp[5]), |
| !!(0x20 & ucp[5]), !!(0x10 & ucp[5])); |
| printf("MSTD=%d MTHRD=%d MOUNTED=%d\n", |
| !!(0x4 & ucp[5]), !!(0x2 & ucp[5]), !!(0x1 & ucp[5])); |
| printf(" DT device activity: "); |
| j = ucp[6]; |
| if (j < (int)(sizeof(dt_dev_activity) / |
| sizeof(dt_dev_activity[0]))) |
| printf("%s\n", dt_dev_activity[j]); |
| else if (j < 0x80) |
| printf("Reserved [0x%x]\n", j); |
| else |
| printf("Vendor specific [0x%x]\n", j); |
| printf(" VS=%d TDDEC=%d EPP=%d ", !!(0x80 & ucp[7]), |
| !!(0x20 & ucp[7]), !!(0x10 & ucp[7])); |
| printf("ESR=%d RRQST=%d INTFC=%d TAFC=%d\n", !!(0x8 & ucp[7]), |
| !!(0x4 & ucp[7]), !!(0x2 & ucp[7]), !!(0x1 & ucp[7])); |
| break; |
| case 0x1: |
| printf(" Very high frequency polling delay: "); |
| if ((pl < 6) || (num < 6)) { |
| if (num < 6) |
| pr2serr("\n truncated by response length, expected at " |
| "least 6 bytes\n"); |
| else |
| pr2serr("\n parameter length >= 6 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" %d milliseconds\n", sg_get_unaligned_be16(ucp + 4)); |
| break; |
| case 0x2: |
| printf(" DT device ADC data encryption control status (hex " |
| "only now):\n"); |
| if ((pl < 12) || (num < 12)) { |
| if (num < 12) |
| pr2serr(" truncated by response length, expected at " |
| "least 12 bytes\n"); |
| else |
| pr2serr(" parameter length >= 12 expected, got %d\n", |
| pl); |
| break; |
| } |
| dStrHex((const char *)ucp + 4, 8, 1); |
| break; |
| case 0x3: |
| printf(" Key management error data (hex only now):\n"); |
| if ((pl < 16) || (num < 16)) { |
| if (num < 16) |
| pr2serr(" truncated by response length, expected at " |
| "least 16 bytes\n"); |
| else |
| pr2serr(" parameter length >= 16 expected, got %d\n", |
| pl); |
| break; |
| } |
| dStrHex((const char *)ucp + 4, 12, 1); |
| break; |
| default: |
| if ((pc >= 0x101) && (pc <= 0x1ff)) { |
| printf(" Primary port %d status:\n", pc - 0x100); |
| if (12 == ucp[3]) { /* if length of desc is 12, assume SAS */ |
| printf(" SAS: negotiated physical link rate: %s\n", |
| sas_negot_link_rate((0xf & (ucp[4] >> 4)), b, |
| sizeof(b))); |
| printf(" signal=%d, pic=%d, ", !!(0x2 & ucp[4]), |
| !!(0x1 & ucp[4])); |
| printf("hashed SAS addr: 0x%u\n", |
| sg_get_unaligned_be24(ucp + 5)); |
| printf(" SAS addr: 0x%" PRIx64 "\n", |
| sg_get_unaligned_be64(ucp + 8)); |
| } else { |
| printf(" non-SAS transport, in hex:\n"); |
| dStrHex((const char *)ucp + 4, |
| ((pl < num) ? pl : num) - 4, 0); |
| } |
| } else if (pc >= 0x8000) { |
| printf(" Vendor specific [parameter_code=0x%x]:\n", pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| } else { |
| printf(" Reserved [parameter_code=0x%x]:\n", pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| } |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* SAT_ATA_RESULTS_LPAGE (SAT-2) [0x16] */ |
| static bool |
| show_ata_pt_results_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| const uint8_t * dp; |
| char str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("ATA pass-through results page (sat-2) [0x16]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| if ((pc < 0xf) && (pl > 17)) { |
| int extend, sector_count; |
| |
| dp = ucp + 4; |
| printf(" Log_index=0x%x (parameter_code=0x%x)\n", pc + 1, pc); |
| extend = dp[2] & 1; |
| sector_count = dp[5] + (extend ? (dp[4] << 8) : 0); |
| printf(" extend=%d error=0x%x sector_count=0x%x\n", extend, |
| dp[3], sector_count); |
| if (extend) |
| printf(" lba=0x%02x%02x%02x%02x%02x%02x\n", dp[10], dp[8], |
| dp[6], dp[11], dp[9], dp[7]); |
| else |
| printf(" lba=0x%02x%02x%02x\n", dp[11], dp[9], dp[7]); |
| printf(" device=0x%x status=0x%x\n", dp[12], dp[13]); |
| } else { |
| printf(" Reserved [parameter_code=0x%x]:\n", pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| static const char * bms_status[] = { |
| "no background scans active", |
| "background medium scan is active", |
| "background pre-scan is active", |
| "background scan halted due to fatal error", |
| "background scan halted due to a vendor specific pattern of error", |
| "background scan halted due to medium formatted without P-List", |
| "background scan halted - vendor specific cause", |
| "background scan halted due to temperature out of range", |
| "background scan enabled, none active (waiting for BMS interval timer " |
| "to expire)", /* 8 */ |
| "background scan halted - scan results list full", |
| "background scan halted - pre-scan time limit timer expired" /* 10 */, |
| }; |
| |
| static const char * reassign_status[] = { |
| "Reassign status: Reserved [0x0]", |
| "Reassignment pending receipt of Reassign or Write command", |
| "Logical block successfully reassigned by device server", |
| "Reassign status: Reserved [0x3]", |
| "Reassignment by device server failed", |
| "Logical block recovered by device server via rewrite", |
| "Logical block reassigned by application client, has valid data", |
| "Logical block reassigned by application client, contains no valid data", |
| "Logical block unsuccessfully reassigned by application client", /* 8 */ |
| }; |
| |
| /* Background scan results [0x15,0] for disk introduced: SBC-3 */ |
| static bool |
| show_background_scan_results_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int j, m, num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Background scan results page [0x15]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0: |
| printf(" Status parameters:\n"); |
| if ((pl < 16) || (num < 16)) { |
| if (num < 16) |
| pr2serr(" truncated by response length, expected at " |
| "least 16 bytes\n"); |
| else |
| pr2serr(" parameter length >= 16 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" Accumulated power on minutes: "); |
| j = sg_get_unaligned_be32(ucp + 4); |
| printf("%d [h:m %d:%d]\n", j, (j / 60), (j % 60)); |
| printf(" Status: "); |
| j = ucp[9]; |
| if (j < (int)(sizeof(bms_status) / sizeof(bms_status[0]))) |
| printf("%s\n", bms_status[j]); |
| else |
| printf("unknown [0x%x] background scan status value\n", j); |
| j = sg_get_unaligned_be16(ucp + 10); |
| printf(" Number of background scans performed: %d\n", j); |
| j = sg_get_unaligned_be16(ucp + 12); |
| #ifdef SG_LIB_MINGW |
| printf(" Background medium scan progress: %g %%\n", |
| (double)(j * 100.0 / 65536.0)); |
| #else |
| printf(" Background medium scan progress: %.2f %%\n", |
| (double)(j * 100.0 / 65536.0)); |
| #endif |
| j = sg_get_unaligned_be16(ucp + 14); |
| if (0 == j) |
| printf(" Number of background medium scans performed: 0 " |
| "[not reported]\n"); |
| else |
| printf(" Number of background medium scans performed: " |
| "%d\n", j); |
| break; |
| default: |
| if (pc > 0x800) { |
| if ((pc >= 0x8000) && (pc <= 0xafff)) |
| printf(" Medium scan parameter # %d [0x%x], vendor " |
| "specific\n", pc, pc); |
| else |
| printf(" Medium scan parameter # %d [0x%x], " |
| "reserved\n", pc, pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| break; |
| } else |
| printf(" Medium scan parameter # %d [0x%x]\n", pc, pc); |
| if ((pl < 24) || (num < 24)) { |
| if (num < 24) |
| pr2serr(" truncated by response length, expected at " |
| "least 24 bytes\n"); |
| else |
| pr2serr(" parameter length >= 24 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" Power on minutes when error detected: "); |
| j = sg_get_unaligned_be32(ucp + 4); |
| printf("%d [%d:%d]\n", j, (j / 60), (j % 60)); |
| j = (ucp[8] >> 4) & 0xf; |
| if (j < |
| (int)(sizeof(reassign_status) / sizeof(reassign_status[0]))) |
| printf(" %s\n", reassign_status[j]); |
| else |
| printf(" Reassign status: reserved [0x%x]\n", j); |
| printf(" sense key: %s [sk,asc,ascq: 0x%x,0x%x,0x%x]\n", |
| sg_get_sense_key_str(ucp[8] & 0xf, sizeof(str), str), |
| ucp[8] & 0xf, ucp[9], ucp[10]); |
| if (ucp[9] || ucp[10]) |
| printf(" %s\n", sg_get_asc_ascq_str(ucp[9], ucp[10], |
| sizeof(str), str)); |
| if (op->verbose) { |
| printf(" vendor bytes [11 -> 15]: "); |
| for (m = 0; m < 5; ++m) |
| printf("0x%02x ", ucp[11 + m]); |
| printf("\n"); |
| } |
| printf(" LBA (associated with medium error): 0x"); |
| for (m = 0; m < 8; ++m) |
| printf("%02x", ucp[16 + m]); |
| printf("\n"); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* PENDING_DEFECTS_SUBPG [0x15,0x1] introduced: SBC-4 */ |
| static bool |
| show_pending_defects_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Pending defects page [0x15,0x1]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0x0: |
| printf(" Pending defect count:"); |
| if ((pl < 8) || (num < 8)) { |
| if (num < 8) |
| pr2serr("\n truncated by response length, expected " |
| "at least 8 bytes\n"); |
| else |
| pr2serr("\n parameter length >= 8 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" %u\n", sg_get_unaligned_be32(ucp + 4)); |
| break; |
| default: |
| printf(" Pending defect: %d\n", pc); |
| if ((pl < 16) || (num < 16)) { |
| if (num < 16) |
| pr2serr("\n truncated by response length, expected " |
| "at least 16 bytes\n"); |
| else |
| pr2serr("\n parameter length >= 16 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" LBA: 0x%" PRIx64 " accumulated_power_on_hours: %u\n", |
| sg_get_unaligned_be64(ucp + 8), |
| sg_get_unaligned_be32(ucp + 4)); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* BACKGROUND_OP_SUBPG [0x15,0x2] introduced: SBC-4 rev 7 */ |
| static bool |
| show_background_op_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Background operation page [0x15,0x2]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0x0: |
| printf(" Background operation:"); |
| if ((pl < 8) || (num < 8)) { |
| if (num < 8) |
| pr2serr("\n truncated by response length, expected " |
| "at least 8 bytes\n"); |
| else |
| pr2serr("\n parameter length >= 8 expected, got %d\n", |
| pl); |
| break; |
| } |
| printf(" BO_STATUS=%d\n", ucp[4]); |
| break; |
| default: |
| printf(" Reserved [parameter_code=0x%x]:\n", pc); |
| dStrHex((const char *)ucp, ((pl < num) ? pl : num), 0); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* LPS misalignment page [0x15,0x3] introduced: SBC-4 rev 10 */ |
| static bool |
| show_lps_misalignment_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("LPS misalignment page [0x15,0x3]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0x0: |
| printf(" LPS misalignment count: "); |
| if (4 == ucp[3]) |
| printf("max lpsm: %" PRIu16 ", count=%" PRIu16 "\n", |
| sg_get_unaligned_be16(ucp + 4), |
| sg_get_unaligned_be16(ucp + 6)); |
| else |
| printf("<unexpected pc=0 parameter length=%d>\n", ucp[4]); |
| break; |
| default: |
| if (pc <= 0xf000) { |
| if (8 == ucp[3]) |
| printf(" LBA of misaligned block: 0x%" PRIx64 "\n", |
| sg_get_unaligned_be64(ucp + 8)); |
| else |
| printf("<unexpected pc=0x%x parameter length=%d>\n", |
| pc, ucp[4]); |
| } else { |
| printf("<unexpected pc=0x%x>\n", pc); |
| dStrHex((const char *)ucp, pl, 0); |
| } |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| |
| /* Sequential access device page [0xc] for tape */ |
| static bool |
| show_sequential_access_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| uint64_t ull, gbytes; |
| bool all_set; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Sequential access device page (ssc-3)\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| ull = sg_get_unaligned_be(pl - 4, ucp + 4); |
| all_set = all_bits_set(ucp + 4, pl - 4); |
| gbytes = ull / 1000000000; |
| switch (pc) { |
| case 0: |
| printf(" Data bytes received with WRITE commands: %" PRIu64 |
| " GB", gbytes); |
| if (op->verbose) |
| printf(" [%" PRIu64 " bytes]", ull); |
| printf("\n"); |
| break; |
| case 1: |
| printf(" Data bytes written to media by WRITE commands: %" PRIu64 |
| " GB", gbytes); |
| if (op->verbose) |
| printf(" [%" PRIu64 " bytes]", ull); |
| printf("\n"); |
| break; |
| case 2: |
| printf(" Data bytes read from media by READ commands: %" PRIu64 |
| " GB", gbytes); |
| if (op->verbose) |
| printf(" [%" PRIu64 " bytes]", ull); |
| printf("\n"); |
| break; |
| case 3: |
| printf(" Data bytes transferred by READ commands: %" PRIu64 |
| " GB", gbytes); |
| if (op->verbose) |
| printf(" [%" PRIu64 " bytes]", ull); |
| printf("\n"); |
| break; |
| case 4: |
| if (! all_set) |
| printf(" Native capacity from BOP to EOD: %" PRIu64 " MB\n", |
| ull); |
| break; |
| case 5: |
| if (! all_set) |
| printf(" Native capacity from BOP to EW of current " |
| "partition: %" PRIu64 " MB\n", ull); |
| break; |
| case 6: |
| if (! all_set) |
| printf(" Minimum native capacity from EW to EOP of current " |
| "partition: %" PRIu64 " MB\n", ull); |
| break; |
| case 7: |
| if (! all_set) |
| printf(" Native capacity from BOP to current position: %" |
| PRIu64 " MB\n", ull); |
| break; |
| case 8: |
| if (! all_set) |
| printf(" Maximum native capacity in device object buffer: %" |
| PRIu64 " MB\n", ull); |
| break; |
| case 0x100: |
| if (ull > 0) |
| printf(" Cleaning action required\n"); |
| else |
| printf(" Cleaning action not required (or completed)\n"); |
| if (op->verbose) |
| printf(" cleaning value: %" PRIu64 "\n", ull); |
| break; |
| default: |
| if (pc >= 0x8000) |
| printf(" Vendor specific parameter [0x%x] value: %" PRIu64 |
| "\n", pc, ull); |
| else |
| printf(" Reserved parameter [0x%x] value: %" PRIu64 "\n", |
| pc, ull); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* 0x14 for tape and ADC */ |
| static bool |
| show_device_stats_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| uint64_t ull; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Device statistics page (ssc-3 and adc)\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| if (pc < 0x1000) { |
| ull = sg_get_unaligned_be(pl - 4, ucp + 4); |
| switch (pc) { |
| case 0: |
| printf(" Lifetime media loads: %" PRIu64 "\n", ull); |
| break; |
| case 1: |
| printf(" Lifetime cleaning operations: %" PRIu64 "\n", ull); |
| break; |
| case 2: |
| printf(" Lifetime power on hours: %" PRIu64 "\n", ull); |
| break; |
| case 3: |
| printf(" Lifetime media motion (head) hours: %" PRIu64 "\n", |
| ull); |
| break; |
| case 4: |
| printf(" Lifetime metres of tape processed: %" PRIu64 "\n", |
| ull); |
| break; |
| case 5: |
| printf(" Lifetime media motion (head) hours when " |
| "incompatible media last loaded: %" PRIu64 "\n", ull); |
| break; |
| case 6: |
| printf(" Lifetime power on hours when last temperature " |
| "condition occurred: %" PRIu64 "\n", ull); |
| break; |
| case 7: |
| printf(" Lifetime power on hours when last power " |
| "consumption condition occurred: %" PRIu64 "\n", ull); |
| break; |
| case 8: |
| printf(" Media motion (head) hours since last successful " |
| "cleaning operation: %" PRIu64 "\n", ull); |
| break; |
| case 9: |
| printf(" Media motion (head) hours since 2nd to last " |
| "successful cleaning: %" PRIu64 "\n", ull); |
| break; |
| case 0xa: |
| printf(" Media motion (head) hours since 3rd to last " |
| "successful cleaning: %" PRIu64 "\n", ull); |
| break; |
| case 0xb: |
| printf(" Lifetime power on hours when last operator " |
| "initiated forced reset\n and/or emergency " |
| "eject occurred: %" PRIu64 "\n", ull); |
| break; |
| case 0xc: |
| printf(" Lifetime power cycles: %" PRIu64 "\n", ull); |
| break; |
| case 0xd: |
| printf(" Volume loads since last parameter reset: %" PRIu64 |
| "\n", ull); |
| break; |
| case 0xe: |
| printf(" Hard write errors: %" PRIu64 "\n", ull); |
| break; |
| case 0xf: |
| printf(" Hard read errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x10: |
| printf(" Duty cycle sample time: %" PRIu64 " ms\n", ull); |
| break; |
| case 0x11: |
| printf(" Read duty cycle: %" PRIu64 "\n", ull); |
| break; |
| case 0x12: |
| printf(" Write duty cycle: %" PRIu64 "\n", ull); |
| break; |
| case 0x13: |
| printf(" Activity duty cycle: %" PRIu64 "\n", ull); |
| break; |
| case 0x14: |
| printf(" Volume not present duty cycle: %" PRIu64 "\n", ull); |
| break; |
| case 0x15: |
| printf(" Ready duty cycle: %" PRIu64 "\n", ull); |
| break; |
| case 0x16: |
| printf(" MBs transferred from app client in duty cycle " |
| "sample time: %" PRIu64 "\n", ull); |
| break; |
| case 0x17: |
| printf(" MBs transferred to app client in duty cycle " |
| "sample time: %" PRIu64 "\n", ull); |
| break; |
| case 0x40: |
| printf(" Drive manufacturer's serial number: %" PRIu64 "\n", |
| ull); |
| break; |
| case 0x41: |
| printf(" Drive serial number: %" PRIu64 "\n", ull); |
| break; |
| case 0x80: |
| printf(" Medium removal prevented: %" PRIu64 "\n", ull); |
| break; |
| case 0x81: |
| printf(" Maximum recommended mechanism temperature " |
| "exceeded: %" PRIu64 "\n", ull); |
| break; |
| case 0x1000: |
| printf(" Medium motion hours for each medium type: %" PRIu64 |
| "\n", ull); |
| break; |
| default: |
| printf(" Reserved parameter [0x%x] value: %" PRIu64 "\n", pc, |
| ull); |
| break; |
| } |
| } else { |
| int k; |
| const uint8_t * p = ucp + 4; |
| |
| switch (pc) { |
| case 0x1000: |
| printf(" Media motion (head) hours for each medium type:\n"); |
| for (k = 0; ((pl - 4) - k) >= 8; k += 8, p += 8) { |
| printf(" Density code: 0x%x, Medium type: 0x%x\n", |
| p[2], p[3]); |
| printf(" Medium motion hours: %u\n", |
| sg_get_unaligned_be32(p + 4)); |
| } |
| break; |
| default: |
| if (pc >= 0x8000) |
| printf(" Vendor specific parameter [0x%x], dump in " |
| "hex:\n", pc); |
| else |
| printf(" Reserved parameter [0x%x], dump in hex:\n", pc); |
| dStrHex((const char *)ucp, pl, 0); |
| break; |
| } |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* 0x14 for media changer */ |
| static bool |
| show_media_stats_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| uint64_t ull; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Media statistics page (smc-3)\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| ull = sg_get_unaligned_be(pl - 4, ucp + 4); |
| switch (pc) { |
| case 0: |
| printf(" Number of moves: %" PRIu64 "\n", ull); |
| break; |
| case 1: |
| printf(" Number of picks: %" PRIu64 "\n", ull); |
| break; |
| case 2: |
| printf(" Number of pick retries: %" PRIu64 "\n", ull); |
| break; |
| case 3: |
| printf(" Number of places: %" PRIu64 "\n", ull); |
| break; |
| case 4: |
| printf(" Number of place retries: %" PRIu64 "\n", ull); |
| break; |
| case 5: |
| printf(" Number of volume tags read by volume " |
| "tag reader: %" PRIu64 "\n", ull); |
| break; |
| case 6: |
| printf(" Number of invalid volume tags returned by " |
| "volume tag reader: %" PRIu64 "\n", ull); |
| break; |
| case 7: |
| printf(" Number of library door opens: %" PRIu64 "\n", ull); |
| break; |
| case 8: |
| printf(" Number of import/export door opens: %" PRIu64 "\n", |
| ull); |
| break; |
| case 9: |
| printf(" Number of physical inventory scans: %" PRIu64 "\n", |
| ull); |
| break; |
| case 0xa: |
| printf(" Number of medium transport unrecovered errors: " |
| "%" PRIu64 "\n", ull); |
| break; |
| case 0xb: |
| printf(" Number of medium transport recovered errors: " |
| "%" PRIu64 "\n", ull); |
| break; |
| case 0xc: |
| printf(" Number of medium transport X axis translation " |
| "unrecovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0xd: |
| printf(" Number of medium transport X axis translation " |
| "recovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0xe: |
| printf(" Number of medium transport Y axis translation " |
| "unrecovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0xf: |
| printf(" Number of medium transport Y axis translation " |
| "recovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x10: |
| printf(" Number of medium transport Z axis translation " |
| "unrecovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x11: |
| printf(" Number of medium transport Z axis translation " |
| "recovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x12: |
| printf(" Number of medium transport rotational translation " |
| "unrecovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x13: |
| printf(" Number of medium transport rotational translation " |
| "recovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x14: |
| printf(" Number of medium transport inversion translation " |
| "unrecovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x15: |
| printf(" Number of medium transport inversion translation " |
| "recovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x16: |
| printf(" Number of medium transport auxiliary translation " |
| "unrecovered errors: %" PRIu64 "\n", ull); |
| break; |
| case 0x17: |
| printf(" Number of medium transport auxiliary translation " |
| "recovered errors: %" PRIu64 "\n", ull); |
| break; |
| default: |
| printf(" Reserved parameter [0x%x] value: %" PRIu64 "\n", |
| pc, ull); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* 0x15 for media changer */ |
| static bool |
| show_element_stats_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| unsigned int v; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Element statistics page (smc-3) [0x15]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| printf(" Element address: %d\n", pc); |
| v = sg_get_unaligned_be32(ucp + 4); |
| printf(" Number of places: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 8); |
| printf(" Number of place retries: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 12); |
| printf(" Number of picks: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 16); |
| printf(" Number of pick retries: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 20); |
| printf(" Number of determined volume identifiers: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 24); |
| printf(" Number of unreadable volume identifiers: %u\n", v); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* 0x16 for tape */ |
| static bool |
| show_tape_diag_data_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int k, num, pl, pc, pcb; |
| unsigned int v; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| char b[80]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Tape diagnostics data page (ssc-3) [0x16]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| printf(" Parameter code: %d\n", pc); |
| printf(" Density code: 0x%x\n", ucp[6]); |
| printf(" Medium type: 0x%x\n", ucp[7]); |
| v = sg_get_unaligned_be32(ucp + 8); |
| printf(" Lifetime media motion hours: %u\n", v); |
| printf(" Repeat: %d\n", !!(ucp[13] & 0x80)); |
| v = ucp[13] & 0xf; |
| printf(" Sense key: 0x%x [%s]\n", v, |
| sg_get_sense_key_str(v, sizeof(b), b)); |
| printf(" Additional sense code: 0x%x\n", ucp[14]); |
| printf(" Additional sense code qualifier: 0x%x\n", ucp[15]); |
| if (ucp[14] || ucp[15]) |
| printf(" [%s]\n", sg_get_asc_ascq_str(ucp[14], ucp[15], |
| sizeof(b), b)); |
| v = sg_get_unaligned_be32(ucp + 16); |
| printf(" Vendor specific code qualifier: 0x%x\n", v); |
| v = sg_get_unaligned_be32(ucp + 20); |
| printf(" Product revision level: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 24); |
| printf(" Hours since last clean: %u\n", v); |
| printf(" Operation code: 0x%x\n", ucp[28]); |
| printf(" Service action: 0x%x\n", ucp[29] & 0xf); |
| // Check Medium id number for all zeros |
| // ssc4r03.pdf does not define this field, why? xxxxxx |
| for (k = 32; k < 64; ++k) { |
| if(ucp[k]) |
| break; |
| } |
| if (64 == k) |
| printf(" Medium id number is 32 bytes of zero\n"); |
| else { |
| printf(" Medium id number (in hex):\n"); |
| dStrHex((const char *)(ucp + 32), 32, 0); |
| } |
| printf(" Timestamp origin: 0x%x\n", ucp[64] & 0xf); |
| // Check Timestamp for all zeros |
| for (k = 66; k < 72; ++k) { |
| if(ucp[k]) |
| break; |
| } |
| if (72 == k) |
| printf(" Timestamp is all zeros:\n"); |
| else { |
| printf(" Timestamp:\n"); |
| dStrHex((const char *)(ucp + 66), 6, 1); |
| } |
| if (pl > 72) { |
| printf(" Vendor specific:\n"); |
| dStrHex((const char *)(ucp + 72), pl - 72, 0); |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* 0x16 for media changer */ |
| static bool |
| show_mchanger_diag_data_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| unsigned int v; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| char b[80]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Media changer diagnostics data page (smc-3) [0x16]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| printf(" Parameter code: %d\n", pc); |
| printf(" Repeat: %d\n", !!(ucp[5] & 0x80)); |
| v = ucp[5] & 0xf; |
| printf(" Sense key: 0x%x [%s]\n", v, |
| sg_get_sense_key_str(v, sizeof(b), b)); |
| printf(" Additional sense code: 0x%x\n", ucp[6]); |
| printf(" Additional sense code qualifier: 0x%x\n", ucp[7]); |
| if (ucp[6] || ucp[7]) |
| printf(" [%s]\n", sg_get_asc_ascq_str(ucp[6], ucp[7], |
| sizeof(b), b)); |
| v = sg_get_unaligned_be32(ucp + 8); |
| printf(" Vendor specific code qualifier: 0x%x\n", v); |
| v = sg_get_unaligned_be32(ucp + 12); |
| printf(" Product revision level: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 16); |
| printf(" Number of moves: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 20); |
| printf(" Number of pick: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 24); |
| printf(" Number of pick retries: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 28); |
| printf(" Number of places: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 32); |
| printf(" Number of place retries: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 36); |
| printf(" Number of determined volume identifiers: %u\n", v); |
| v = sg_get_unaligned_be32(ucp + 40); |
| printf(" Number of unreadable volume identifiers: %u\n", v); |
| printf(" Operation code: 0x%x\n", ucp[44]); |
| printf(" Service action: 0x%x\n", ucp[45] & 0xf); |
| printf(" Media changer error type: 0x%x\n", ucp[46]); |
| printf(" MTAV: %d\n", !!(ucp[47] & 0x8)); |
| printf(" IAV: %d\n", !!(ucp[47] & 0x4)); |
| printf(" LSAV: %d\n", !!(ucp[47] & 0x2)); |
| printf(" DAV: %d\n", !!(ucp[47] & 0x1)); |
| v = sg_get_unaligned_be16(ucp + 48); |
| printf(" Medium transport address: 0x%x\n", v); |
| v = sg_get_unaligned_be16(ucp + 50); |
| printf(" Intial address: 0x%x\n", v); |
| v = sg_get_unaligned_be16(ucp + 52); |
| printf(" Last successful address: 0x%x\n", v); |
| v = sg_get_unaligned_be16(ucp + 54); |
| printf(" Destination address: 0x%x\n", v); |
| if (pl > 91) { |
| printf(" Volume tag information:\n"); |
| dStrHex((const char *)(ucp + 56), 36, 0); |
| } |
| if (pl > 99) { |
| printf(" Timestamp origin: 0x%x\n", ucp[92] & 0xf); |
| printf(" Timestamp:\n"); |
| dStrHex((const char *)(ucp + 94), 6, 1); |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* Helper for show_volume_stats_page() */ |
| static void |
| volume_stats_partition(const uint8_t * xp, int len, bool in_hex) |
| { |
| int dl, pn, k; |
| bool all_ffs, ffs_last_fe; |
| uint8_t uc; |
| uint64_t ull; |
| |
| while (len > 3) { |
| dl = xp[0] + 1; |
| if (dl < 3) |
| return; |
| pn = sg_get_unaligned_be16(xp + 2); |
| for (k = 0, all_ffs = false, ffs_last_fe = false; k < (dl - 4); ++k) { |
| uc = xp[4 + k]; |
| if (uc < 0xfe) |
| break; |
| if ((k < (dl - 5)) && (0xfe == uc)) |
| break; |
| if (k == (dl - 5)) { |
| if (0xff == uc) |
| all_ffs = true; |
| else if (0xfe == uc) |
| ffs_last_fe = true; |
| } |
| } |
| if ((! all_ffs) && (! ffs_last_fe)) { |
| ull = sg_get_unaligned_be(dl - 4, xp + 4); |
| if (in_hex) |
| printf(" partition number: %d, partition record data " |
| "counter: 0x%" PRIx64 "\n", pn, ull); |
| else |
| printf(" partition number: %d, partition record data " |
| "counter: %" PRIu64 "\n", pn, ull); |
| } else if (all_ffs) |
| printf(" partition number: %d, partition record data " |
| "counter is all 0xFFs\n", pn); |
| else |
| printf(" partition number: %d, partition record data " |
| "counter is all 0xFFs apart\n from a trailing " |
| "0xFE\n", pn); |
| xp += dl; |
| len -= dl; |
| } |
| } |
| |
| /* Volume Statistics log page (ssc-4) [0x17, 0x1-0xf] */ |
| static bool |
| show_volume_stats_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb, spf, subpg_code; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| char b[64]; |
| |
| spf = !!(resp[0] & 0x40); |
| subpg_code = spf ? resp[1] : 0; |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) { |
| if (0 == subpg_code) |
| printf("Volume statistics page (ssc-4) but subpage=0, abnormal: " |
| "treat like subpage=1\n"); |
| else if (subpg_code < 0x10) |
| printf("Volume statistics page (ssc-4), subpage=%d\n", |
| subpg_code); |
| else { |
| printf("Volume statistics page (ssc-4), subpage=%d; Reserved, " |
| "skip\n", subpg_code); |
| return false; |
| } |
| } |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| |
| switch (pc) { |
| case 0: |
| printf(" Page valid: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 1: |
| printf(" Thread count: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 2: |
| printf(" Total data sets written: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 3: |
| printf(" Total write retries: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 4: |
| printf(" Total unrecovered write errors: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 5: |
| printf(" Total suspended writes: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 6: |
| printf(" Total fatal suspended writes: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 7: |
| printf(" Total data sets read: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 8: |
| printf(" Total read retries: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 9: |
| printf(" Total unrecovered read errors: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0xa: |
| printf(" Total suspended reads: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0xb: |
| printf(" Total fatal suspended reads: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0xc: |
| printf(" Last mount unrecovered write errors: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0xd: |
| printf(" Last mount unrecovered read errors: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0xe: |
| printf(" Last mount megabytes written: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0xf: |
| printf(" Last mount megabytes read: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x10: |
| printf(" Lifetime megabytes written: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x11: |
| printf(" Lifetime megabytes read: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x12: |
| printf(" Last load write compression ratio: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x13: |
| printf(" Last load read compression ratio: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x14: |
| printf(" Medium mount time: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x15: |
| printf(" Medium ready time: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x16: |
| printf(" Total native capacity [MB]: %s\n", |
| num_or_unknown(ucp + 4, pl - 4, false, b, sizeof(b))); |
| break; |
| case 0x17: |
| printf(" Total used native capacity [MB]: %s\n", |
| num_or_unknown(ucp + 4, pl - 4, false, b, sizeof(b))); |
| break; |
| case 0x40: |
| printf(" Volume serial number: %.*s\n", pl - 4, ucp + 4); |
| break; |
| case 0x41: |
| printf(" Tape lot identifier: %.*s\n", pl - 4, ucp + 4); |
| break; |
| case 0x42: |
| printf(" Volume barcode: %.*s\n", pl - 4, ucp + 4); |
| break; |
| case 0x43: |
| printf(" Volume manufacturer: %.*s\n", pl - 4, ucp + 4); |
| break; |
| case 0x44: |
| printf(" Volume license code: %.*s\n", pl - 4, ucp + 4); |
| break; |
| case 0x45: |
| printf(" Volume personality: %.*s\n", pl - 4, ucp + 4); |
| break; |
| case 0x80: |
| printf(" Write protect: %s\n", |
| num_or_unknown(ucp + 4, pl - 4, false, b, sizeof(b))); |
| break; |
| case 0x81: |
| printf(" WORM: %s\n", |
| num_or_unknown(ucp + 4, pl - 4, false, b, sizeof(b))); |
| break; |
| case 0x82: |
| printf(" Maximum recommended tape path temperature exceeded: " |
| "%s\n", num_or_unknown(ucp + 4, pl - 4, false, b, |
| sizeof(b))); |
| break; |
| case 0x100: |
| printf(" Volume write mounts: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x101: |
| printf(" Beginning of medium passes: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x102: |
| printf(" Middle of medium passes: %" PRIu64 "\n", |
| sg_get_unaligned_be(pl - 4, ucp + 4)); |
| break; |
| case 0x200: |
| printf(" Logical position of first encrypted logical object:\n"); |
| volume_stats_partition(ucp + 4, pl - 4, true); |
| break; |
| case 0x201: |
| printf(" Logical position of first unencrypted logical object " |
| "after first\n encrypted logical object:\n"); |
| volume_stats_partition(ucp + 4, pl - 4, true); |
| break; |
| case 0x202: |
| printf(" Native capacity partition(s) [MB]:\n"); |
| volume_stats_partition(ucp + 4, pl - 4, false); |
| break; |
| case 0x203: |
| printf(" Used native capacity partition(s) [MB]:\n"); |
| volume_stats_partition(ucp + 4, pl - 4, false); |
| break; |
| case 0x204: |
| printf(" Remaining native capacity partition(s) [MB]:\n"); |
| volume_stats_partition(ucp + 4, pl - 4, false); |
| break; |
| case 0x300: |
| printf(" Mount history, payload in hex:\n"); |
| // xxxxxxxx TODO |
| dStrHex((const char *)(ucp + 4), pl - 4, 0); |
| break; |
| |
| default: |
| if (pc >= 0xf000) |
| printf(" Vendor specific parameter code (0x%x), payload " |
| "in hex\n", pc); |
| else |
| printf(" Reserved parameter code (0x%x), payload in hex\n", |
| pc); |
| dStrHex((const char *)(ucp + 4), pl - 4, 0); |
| break; |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf(" <%s>\n", pcb_str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| static const char * tape_alert_strs[] = { |
| "<parameter code 0, unknown>", /* 0x0 */ |
| "Read warning", |
| "Write warning", |
| "Hard error", |
| "Media", |
| "Read failure", |
| "Write failure", |
| "Media life", |
| "Not data grade", /* 0x8 */ |
| "Write protect", |
| "No removal", |
| "Cleaning media", |
| "Unsupported format", |
| "Recoverable mechanical cartridge failure", |
| "Unrecoverable mechanical cartridge failure", |
| "Memory chip in cartridge failure", |
| "Forced eject", /* 0x10 */ |
| "Read only format", |
| "Tape directory corrupted on load", |
| "Nearing media life", |
| "Cleaning required", |
| "Cleaning requested", |
| "Expired cleaning media", |
| "Invalid cleaning tape", |
| "Retension requested", /* 0x18 */ |
| "Dual port interface error", |
| "Cooling fan failing", |
| "Power supply failure", |
| "Power consumption", |
| "Drive maintenance", |
| "Hardware A", |
| "Hardware B", |
| "Interface", /* 0x20 */ |
| "Eject media", |
| "Microcode update fail", |
| "Drive humidity", |
| "Drive temperature", |
| "Drive voltage", |
| "Predictive failure", |
| "Diagnostics required", |
| "Obsolete (28h)", /* 0x28 */ |
| "Obsolete (29h)", |
| "Obsolete (2Ah)", |
| "Obsolete (2Bh)", |
| "Obsolete (2Ch)", |
| "Obsolete (2Dh)", |
| "Obsolete (2Eh)", |
| "Reserved (2Fh)", |
| "Reserved (30h)", /* 0x30 */ |
| "Reserved (31h)", |
| "Lost statistics", |
| "Tape directory invalid at unload", |
| "Tape system area write failure", |
| "Tape system area read failure", |
| "No start of data", |
| "Loading failure", |
| "Unrecoverable unload failure", /* 0x38 */ |
| "Automation interface failure", |
| "Firmware failure", |
| "WORM medium - integrity check failed", |
| "WORM medium - overwrite attempted", |
| }; |
| |
| /* TAPE_ALERT_LPAGE [0x2e] */ |
| static bool |
| show_tape_alert_ssc_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb, flag; |
| const uint8_t * ucp; |
| char str[PCB_STR_LEN]; |
| |
| /* N.B. the Tape alert log page for smc-3 is different */ |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Tape alert page (ssc-3) [0x2e]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| flag = ucp[4] & 1; |
| if (op->verbose && (0 == op->do_brief) && flag) |
| printf(" >>>> "); |
| if ((0 == op->do_brief) || op->verbose || flag) { |
| if (pc < (int)(sizeof(tape_alert_strs) / |
| sizeof(tape_alert_strs[0]))) |
| printf(" %s: %d\n", tape_alert_strs[pc], flag); |
| else |
| printf(" Reserved parameter code 0x%x, flag: %d\n", pc, |
| flag); |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, str, sizeof(str)); |
| printf(" <%s>\n", str); |
| } |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* 0x37 */ |
| static bool |
| show_seagate_cache_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb; |
| const uint8_t * ucp; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Seagate cache page [0x37]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| switch (pc) { |
| case 0: printf(" Blocks sent to initiator"); break; |
| case 1: printf(" Blocks received from initiator"); break; |
| case 2: |
| printf(" Blocks read from cache and sent to initiator"); |
| break; |
| case 3: |
| printf(" Number of read and write commands whose size " |
| "<= segment size"); |
| break; |
| case 4: |
| printf(" Number of read and write commands whose size " |
| "> segment size"); break; |
| default: printf(" Unknown Seagate parameter code = 0x%x", pc); break; |
| } |
| printf(" = %" PRIu64 "", sg_get_unaligned_be(pl - 4, ucp + 4)); |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| /* 0x3e */ |
| static bool |
| show_seagate_factory_page(const uint8_t * resp, int len, |
| const struct opts_t * op) |
| { |
| int num, pl, pc, pcb, valid; |
| const uint8_t * ucp; |
| uint64_t ull; |
| char pcb_str[PCB_STR_LEN]; |
| |
| if (op->verbose || ((0 == op->do_raw) && (0 == op->do_hex))) |
| printf("Seagate/Hitachi factory page [0x3e]\n"); |
| num = len - 4; |
| ucp = &resp[0] + 4; |
| while (num > 3) { |
| pc = sg_get_unaligned_be16(ucp + 0); |
| pcb = ucp[2]; |
| pl = ucp[3] + 4; |
| if (op->filter_given) { |
| if (pc != op->filter) |
| goto skip; |
| if (op->do_raw) { |
| dStrRaw((const char *)ucp, pl); |
| break; |
| } else if (op->do_hex) { |
| dStrHex((const char *)ucp, pl, ((1 == op->do_hex) ? 1 : -1)); |
| break; |
| } |
| } |
| valid = 1; |
| switch (pc) { |
| case 0: printf(" number of hours powered up"); break; |
| case 8: printf(" number of minutes until next internal SMART test"); |
| break; |
| default: |
| valid = 0; |
| printf(" Unknown Seagate/Hitachi parameter code = 0x%x", pc); |
| break; |
| } |
| if (valid) { |
| ull = sg_get_unaligned_be(pl - 4, ucp + 4); |
| if (0 == pc) |
| printf(" = %.2f", ((double)ull) / 60.0 ); |
| else |
| printf(" = %" PRIu64 "", ull); |
| } |
| if (op->do_pcb) { |
| get_pcb_str(pcb, pcb_str, sizeof(pcb_str)); |
| printf("\n <%s>\n", pcb_str); |
| } else |
| printf("\n"); |
| if (op->filter_given) |
| break; |
| skip: |
| num -= pl; |
| ucp += pl; |
| } |
| return true; |
| } |
| |
| static void |
| show_ascii_page(const uint8_t * resp, int len, const struct opts_t * op) |
| { |
| int pg_code, subpg_code, spf; |
| bool done = false; |
| const struct log_elem * lep; |
| |
| if (len < 3) { |
| pr2serr("%s: response has bad length: %d\n", __func__, len); |
| return; |
| } |
| spf = !!(resp[0] & 0x40); |
| pg_code = resp[0] & 0x3f; |
| subpg_code = spf ? resp[1] : 0; |
| if ((SUPP_SPGS_SUBPG == subpg_code) && (SUPP_PAGES_LPAGE != pg_code)) { |
| done = show_supported_pgs_sub_page(resp, len, op); |
| if (done) |
| return; |
| } |
| lep = pg_subpg_pdt_search(pg_code, subpg_code, op->dev_pdt); |
| if (lep && lep->show_pagep) |
| done = (*lep->show_pagep)(resp, len, op); |
| |
| if (! done) { |
| if (spf) |
| printf("Unable to decode page = 0x%x, subpage = 0x%x, here is " |
| "hex:\n", pg_code, subpg_code); |
| else |
| printf("Unable to decode page = 0x%x, here is hex:\n", pg_code); |
| if (len > 128) { |
| dStrHex((const char *)resp, 64, 1); |
| printf(" ..... [truncated after 64 of %d bytes (use '-H' to " |
| "see the rest)]\n", len); |
| } |
| else |
| dStrHex((const char *)resp, len, 1); |
| } |
| } |
| |
| static int |
| fetchTemperature(int sg_fd, uint8_t * resp, int max_len, struct opts_t * op) |
| { |
| int len; |
| int res = 0; |
| |
| op->pg_code = TEMPERATURE_LPAGE; |
| op->subpg_code = NOT_SPG_SUBPG; |
| res = do_logs(sg_fd, resp, max_len, op); |
| if (0 == res) { |
| len = sg_get_unaligned_be16(resp + 2) + 4; |
| if (op->do_raw) |
| dStrRaw((const char *)resp, len); |
| else if (op->do_hex) |
| dStrHex((const char *)resp, len, (1 == op->do_hex)); |
| else |
| show_temperature_page(resp, len, op); |
| } else if (SG_LIB_CAT_NOT_READY == res) |
| pr2serr("Device not ready\n"); |
| else { |
| op->pg_code = IE_LPAGE; |
| res = do_logs(sg_fd, resp, max_len, op); |
| if (0 == res) { |
| len = sg_get_unaligned_be16(resp + 2) + 4; |
| if (op->do_raw) |
| dStrRaw((const char *)resp, len); |
| else if (op->do_hex) |
| dStrHex((const char *)resp, len, (1 == op->do_hex)); |
| else |
| show_ie_page(resp, len, op); |
| } else |
| pr2serr("Unable to find temperature in either Temperature or " |
| "IE log page\n"); |
| } |
| sg_cmds_close_device(sg_fd); |
| return (res >= 0) ? res : SG_LIB_CAT_OTHER; |
| } |
| |
| static int |
| decode_pg_arg(struct opts_t * op) |
| { |
| int n, nn; |
| const struct log_elem * lep; |
| char * cp; |
| char b[80]; |
| |
| if (isalpha(op->pg_arg[0])) { |
| if (strlen(op->pg_arg) >= (sizeof(b) - 1)) { |
| pr2serr("argument to '--page=' is too long\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| strcpy(b, op->pg_arg); |
| cp = (char *)strchr(b, ','); |
| if (cp) |
| *cp = '\0'; |
| lep = acron_search(b); |
| if (NULL == lep) { |
| pr2serr("bad argument to '--page=' no acronyn match to " |
| "'%s'\n", b); |
| pr2serr(" Try using '-e' or'-ee' to see available " |
| "acronyns\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->lep = lep; |
| op->pg_code = lep->pg_code; |
| if (cp) { |
| nn = sg_get_num_nomult(cp + 1); |
| if ((nn < 0) || (nn > 255)) { |
| pr2serr("Bad second value in argument to " |
| "'--page='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->subpg_code = nn; |
| } else |
| op->subpg_code = lep->subpg_code; |
| } else { /* numeric arg: either 'pg_num' or 'pg_num,subpg_num' */ |
| cp = (char *)strchr(op->pg_arg, ','); |
| n = sg_get_num_nomult(op->pg_arg); |
| if ((n < 0) || (n > 63)) { |
| pr2serr("Bad argument to '--page='\n"); |
| usage(1); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (cp) { |
| nn = sg_get_num_nomult(cp + 1); |
| if ((nn < 0) || (nn > 255)) { |
| pr2serr("Bad second value in argument to " |
| "'--page='\n"); |
| usage(1); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } else |
| nn = 0; |
| op->pg_code = n; |
| op->subpg_code = nn; |
| } |
| return 0; |
| } |
| |
| |
| int |
| main(int argc, char * argv[]) |
| { |
| int sg_fd, k, pg_len, res, resp_len; |
| int in_len = -1; |
| int ret = 0; |
| struct sg_simple_inquiry_resp inq_out; |
| struct opts_t opts; |
| struct opts_t * op; |
| |
| op = &opts; |
| memset(op, 0, sizeof(opts)); |
| memset(rsp_buff, 0, sizeof(rsp_buff)); |
| /* N.B. some disks only give data for current cumulative */ |
| op->page_control = 1; |
| op->dev_pdt = -1; |
| res = process_cl(op, argc, argv); |
| if (res) |
| return SG_LIB_SYNTAX_ERROR; |
| if (op->do_help) { |
| usage_for(op->do_help, op); |
| return 0; |
| } |
| if (op->do_version) { |
| pr2serr("Version string: %s\n", version_str); |
| return 0; |
| } |
| if ((op->do_enumerate > 0) || (op->do_enum_vendor > 0)) { |
| if (op->device_name && op->verbose) |
| pr2serr("Warning: device: %s is being ignored\n", |
| op->device_name); |
| if ((op->do_enum_vendor > 0) && (0 == op->do_enumerate)) |
| op->do_enumerate = 1; |
| enumerate_pages(op); |
| return 0; |
| } |
| |
| if (NULL == op->device_name) { |
| if (op->in_fn) { |
| const struct log_elem * lep; |
| const unsigned char * ucp; |
| int pg_code, subpg_code, pdt, n; |
| uint16_t u; |
| |
| if (f2hex_arr(op->in_fn, op->do_raw, 0, rsp_buff, &in_len, |
| sizeof(rsp_buff))) |
| return SG_LIB_FILE_ERROR; |
| if (op->do_raw) |
| op->do_raw = 0; /* can interfere on decode */ |
| if (in_len < 4) { |
| pr2serr("--in=%s only decoded %d bytes (needs 4 at least)\n", |
| op->in_fn, in_len); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (op->pg_arg && (0 == op->do_brief)) |
| pr2serr(">>> --page=%s option is being ignored, using values " |
| "in file: %s\n", op->pg_arg, op->in_fn); |
| for (ucp = rsp_buff, k = 0; k < in_len; ucp += n, k += n) { |
| pg_code = ucp[0] & 0x3f; |
| subpg_code = (ucp[0] & 0x40) ? ucp[1] : 0; |
| u = sg_get_unaligned_be16(ucp + 2); |
| n = u + 4; |
| if (n > (in_len - k)) { |
| pr2serr("bytes decoded remaining (%d) less than lpage " |
| "length (%d), try decoding anyway\n", in_len - k, |
| n); |
| n = in_len - k; |
| } |
| pdt = (op->filter_given && (op->filter >= 0)) ? |
| op->filter : -1; |
| op->dev_pdt = pdt; |
| lep = pg_subpg_pdt_search(pg_code, subpg_code, pdt); |
| if (lep) { |
| if (lep->show_pagep) |
| (*lep->show_pagep)(ucp, n, op); |
| else |
| printf("Unable to decode %s [%s]\n", lep->name, |
| lep->acron); |
| } else { |
| printf("Unable to decode page=0x%x", pg_code); |
| if (subpg_code > 0) |
| printf(", subpage=0x%x", subpg_code); |
| if (pdt >= 0) |
| printf(", pdt=0x%x\n", pdt); |
| else |
| printf("\n"); |
| } |
| } |
| return 0; |
| } |
| pr2serr("No DEVICE argument given\n"); |
| usage_for(1, op); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (op->do_select) { |
| if (op->do_temperature) { |
| pr2serr("--select cannot be used with --temperature\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (op->do_transport) { |
| pr2serr("--select cannot be used with --transport\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } else if (op->do_raw) { |
| if (sg_set_binary_mode(STDOUT_FILENO) < 0) { |
| perror("sg_set_binary_mode"); |
| return SG_LIB_FILE_ERROR; |
| } |
| } |
| if (op->do_all) { |
| if (op->do_select) { |
| pr2serr("--all conflicts with --select\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (op->filter) { |
| pr2serr("--all conflicts with --filter\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| if (op->in_fn) { |
| if (! op->do_select) { |
| pr2serr("--in=FN can only be used with --select when DEVICE " |
| "given\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (f2hex_arr(op->in_fn, op->do_raw, 0, rsp_buff, &in_len, |
| sizeof(rsp_buff))) |
| return SG_LIB_FILE_ERROR; |
| } |
| if (op->pg_arg) { |
| if (op->do_all) { |
| if (0 == op->do_brief) |
| pr2serr(">>> warning: --page=%s ignored when --all given\n", |
| op->pg_arg); |
| } else { |
| res = decode_pg_arg(op); |
| if (res) |
| return res; |
| } |
| } |
| |
| #ifdef SG_LIB_WIN32 |
| #ifdef SG_LIB_WIN32_DIRECT |
| win32_spt_init_state = scsi_pt_win32_spt_state(); |
| if (op->verbose > 4) |
| pr2serr("Initial win32 SPT interface state: %s\n", |
| win32_spt_init_state ? "direct" : "indirect"); |
| #endif |
| #endif |
| sg_fd = sg_cmds_open_device(op->device_name, op->o_readonly, |
| op->verbose); |
| if ((sg_fd < 0) && (0 == op->o_readonly)) |
| sg_fd = sg_cmds_open_device(op->device_name, 1 /* ro */, |
| op->verbose); |
| if (sg_fd < 0) { |
| pr2serr("error opening file: %s: %s \n", op->device_name, |
| safe_strerror(-sg_fd)); |
| return SG_LIB_FILE_ERROR; |
| } |
| if (op->do_list || op->do_all) { |
| op->pg_code = SUPP_PAGES_LPAGE; |
| if ((op->do_list > 1) || (op->do_all > 1)) |
| op->subpg_code = SUPP_SPGS_SUBPG; |
| } |
| if (op->do_transport) { |
| if ((op->pg_code > 0) || (op->subpg_code > 0) || |
| op->do_temperature) { |
| pr2serr("'-T' should not be mixed with options implying other " |
| "pages\n"); |
| return SG_LIB_FILE_ERROR; |
| } |
| op->pg_code = PROTO_SPECIFIC_LPAGE; |
| } |
| pg_len = 0; |
| |
| if (op->no_inq < 2) { |
| if (sg_simple_inquiry(sg_fd, &inq_out, 1, op->verbose)) { |
| pr2serr("%s doesn't respond to a SCSI INQUIRY\n", |
| op->device_name); |
| sg_cmds_close_device(sg_fd); |
| return SG_LIB_CAT_OTHER; |
| } |
| op->dev_pdt = inq_out.peripheral_type; |
| if ((0 == op->do_raw) && (0 == op->do_hex) && (0 == op->do_name) && |
| (0 == op->no_inq) && (0 == op->do_brief)) |
| printf(" %.8s %.16s %.4s\n", inq_out.vendor, |
| inq_out.product, inq_out.revision); |
| } else |
| memset(&inq_out, 0, sizeof(inq_out)); |
| |
| if (1 == op->do_temperature) |
| return fetchTemperature(sg_fd, rsp_buff, SHORT_RESP_LEN, op); |
| |
| if (op->do_select) { |
| k = sg_ll_log_select(sg_fd, !!(op->do_pcreset), op->do_sp, |
| op->page_control, op->pg_code, op->subpg_code, |
| rsp_buff, ((in_len > 0) ? in_len : 0), |
| 1, op->verbose); |
| if (k) { |
| if (SG_LIB_CAT_NOT_READY == k) |
| pr2serr("log_select: device not ready\n"); |
| else if (SG_LIB_CAT_ILLEGAL_REQ == res) |
| pr2serr("log_select: field in cdb illegal\n"); |
| else if (SG_LIB_CAT_INVALID_OP == k) |
| pr2serr("log_select: not supported\n"); |
| else if (SG_LIB_CAT_UNIT_ATTENTION == k) |
| pr2serr("log_select: unit attention\n"); |
| else if (SG_LIB_CAT_ABORTED_COMMAND == k) |
| pr2serr("log_select: aborted command\n"); |
| else |
| pr2serr("log_select: failed (%d), try '-v' for more " |
| "information\n", k); |
| } |
| return (k >= 0) ? k : SG_LIB_CAT_OTHER; |
| } |
| resp_len = (op->maxlen > 0) ? op->maxlen : MX_ALLOC_LEN; |
| res = do_logs(sg_fd, rsp_buff, resp_len, op); |
| if (0 == res) { |
| pg_len = sg_get_unaligned_be16(rsp_buff + 2); |
| if ((pg_len + 4) > resp_len) { |
| pr2serr("Only fetched %d bytes of response (available: %d " |
| "bytes)\n truncate output\n", |
| resp_len, pg_len + 4); |
| pg_len = resp_len - 4; |
| } |
| } else if (SG_LIB_CAT_INVALID_OP == res) |
| pr2serr("log_sense: not supported\n"); |
| else if (SG_LIB_CAT_NOT_READY == res) |
| pr2serr("log_sense: device not ready\n"); |
| else if (SG_LIB_CAT_ILLEGAL_REQ == res) |
| pr2serr("log_sense: field in cdb illegal\n"); |
| else if (SG_LIB_CAT_UNIT_ATTENTION == res) |
| pr2serr("log_sense: unit attention\n"); |
| else if (SG_LIB_CAT_ABORTED_COMMAND == res) |
| pr2serr("log_sense: aborted command\n"); |
| if (0 == op->do_all) { |
| if (op->filter_given) { |
| if (op->do_hex > 2) |
| dStrHex((const char *)rsp_buff, pg_len + 4, |
| (op->do_hex < 4)); |
| else |
| show_ascii_page(rsp_buff, pg_len + 4, op); |
| } else if (op->do_raw) |
| dStrRaw((const char *)rsp_buff, pg_len + 4); |
| else if (op->do_hex > 1) |
| dStrHex((const char *)rsp_buff, pg_len + 4, |
| (2 == op->do_hex) ? 0 : -1); |
| else if (pg_len > 1) { |
| if (op->do_hex) { |
| if (rsp_buff[0] & 0x40) |
| printf("Log page code=0x%x,0x%x, DS=%d, SPF=1, " |
| "page_len=0x%x\n", rsp_buff[0] & 0x3f, rsp_buff[1], |
| !!(rsp_buff[0] & 0x80), pg_len); |
| else |
| printf("Log page code=0x%x, DS=%d, SPF=0, page_len=0x%x\n", |
| rsp_buff[0] & 0x3f, !!(rsp_buff[0] & 0x80), pg_len); |
| dStrHex((const char *)rsp_buff, pg_len + 4, 1); |
| } |
| else |
| show_ascii_page(rsp_buff, pg_len + 4, op); |
| } |
| } |
| ret = res; |
| |
| if (op->do_all && (pg_len > 1)) { |
| int my_len = pg_len; |
| int spf; |
| uint8_t parr[1024]; |
| |
| spf = !!(rsp_buff[0] & 0x40); |
| if (my_len > (int)sizeof(parr)) { |
| pr2serr("Unexpectedly large page_len=%d, trim to %d\n", my_len, |
| (int)sizeof(parr)); |
| my_len = sizeof(parr); |
| } |
| memcpy(parr, rsp_buff + 4, my_len); |
| for (k = 0; k < my_len; ++k) { |
| if (0 == op->do_raw) |
| printf("\n"); |
| op->pg_code = parr[k] & 0x3f; |
| if (spf) |
| op->subpg_code = parr[++k]; |
| else |
| op->subpg_code = NOT_SPG_SUBPG; |
| |
| res = do_logs(sg_fd, rsp_buff, resp_len, op); |
| if (0 == res) { |
| pg_len = sg_get_unaligned_be16(rsp_buff + 2); |
| if ((pg_len + 4) > resp_len) { |
| pr2serr("Only fetched %d bytes of response, truncate " |
| "output\n", resp_len); |
| pg_len = resp_len - 4; |
| } |
| if (op->do_raw) |
| dStrRaw((const char *)rsp_buff, pg_len + 4); |
| else if (op->do_hex > 1) |
| dStrHex((const char *)rsp_buff, pg_len + 4, |
| (2 == op->do_hex) ? 0 : -1); |
| else if (op->do_hex) { |
| if (rsp_buff[0] & 0x40) |
| printf("Log page code=0x%x,0x%x, DS=%d, SPF=1, page_" |
| "len=0x%x\n", rsp_buff[0] & 0x3f, rsp_buff[1], |
| !!(rsp_buff[0] & 0x80), pg_len); |
| else |
| printf("Log page code=0x%x, DS=%d, SPF=0, page_len=" |
| "0x%x\n", rsp_buff[0] & 0x3f, |
| !!(rsp_buff[0] & 0x80), pg_len); |
| dStrHex((const char *)rsp_buff, pg_len + 4, 1); |
| } |
| else |
| show_ascii_page(rsp_buff, pg_len + 4, op); |
| } else if (SG_LIB_CAT_INVALID_OP == res) |
| pr2serr("log_sense: page=0x%x,0x%x not supported\n", |
| op->pg_code, op->subpg_code); |
| else if (SG_LIB_CAT_NOT_READY == res) |
| pr2serr("log_sense: device not ready\n"); |
| else if (SG_LIB_CAT_ILLEGAL_REQ == res) |
| pr2serr("log_sense: field in cdb illegal " |
| "[page=0x%x,0x%x]\n", op->pg_code, op->subpg_code); |
| else if (SG_LIB_CAT_UNIT_ATTENTION == res) |
| pr2serr("log_sense: unit attention\n"); |
| else if (SG_LIB_CAT_ABORTED_COMMAND == res) |
| pr2serr("log_sense: aborted command\n"); |
| else |
| pr2serr("log_sense: failed, try '-v' for more information\n"); |
| } |
| } |
| sg_cmds_close_device(sg_fd); |
| return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; |
| } |