/* * Copyright (c) 2006-2022 Douglas Gilbert. * All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the BSD_LICENSE file. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #define __STDC_FORMAT_MACROS 1 #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "sg_lib.h" #include "sg_lib_data.h" #include "sg_cmds_basic.h" #include "sg_unaligned.h" #include "sg_pr2serr.h" #include "sg_vpd_common.h" /* This file holds common code for sg_inq and sg_vpd as both those utilities * decode SCSI VPD pages. */ const char * t10_vendor_id_hr = "T10_vendor_identification"; const char * t10_vendor_id_js = "t10_vendor_identification"; const char * product_id_hr = "Product_identification"; const char * product_id_js = "product_identification"; const char * product_rev_lev_hr = "Product_revision_level"; const char * product_rev_lev_js = "product_revision_level"; static const char * const y_s = "yes"; static const char * const n_s = "no"; static const char * const nl_s = "no limit"; static const char * const nlr_s = "no limit reported"; /* Earlier gcc compilers (e.g. 6.4) don't accept this first form when it is * used in another array of strings initialization (e.g. bdc_zoned_strs) */ // static const char * const nr_s = "not reported"; static char nr_s[] = "not reported"; static const char * const ns_s = "not supported"; // static const char * const rsv_s = "Reserved"; static char rsv_s[] = "Reserved"; static const char * const vs_s = "Vendor specific"; static const char * const null_s = ""; static const char * const mn_s = "meaning"; /* Supported vendor specific VPD pages */ /* Arrange in alphabetical order by acronym */ struct svpd_vp_name_t vp_arr[] = { {VPD_VP_DDS, "dds", "DDS tape family from IBM"}, {VPD_VP_EMC, "emc", "EMC (company)"}, {VPD_VP_WDC_HITACHI, "hit", "WDC/Hitachi disk"}, {VPD_VP_HP3PAR, "hp3par", "3PAR array (HP was Left Hand)"}, {VPD_VP_HP_LTO, "hp_lto", "HP LTO tape/systems"}, {VPD_VP_IBM_LTO, "ibm_lto", "IBM LTO tape/systems"}, {VPD_VP_NVME, "nvme", "NVMe related"}, {VPD_VP_RDAC, "rdac", "RDAC array (NetApp E-Series)"}, {VPD_VP_SEAGATE, "sea", "Seagate disk"}, {VPD_VP_SG, "sg", "sg3_utils extensions"}, {VPD_VP_WDC_HITACHI, "wdc", "WDC/Hitachi disk"}, {0, NULL, NULL}, }; /* Supported vendor specific VPD pages */ /* 'subvalue' holds vendor/product number to disambiguate */ /* Arrange in alphabetical order by acronym */ struct svpd_values_name_t vendor_vpd_pg[] = { {VPD_V_ACI_LTO, VPD_VP_HP_LTO, 1, "aci", "ACI revision level (HP LTO)"}, {VPD_V_DATC_SEA, VPD_VP_SEAGATE, 0, "datc", "Date code (Seagate)"}, {VPD_V_DCRL_LTO, VPD_VP_IBM_LTO, 1, "dcrl", "Drive component revision " "levels (IBM LTO)"}, {VPD_V_FVER_DDS, VPD_VP_DDS, 1, "ddsver", "Firmware revision (DDS)"}, {VPD_V_DEV_BEH_SEA, VPD_VP_SEAGATE, 0, "devb", "Device behavior " "(Seagate)"}, {VPD_V_DSN_LTO, VPD_VP_IBM_LTO, 1, "dsn", "Drive serial numbers (IBM " "LTO)"}, {VPD_V_DUCD_LTO, VPD_VP_IBM_LTO, 1, "ducd", "Device unique " "configuration data (IBM LTO)"}, {VPD_V_EDID_RDAC, VPD_VP_RDAC, 0, "edid", "Extended device " "identification (RDAC)"}, {VPD_V_FIRM_SEA, VPD_VP_SEAGATE, 0, "firm", "Firmware numbers " "(Seagate)"}, {VPD_V_FVER_LTO, VPD_VP_HP_LTO, 0, "frl", "Firmware revision level " "(HP LTO)"}, {VPD_V_FVER_RDAC, VPD_VP_RDAC, 0, "fwr4", "Firmware version (RDAC)"}, {VPD_V_HEAD_LTO, VPD_VP_HP_LTO, 1, "head", "Head Assy revision level " "(HP LTO)"}, {VPD_V_HP3PAR, VPD_VP_HP3PAR, 0, "hp3par", "Volume information " "(HP/3PAR)"}, {VPD_V_HVER_LTO, VPD_VP_HP_LTO, 1, "hrl", "Hardware revision level " "(HP LTO)"}, {VPD_V_HVER_RDAC, VPD_VP_RDAC, 0, "hwr4", "Hardware version (RDAC)"}, {VPD_V_JUMP_SEA, VPD_VP_SEAGATE, 0, "jump", "Jump setting (Seagate)"}, {VPD_V_MECH_LTO, VPD_VP_HP_LTO, 1, "mech", "Mechanism revision level " "(HP LTO)"}, {VPD_V_MPDS_LTO, VPD_VP_IBM_LTO, 1, "mpds", "Mode parameter default " "settings (IBM LTO)"}, {SG_NVME_VPD_NICR, VPD_VP_SG, 0, "nicr", "NVMe Identify Controller Response (sg3_utils)"}, {VPD_V_PCA_LTO, VPD_VP_HP_LTO, 1, "pca", "PCA revision level (HP LTO)"}, {VPD_V_FEAT_RDAC, VPD_VP_RDAC, 0, "prm4", "Feature Parameters (RDAC)"}, {VPD_V_RVSI_RDAC, VPD_VP_RDAC, 0, "rvsi", "Replicated volume source " "identifier (RDAC)"}, {VPD_V_SAID_RDAC, VPD_VP_RDAC, 0, "said", "Storage array world wide " "name (RDAC)"}, {VPD_V_SUBS_RDAC, VPD_VP_RDAC, 0, "subs", "Subsystem identifier (RDAC)"}, {VPD_V_SVER_RDAC, VPD_VP_RDAC, 0, "swr4", "Software version (RDAC)"}, {VPD_V_UPR_EMC, VPD_VP_EMC, 0, "upr", "Unit path report (EMC)"}, {VPD_V_VAC_RDAC, VPD_VP_RDAC, 0, "vac1", "Volume access control (RDAC)"}, {VPD_V_HIT_PG3, VPD_VP_WDC_HITACHI, 0, "wp3", "Page 0x3 (WDC/Hitachi)"}, {VPD_V_HIT_PG_D1, VPD_VP_WDC_HITACHI, 0, "wpd1", "Page 0xd1 (WDC/Hitachi)"}, {VPD_V_HIT_PG_D2, VPD_VP_WDC_HITACHI, 0, "wpd2", "Page 0xd2 (WDC/Hitachi)"}, {0, 0, 0, NULL, NULL}, }; int no_ascii_4hex(const struct opts_t * op) { if (op->do_hex < 2) return 1; else if (2 == op->do_hex) return 0; else return -1; } int svpd_find_vp_num_by_acron(const char * vp_ap) { size_t len; const struct svpd_vp_name_t * vpp; for (vpp = vp_arr; vpp->acron; ++vpp) { len = strlen(vpp->acron); if (0 == strncmp(vpp->acron, vp_ap, len)) return vpp->vend_prod_num; } return -1; } /* if vend_prod_num < -1 then list vendor_product ids + vendor pages, =-1 * list only vendor_product ids, else list pages for that vend_prod_num */ void svpd_enumerate_vendor(int vend_prod_num) { bool seen; const struct svpd_vp_name_t * vpp; const struct svpd_values_name_t * vnp; if (vend_prod_num < 0) { for (seen = false, vpp = vp_arr; vpp->acron; ++vpp) { if (vpp->name) { if (! seen) { printf("\nVendor/product identifiers:\n"); seen = true; } printf(" %-10s %d %s\n", vpp->acron, vpp->vend_prod_num, vpp->name); } } } if (-1 == vend_prod_num) return; for (seen = false, vnp = vendor_vpd_pg; vnp->acron; ++vnp) { if ((vend_prod_num >= 0) && (vend_prod_num != vnp->subvalue)) continue; if (vnp->name) { if (! seen) { printf("\nVendor specific VPD pages:\n"); seen = true; } printf(" %-10s 0x%02x,%d %s\n", vnp->acron, vnp->value, vnp->subvalue, vnp->name); } } } /* mxlen is command line --maxlen=LEN option (def: 0) or -1 for a VPD page * with a short length (1 byte). Returns 0 for success. */ int /* global: use by sg_vpd_vendor.c */ vpd_fetch_page(int sg_fd, uint8_t * rp, int page, int mxlen, bool qt, int vb, int * rlenp) { int res, resid, rlen, len, n; if (sg_fd < 0) { len = sg_get_unaligned_be16(rp + 2) + 4; if (vb && (len > mxlen)) pr2serr("warning: VPD page's length (%d) > bytes in --inhex=FN " "file (%d)\n", len , mxlen); if (rlenp) *rlenp = (len < mxlen) ? len : mxlen; return 0; } if (mxlen > MX_ALLOC_LEN) { pr2serr("--maxlen=LEN too long: %d > %d\n", mxlen, MX_ALLOC_LEN); return SG_LIB_SYNTAX_ERROR; } n = (mxlen > 0) ? mxlen : DEF_ALLOC_LEN; res = sg_ll_inquiry_v2(sg_fd, true, page, rp, n, DEF_PT_TIMEOUT, &resid, ! qt, vb); if (res) return res; rlen = n - resid; if (rlen < 4) { pr2serr("VPD response too short (len=%d)\n", rlen); return SG_LIB_CAT_MALFORMED; } if (page != rp[1]) { pr2serr("invalid VPD response; probably a STANDARD INQUIRY " "response\n"); n = (rlen < 32) ? rlen : 32; if (vb) { pr2serr("First %d bytes of bad response\n", n); hex2stderr(rp, n, 0); } return SG_LIB_CAT_MALFORMED; } else if ((0x80 == page) && (0x2 == rp[2]) && (0x2 == rp[3])) { /* could be a Unit Serial number VPD page with a very long * length of 4+514 bytes; more likely standard response for * SCSI-2, RMB=1 and a response_data_format of 0x2. */ pr2serr("invalid Unit Serial Number VPD response; probably a " "STANDARD INQUIRY response\n"); return SG_LIB_CAT_MALFORMED; } if (mxlen < 0) len = rp[3] + 4; else len = sg_get_unaligned_be16(rp + 2) + 4; if (len <= rlen) { if (rlenp) *rlenp = len; return 0; } else if (mxlen) { if (rlenp) *rlenp = rlen; return 0; } if (len > MX_ALLOC_LEN) { pr2serr("response length too long: %d > %d\n", len, MX_ALLOC_LEN); return SG_LIB_CAT_MALFORMED; } else { res = sg_ll_inquiry_v2(sg_fd, true, page, rp, len, DEF_PT_TIMEOUT, &resid, ! qt, vb); if (res) return res; rlen = len - resid; /* assume it is well behaved: hence page and len still same */ if (rlenp) *rlenp = rlen; return 0; } } sgj_opaque_p sg_vpd_js_hdr(sgj_state * jsp, sgj_opaque_p jop, const char * name, const uint8_t * vpd_hdrp) { int pdt = vpd_hdrp[0] & PDT_MASK; int pqual = (vpd_hdrp[0] & 0xe0) >> 5; int pn = vpd_hdrp[1]; const char * pdt_str; sgj_opaque_p jo2p = sgj_snake_named_subobject_r(jsp, jop, name); char d[64]; pdt_str = sg_get_pdt_str(pdt, sizeof(d), d); sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_qualifier", pqual, NULL, pqual_str(pqual)); sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_device_type", pdt, NULL, pdt_str); sgj_js_nv_ihex(jsp, jo2p, "page_code", pn); return jo2p; } const char * pqual_str(int pqual) { switch (pqual) { case 0: return "LU accessible"; case 1: return "LU temporarily unavailable"; case 3: return "LU not accessible via this port"; default: return "value reserved by T10"; } } static const char * network_service_type_arr[] = { "unspecified", "storage configuration service", "diagnostics", "status", "logging", "code download", "copy service", "administrative configuration service", "reserved[0x8]", "reserved[0x9]", "reserved[0xa]", "reserved[0xb]", "reserved[0xc]", "reserved[0xd]", "reserved[0xe]", "reserved[0xf]", "reserved[0x10]", "reserved[0x11]", "reserved[0x12]", "reserved[0x13]", "reserved[0x14]", "reserved[0x15]", "reserved[0x16]", "reserved[0x17]", "reserved[0x18]", "reserved[0x19]", "reserved[0x1a]", "reserved[0x1b]", "reserved[0x1c]", "reserved[0x1d]", "reserved[0x1e]", "reserved[0x1f]", }; /* VPD_MAN_NET_ADDR 0x85 ["mna"] */ void decode_net_man_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k, bump, na_len, assoc, nst; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p; const uint8_t * bp; const char * assoc_str; const char * nst_str; if ((1 == op->do_hex) || (op->do_hex > 2)) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } if (len < 4) { pr2serr("Management network addresses VPD page length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0; k < len; k += bump, bp += bump) { assoc = (bp[0] >> 5) & 0x3; assoc_str = sg_get_desig_assoc_str(assoc); nst = bp[0] & 0x1f; nst_str = network_service_type_arr[nst]; sgj_pr_hr(jsp, " %s, Service type: %s\n", assoc_str, nst_str); na_len = sg_get_unaligned_be16(bp + 2); if (jsp->pr_as_json) { jo2p = sgj_new_unattached_object_r(jsp); sgj_js_nv_ihexstr(jsp, jo2p, "association", assoc, NULL, assoc_str); sgj_js_nv_ihexstr(jsp, jo2p, "service_type", nst, NULL, nst_str); sgj_js_nv_s_len(jsp, jo2p, "network_address", (const char *)(bp + 4), na_len); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } if (na_len > 0) { if (op->do_hex > 1) { sgj_pr_hr(jsp, " Network address:\n"); hex2stdout((bp + 4), na_len, 0); } else sgj_pr_hr(jsp, " %s\n", bp + 4); } bump = 4 + na_len; if ((k + bump) > len) { pr2serr("Management network addresses VPD page, short " "descriptor length=%d, left=%d\n", bump, (len - k)); return; } } } /* VPD_EXT_INQ Extended Inquiry VPD ["ei"] */ void decode_x_inq_vpd(const uint8_t * b, int len, bool protect, struct opts_t * op, sgj_opaque_p jop) { bool do_long_nq = op->do_long && (! op->do_quiet); int n; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p; const char * cp; const char * np; const char * nex_p; char d[128]; static const int dlen = sizeof(d); if (len < 7) { pr2serr("Extended INQUIRY data VPD page length too short=%d\n", len); return; } if (op->do_hex) { hex2stdout(b, len, (1 == op->do_hex) ? 0 : -1); return; } if (do_long_nq || jsp->pr_as_json) { n = (b[4] >> 6) & 0x3; if (1 == n) cp = "before final WRITE BUFFER"; else if (2 == n) cp = "after power on or hard reset"; else { cp = "none"; d[0] = '\0'; } if (cp[0]) snprintf(d, dlen, " [%s]", cp); sgj_pr_hr(jsp, " ACTIVATE_MICROCODE=%d%s\n", n, d); sgj_js_nv_ihexstr(jsp, jop, "activate_microcode", n, NULL, cp); n = (b[4] >> 3) & 0x7; if (protect) { switch (n) { case 0: cp = "protection type 1 supported"; break; case 1: cp = "protection types 1 and 2 supported"; break; case 2: cp = "protection type 2 supported"; break; case 3: cp = "protection types 1 and 3 supported"; break; case 4: cp = "protection type 3 supported"; break; case 5: cp = "protection types 2 and 3 supported"; break; case 6: cp = "see Supported block lengths and protection types " "VPD page"; break; case 7: cp = "protection types 1, 2 and 3 supported"; break; } } else if (op->protect_not_sure) { cp = "Unsure because unable to read PROTECT bit in standard " "INQUIRY response"; d[0] = '\0'; } else { cp = "none"; d[0] = '\0'; } if (cp[0]) snprintf(d, dlen, " [%s]", cp); sgj_pr_hr(jsp, " SPT=%d%s\n", n, d); sgj_js_nv_ihexstr_nex(jsp, jop, "spt", n, false, NULL, cp, "Supported Protection Type"); sgj_haj_vi_nex(jsp, jop, 2, "GRD_CHK", SGJ_SEP_EQUAL_NO_SPACE, !!(b[4] & 0x4), false, "guard check"); sgj_haj_vi_nex(jsp, jop, 2, "APP_CHK", SGJ_SEP_EQUAL_NO_SPACE, !!(b[4] & 0x2), false, "application tag check"); sgj_haj_vi_nex(jsp, jop, 2, "REF_CHK", SGJ_SEP_EQUAL_NO_SPACE, !!(b[4] & 0x1), false, "reference tag check"); sgj_haj_vi_nex(jsp, jop, 2, "UASK_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[5] & 0x20), false, "Unit Attention " "condition Sense Key specific data Supported"); sgj_haj_vi_nex(jsp, jop, 2, "GROUP_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[5] & 0x10), false, "grouping function supported"); sgj_haj_vi_nex(jsp, jop, 2, "PRIOR_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[5] & 0x8), false, "priority supported"); sgj_haj_vi_nex(jsp, jop, 2, "HEADSUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[5] & 0x4), false, "head of queue supported"); sgj_haj_vi_nex(jsp, jop, 2, "ORDSUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[5] & 0x2), false, "ordered (task attribute) " "supported"); sgj_haj_vi_nex(jsp, jop, 2, "SIMPSUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[5] & 0x1), false, "simple (task attribute) " "supported"); sgj_haj_vi_nex(jsp, jop, 2, "WU_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[6] & 0x8), false, "Write uncorrectable " "supported"); sgj_haj_vi_nex(jsp, jop, 2, "CRD_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[6] & 0x4), false, "Correction disable " "supported (obsolete SPC-5)"); sgj_haj_vi_nex(jsp, jop, 2, "NV_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[6] & 0x2), false, "Nonvolatile cache " "supported"); sgj_haj_vi_nex(jsp, jop, 2, "V_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[6] & 0x1), false, "Volatile cache supported"); sgj_haj_vi_nex(jsp, jop, 2, "NO_PI_CHK", SGJ_SEP_EQUAL_NO_SPACE, !!(b[7] & 0x20), false, "No protection " "information checking"); /* spc5r02 */ sgj_haj_vi_nex(jsp, jop, 2, "P_I_I_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[7] & 0x10), false, "Protection information " "interval supported"); sgj_haj_vi_nex(jsp, jop, 2, "LUICLR", SGJ_SEP_EQUAL_NO_SPACE, !!(b[7] & 0x1), false, "Logical unit I_T nexus clear"); np = "LU_COLL_TYPE"; n = (b[8] >> 5) & 0x7; nex_p = "Logical unit collection type"; if (jsp && (jsp->pr_string)) { switch (n) { case 0: cp = "not reported"; break; case 1: cp = "Conglomerate"; break; case 2: cp = "Logical unit group"; break; default: cp = rsv_s; break; } jo2p = sgj_haj_subo_r(jsp, jop, 2, np, SGJ_SEP_EQUAL_NO_SPACE, n, false); sgj_js_nv_s(jsp, jo2p, mn_s, cp); if (jsp->pr_name_ex) sgj_js_nv_s(jsp, jo2p, "abbreviated_name_expansion", nex_p); } else sgj_haj_vi_nex(jsp, jop, 2, np, SGJ_SEP_EQUAL_NO_SPACE, n, true, nex_p); sgj_haj_vi_nex(jsp, jop, 2, "R_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[8] & 0x10), false, "Referrals supported"); sgj_haj_vi_nex(jsp, jop, 2, "RTD_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[8] & 0x8), false, "Revert to defaults supported"); sgj_haj_vi_nex(jsp, jop, 2, "HSSRELEF", SGJ_SEP_EQUAL_NO_SPACE, !!(b[8] & 0x2), false, "History snapshots release effects"); sgj_haj_vi_nex(jsp, jop, 2, "CBCS", SGJ_SEP_EQUAL_NO_SPACE, !!(b[8] & 0x1), false, "Capability-based command " "security (obsolete SPC-5)"); sgj_haj_vi(jsp, jop, 2, "Multi I_T nexus microcode download", SGJ_SEP_EQUAL_NO_SPACE, b[9] & 0xf, true); sgj_haj_vi(jsp, jop, 2, "Extended self-test completion minutes", SGJ_SEP_EQUAL_NO_SPACE, sg_get_unaligned_be16(b + 10), true); sgj_haj_vi_nex(jsp, jop, 2, "POA_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[12] & 0x80), false, "Power on activation supported"); sgj_haj_vi_nex(jsp, jop, 2, "HRA_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[12] & 0x40), false, "Hard reset activation supported"); sgj_haj_vi_nex(jsp, jop, 2, "VSA_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(b[12] & 0x20), false, "Vendor specific activation supported"); sgj_haj_vi_nex(jsp, jop, 2, "DMS_VALID", SGJ_SEP_EQUAL_NO_SPACE, !!(b[12] & 0x10), false, "Download microcode support byte valid"); sgj_haj_vi(jsp, jop, 2, "Maximum supported sense data length", SGJ_SEP_EQUAL_NO_SPACE, b[13], true); sgj_haj_vi_nex(jsp, jop, 2, "IBS", SGJ_SEP_EQUAL_NO_SPACE, !!(b[14] & 0x80), false, "Implicit bind supported"); sgj_haj_vi_nex(jsp, jop, 2, "IAS", SGJ_SEP_EQUAL_NO_SPACE, !!(b[14] & 0x40), false, "Implicit affiliation supported"); sgj_haj_vi_nex(jsp, jop, 2, "SAC", SGJ_SEP_EQUAL_NO_SPACE, !!(b[14] & 0x4), false, "Set affiliation command supported"); sgj_haj_vi_nex(jsp, jop, 2, "NRD1", SGJ_SEP_EQUAL_NO_SPACE, !!(b[14] & 0x2), false, "No redirect one supported (BIND)"); sgj_haj_vi_nex(jsp, jop, 2, "NRD0", SGJ_SEP_EQUAL_NO_SPACE, !!(b[14] & 0x1), false, "No redirect zero supported (BIND)"); sgj_haj_vi(jsp, jop, 2, "Maximum inquiry change logs", SGJ_SEP_EQUAL_NO_SPACE, sg_get_unaligned_be16(b + 15), true); sgj_haj_vi(jsp, jop, 2, "Maximum mode page change logs", SGJ_SEP_EQUAL_NO_SPACE, sg_get_unaligned_be16(b + 17), true); sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_4", SGJ_SEP_EQUAL_NO_SPACE, !!(b[19] & 0x80), false, "Download microcode mode 4 supported"); sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_5", SGJ_SEP_EQUAL_NO_SPACE, !!(b[19] & 0x40), false, "Download microcode mode 5 supported"); sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_6", SGJ_SEP_EQUAL_NO_SPACE, !!(b[19] & 0x20), false, "Download microcode mode 6 supported"); sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_7", SGJ_SEP_EQUAL_NO_SPACE, !!(b[19] & 0x10), false, "Download microcode mode 7 supported"); sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_D", SGJ_SEP_EQUAL_NO_SPACE, !!(b[19] & 0x8), false, "Download microcode mode 0xd supported"); sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_E", SGJ_SEP_EQUAL_NO_SPACE, !!(b[19] & 0x4), false, "Download microcode mode 0xe supported"); sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_F", SGJ_SEP_EQUAL_NO_SPACE, !!(b[19] & 0x2), false, "Download microcode mode 0xf supported"); if (do_long_nq || (! jsp->pr_out_hr)) return; } sgj_pr_hr(jsp, " ACTIVATE_MICROCODE=%d SPT=%d GRD_CHK=%d APP_CHK=%d " "REF_CHK=%d\n", ((b[4] >> 6) & 0x3), ((b[4] >> 3) & 0x7), !!(b[4] & 0x4), !!(b[4] & 0x2), !!(b[4] & 0x1)); sgj_pr_hr(jsp, " UASK_SUP=%d GROUP_SUP=%d PRIOR_SUP=%d HEADSUP=%d " "ORDSUP=%d SIMPSUP=%d\n", !!(b[5] & 0x20), !!(b[5] & 0x10), !!(b[5] & 0x8), !!(b[5] & 0x4), !!(b[5] & 0x2), !!(b[5] & 0x1)); sgj_pr_hr(jsp, " WU_SUP=%d [CRD_SUP=%d] NV_SUP=%d V_SUP=%d\n", !!(b[6] & 0x8), !!(b[6] & 0x4), !!(b[6] & 0x2), !!(b[6] & 0x1)); sgj_pr_hr(jsp, " NO_PI_CHK=%d P_I_I_SUP=%d LUICLR=%d\n", !!(b[7] & 0x20), !!(b[7] & 0x10), !!(b[7] & 0x1)); /* RTD_SUP added in spc5r11, LU_COLL_TYPE added in spc5r09, * HSSRELEF added in spc5r02; CBCS obsolete in spc5r01 */ sgj_pr_hr(jsp, " LU_COLL_TYPE=%d R_SUP=%d RTD_SUP=%d HSSRELEF=%d " "[CBCS=%d]\n", (b[8] >> 5) & 0x7, !!(b[8] & 0x10), !!(b[8] & 0x8), !!(b[8] & 0x2), !!(b[8] & 0x1)); sgj_pr_hr(jsp, " Multi I_T nexus microcode download=%d\n", b[9] & 0xf); sgj_pr_hr(jsp, " Extended self-test completion minutes=%d\n", sg_get_unaligned_be16(b + 10)); /* spc4r27 */ sgj_pr_hr(jsp, " POA_SUP=%d HRA_SUP=%d VSA_SUP=%d DMS_VALID=%d\n", !!(b[12] & 0x80), !!(b[12] & 0x40), !!(b[12] & 0x20), !!(b[12] & 0x10)); /* spc5r20 */ sgj_pr_hr(jsp, " Maximum supported sense data length=%d\n", b[13]); /* spc4r34 */ sgj_pr_hr(jsp, " IBS=%d IAS=%d SAC=%d NRD1=%d NRD0=%d\n", !!(b[14] & 0x80), !!(b[14] & 0x40), !!(b[14] & 0x4), !!(b[14] & 0x2), !!(b[14] & 0x1)); /* added in spc5r09 */ sgj_pr_hr(jsp, " Maximum inquiry change logs=%u\n", sg_get_unaligned_be16(b + 15)); /* spc5r17 */ sgj_pr_hr(jsp, " Maximum mode page change logs=%u\n", sg_get_unaligned_be16(b + 17)); /* spc5r17 */ sgj_pr_hr(jsp, " DM_MD_4=%d DM_MD_5=%d DM_MD_6=%d DM_MD_7=%d\n", !!(b[19] & 0x80), !!(b[19] & 0x40), !!(b[19] & 0x20), !!(b[19] & 0x10)); /* spc5r20 */ sgj_pr_hr(jsp, " DM_MD_D=%d DM_MD_E=%d DM_MD_F=%d\n", !!(b[19] & 0x8), !!(b[19] & 0x4), !!(b[19] & 0x2)); } /* VPD_SOFTW_INF_ID 0x84 */ void decode_softw_inf_id(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { sgj_state * jsp = &op->json_st; sgj_opaque_p jop; uint64_t ieee_id; if (op->do_hex) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } len -= 4; buff += 4; for ( ; len > 5; len -= 6, buff += 6) { ieee_id = sg_get_unaligned_be48(buff + 0); sgj_pr_hr(jsp, " IEEE identifier: 0x%" PRIx64 "\n", ieee_id); if (jsp->pr_as_json) { jop = sgj_new_unattached_object_r(jsp); sgj_js_nv_ihex(jsp, jop, "ieee_identifier", ieee_id); sgj_js_nv_o(jsp, jap, NULL /* name */, jop); } } } static const char * mode_page_policy_arr[] = { "shared", "per target port", "per initiator port", "per I_T nexus", }; /* VPD_MODE_PG_POLICY 0x87 ["mpp"] */ void decode_mode_policy_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k, n, bump, ppc, pspc; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p; const uint8_t * bp; char b[128]; static const int blen = sizeof(b); if ((1 == op->do_hex) || (op->do_hex > 2)) { hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1); return; } if (len < 4) { pr2serr("Mode page policy VPD page length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0; k < len; k += bump, bp += bump) { bump = 4; if ((k + bump) > len) { pr2serr("Mode page policy VPD page, short " "descriptor length=%d, left=%d\n", bump, (len - k)); return; } if (op->do_hex > 1) hex2stdout(bp, 4, 1); else { n = 0; ppc = (bp[0] & 0x3f); pspc = bp[1]; n = sg_scnpr(b + n, blen - n, " Policy page code: 0x%x", ppc); if (pspc) n += sg_scnpr(b + n, blen - n, ", subpage code: 0x%x", pspc); sgj_pr_hr(jsp, "%s\n", b); if ((0 == k) && (0x3f == (0x3f & bp[0])) && (0xff == bp[1])) sgj_pr_hr(jsp, " therefore the policy applies to all modes " "pages and subpages\n"); sgj_pr_hr(jsp, " MLUS=%d, Policy: %s\n", !!(bp[2] & 0x80), mode_page_policy_arr[bp[2] & 0x3]); if (jsp->pr_as_json) { jo2p = sgj_new_unattached_object_r(jsp); sgj_js_nv_ihex(jsp, jo2p, "policy_page_code", ppc); sgj_js_nv_ihex(jsp, jo2p, "policy_subpage_code", pspc); sgj_js_nv_ihex_nex(jsp, jo2p, "mlus", !!(bp[2] & 0x80), false, "Multiple logical units share"); sgj_js_nv_ihexstr(jsp, jo2p, "mode_page_policy", bp[2] & 0x3, NULL, mode_page_policy_arr[bp[2] & 0x3]); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } } } /* VPD_POWER_CONDITION 0x8a ["pc"] */ void decode_power_condition(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { sgj_state * jsp = &op->json_st; if (len < 18) { pr2serr("Power condition VPD page length too short=%d\n", len); return; } if (op->do_hex) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } sgj_pr_hr(jsp, " Standby_y=%d Standby_z=%d Idle_c=%d Idle_b=%d " "Idle_a=%d\n", !!(buff[4] & 0x2), !!(buff[4] & 0x1), !!(buff[5] & 0x4), !!(buff[5] & 0x2), !!(buff[5] & 0x1)); if (jsp->pr_as_json) { sgj_js_nv_ihex(jsp, jop, "standby_y", !!(buff[4] & 0x2)); sgj_js_nv_ihex(jsp, jop, "standby_z", !!(buff[4] & 0x1)); sgj_js_nv_ihex(jsp, jop, "idle_c", !!(buff[5] & 0x4)); sgj_js_nv_ihex(jsp, jop, "idle_b", !!(buff[5] & 0x2)); sgj_js_nv_ihex(jsp, jop, "idle_a", !!(buff[5] & 0x1)); } sgj_haj_vi_nex(jsp, jop, 2, "Stopped condition recovery time", SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 6), true, "unit: millisecond"); sgj_haj_vi_nex(jsp, jop, 2, "Standby_z condition recovery time", SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 8), true, "unit: millisecond"); sgj_haj_vi_nex(jsp, jop, 2, "Standby_y condition recovery time", SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 10), true, "unit: millisecond"); sgj_haj_vi_nex(jsp, jop, 2, "Idle_a condition recovery time", SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 12), true, "unit: millisecond"); sgj_haj_vi_nex(jsp, jop, 2, "Idle_b condition recovery time", SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 14), true, "unit: millisecond"); sgj_haj_vi_nex(jsp, jop, 2, "Idle_c condition recovery time", SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 16), true, "unit: millisecond"); } int filter_json_dev_ids(uint8_t * buff, int len, int m_assoc, struct opts_t * op, sgj_opaque_p jap) { int u, off, i_len; sgj_opaque_p jo2p; const uint8_t * bp; sgj_state * jsp = &op->json_st; off = -1; while ((u = sg_vpd_dev_id_iter(buff, len, &off, m_assoc, -1, -1)) == 0) { bp = buff + off; i_len = bp[3]; if ((off + i_len + 4) > len) { pr2serr(" VPD page error: designator length longer than\n" " remaining response length=%d\n", (len - off)); return SG_LIB_CAT_MALFORMED; } jo2p = sgj_new_unattached_object_r(jsp); sgj_js_designation_descriptor(jsp, jo2p, bp, i_len + 4); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } if (-2 == u) { pr2serr("VPD page error: short designator around offset %d\n", off); return SG_LIB_CAT_MALFORMED; } return 0; } /* VPD_ATA_INFO 0x89 ["ai"] */ void decode_ata_info_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { bool do_long_nq = op->do_long && (! op->do_quiet); int num, is_be, cc, n; sgj_state * jsp = &op->json_st; const char * cp; const char * ata_transp; char b[512]; char d[80]; static const int blen = sizeof(b); static const int dlen = sizeof(d); static const char * sat_vip = "SAT Vendor identification"; static const char * sat_pip = "SAT Product identification"; static const char * sat_prlp = "SAT Product revision level"; if (len < 36) { pr2serr("ATA information VPD page length too short=%d\n", len); return; } if (op->do_hex && (2 != op->do_hex)) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } memcpy(b, buff + 8, 8); b[8] = '\0'; sgj_pr_hr(jsp, " %s: %s\n", sat_vip, b); memcpy(b, buff + 16, 16); b[16] = '\0'; sgj_pr_hr(jsp, " %s: %s\n", sat_pip, b); memcpy(b, buff + 32, 4); b[4] = '\0'; sgj_pr_hr(jsp, " %s: %s\n", sat_prlp, b); if (len < 56) return; ata_transp = (0x34 == buff[36]) ? "SATA" : "PATA"; if (do_long_nq) { sgj_pr_hr(jsp, " Device signature [%s] (in hex):\n", ata_transp); hex2stdout(buff + 36, 20, 0); } else sgj_pr_hr(jsp, " Device signature indicates %s transport\n", ata_transp); cc = buff[56]; /* 0xec for IDENTIFY DEVICE and 0xa1 for IDENTIFY * PACKET DEVICE (obsolete) */ n = sg_scnpr(b, blen, " Command code: 0x%x\n", cc); if (len < 60) return; if (0xec == cc) cp = null_s; else if (0xa1 == cc) cp = "PACKET "; else cp = NULL; is_be = sg_is_big_endian(); if (cp) { n += sg_scnpr(b + n, blen - n, " ATA command IDENTIFY %sDEVICE " "response summary:\n", cp); num = sg_ata_get_chars((const unsigned short *)(buff + 60), 27, 20, is_be, d); d[num] = '\0'; n += sg_scnpr(b + n, blen - n, " model: %s\n", d); num = sg_ata_get_chars((const unsigned short *)(buff + 60), 10, 10, is_be, d); d[num] = '\0'; n += sg_scnpr(b + n, blen - n, " serial number: %s\n", d); num = sg_ata_get_chars((const unsigned short *)(buff + 60), 23, 4, is_be, d); d[num] = '\0'; n += sg_scnpr(b + n, blen - n, " firmware revision: %s\n", d); sgj_pr_hr(jsp, "%s", b); if (do_long_nq) sgj_pr_hr(jsp, " ATA command IDENTIFY %sDEVICE response in " "hex:\n", cp); } else if (do_long_nq) sgj_pr_hr(jsp, " ATA command 0x%x got following response:\n", (unsigned int)cc); if (jsp->pr_as_json) { sgj_convert_to_snake_name(sat_vip, d, dlen); sgj_js_nv_s_len(jsp, jop, d, (const char *)(buff + 8), 8); sgj_convert_to_snake_name(sat_pip, d, dlen); sgj_js_nv_s_len(jsp, jop, d, (const char *)(buff + 16), 16); sgj_convert_to_snake_name(sat_prlp, d, dlen); sgj_js_nv_s_len(jsp, jop, d, (const char *)(buff + 32), 4); sgj_js_nv_hex_bytes(jsp, jop, "ata_device_signature", buff + 36, 20); sgj_js_nv_ihex(jsp, jop, "command_code", buff[56]); sgj_js_nv_s(jsp, jop, "ata_identify_device_data_example", "sg_vpd -p ai -HHH /dev/sdc | hdparm --Istdin"); } if (len < 572) return; if (2 == op->do_hex) hex2stdout((buff + 60), 512, 0); else if (do_long_nq) dWordHex((const unsigned short *)(buff + 60), 256, 0, is_be); } /* VPD_SCSI_FEATURE_SETS 0x92 ["sfs"] */ void decode_feature_sets_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k, bump; uint16_t sf_code; bool found; const uint8_t * bp; sgj_opaque_p jo2p; sgj_state * jsp = &op->json_st; char b[256]; char d[80]; if ((1 == op->do_hex) || (op->do_hex > 2)) { hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1); return; } if (len < 4) { pr2serr("SCSI Feature sets VPD page length too short=%d\n", len); return; } len -= 8; bp = buff + 8; for (k = 0; k < len; k += bump, bp += bump) { jo2p = sgj_new_unattached_object_r(jsp); sf_code = sg_get_unaligned_be16(bp); bump = 2; if ((k + bump) > len) { pr2serr("SCSI Feature sets, short descriptor length=%d, " "left=%d\n", bump, (len - k)); return; } if (2 == op->do_hex) hex2stdout(bp + 8, 2, 1); else if (op->do_hex > 2) hex2stdout(bp, 2, 1); else { sg_scnpr(b, sizeof(b), " %s", sg_get_sfs_str(sf_code, -2, sizeof(d), d, &found, op->verbose)); if (op->verbose == 1) sgj_pr_hr(jsp, "%s [0x%x]\n", b, (unsigned int)sf_code); else if (op->verbose > 1) sgj_pr_hr(jsp, "%s [0x%x] found=%s\n", b, (unsigned int)sf_code, found ? "true" : "false"); else sgj_pr_hr(jsp, "%s\n", b); sgj_js_nv_ihexstr(jsp, jo2p, "feature_set_code", sf_code, NULL, d); if (jsp->verbose) sgj_js_nv_b(jsp, jo2p, "meaning_is_match", found); } sgj_js_nv_o(jsp, jap, NULL, jo2p); } } static const char * constituent_type_arr[] = { "Reserved", "Virtual tape library", "Virtual tape drive", "Direct access block device", }; /* VPD_DEVICE_CONSTITUENTS 0x8b ["dc"] */ void decode_dev_constit_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap, recurse_vpd_decodep fp) { uint16_t constit_type; int k, j, res, bump, csd_len; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p, jo3p, ja2p; const uint8_t * bp; char b[256]; char d[64]; static const int blen = sizeof(b); static const int dlen = sizeof(d); if ((1 == op->do_hex) || (op->do_hex > 2)) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } if (len < 4) { pr2serr("page length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0, j = 0; k < len; k += bump, bp += bump, ++j) { jo2p = sgj_new_unattached_object_r(jsp); if (j > 0) sgj_pr_hr(jsp, "\n"); sgj_pr_hr(jsp, " Constituent descriptor %d:\n", j + 1); if ((k + 36) > len) { pr2serr("short descriptor length=36, left=%d\n", (len - k)); sgj_js_nv_o(jsp, jap, NULL, jo2p); return; } constit_type = sg_get_unaligned_be16(bp + 0); if (constit_type >= SG_ARRAY_SIZE(constituent_type_arr)) sgj_pr_hr(jsp," Constituent type: unknown [0x%x]\n", constit_type); else sgj_pr_hr(jsp, " Constituent type: %s [0x%x]\n", constituent_type_arr[constit_type], constit_type); sg_scnpr(b, blen, " Constituent device type: "); if (0xff == bp[2]) sgj_pr_hr(jsp, "%sUnknown [0xff]\n", b); else if (bp[2] >= 0x20) sgj_pr_hr(jsp, "%s%s [0x%x]\n", b, rsv_s, bp[2]); else sgj_pr_hr(jsp, "%s%s [0x%x]\n", b, sg_get_pdt_str(PDT_MASK & bp[2], dlen, d), bp[2]); snprintf(b, blen, "%.8s", bp + 4); sgj_pr_hr(jsp, " %s: %s\n", t10_vendor_id_hr, b); sgj_js_nv_s(jsp, jo2p, t10_vendor_id_js, b); snprintf(b, blen, "%.16s", bp + 12); sgj_pr_hr(jsp, " %s: %s\n", product_id_hr, b); sgj_js_nv_s(jsp, jo2p, product_id_js, b); snprintf(b, blen, "%.4s", bp + 28); sgj_pr_hr(jsp, " %s: %s\n", product_rev_lev_hr, b); sgj_js_nv_s(jsp, jo2p, product_rev_lev_js, b); csd_len = sg_get_unaligned_be16(bp + 34); bump = 36 + csd_len; if ((k + bump) > len) { pr2serr("short descriptor length=%d, left=%d\n", bump, (len - k)); sgj_js_nv_o(jsp, jap, NULL, jo2p); return; } if (csd_len > 0) { int m, q, cs_bump; uint8_t cs_type; uint8_t cs_len; const uint8_t * cs_bp; sgj_pr_hr(jsp, " Constituent specific descriptors:\n"); ja2p = sgj_named_subarray_r(jsp, jo2p, "constituent_specific_descriptor_list"); for (m = 0, q = 0, cs_bp = bp + 36; m < csd_len; m += cs_bump, ++q, cs_bp += cs_bump) { jo3p = sgj_new_unattached_object_r(jsp); cs_type = cs_bp[0]; cs_len = sg_get_unaligned_be16(cs_bp + 2); cs_bump = cs_len + 4; sgj_js_nv_ihex(jsp, jo3p, "constituent_specific_type", cs_type); if (1 == cs_type) { /* VPD page */ int off = cs_bp + 4 - buff; sgj_pr_hr(jsp, " Constituent specific VPD page " "%d:\n", q + 1); /* SPC-5 says these shall _not_ themselves be Device * Constituent VPD pages. So no infinite recursion. */ res = (*fp)(op, jo3p, off); if (res) pr2serr("%s: recurse_vpd_decode() failed, res=%d\n", __func__, res); } else { if (0xff == cs_type) sgj_pr_hr(jsp, " Vendor specific data (in " "hex):\n"); else sgj_pr_hr(jsp, " %s [0x%x] specific data (in " "hex):\n", rsv_s, cs_type); if (jsp->pr_as_json) sgj_js_nv_hex_bytes(jsp, jo3p, "constituent_specific_data_hex", cs_bp + 4, cs_len); else hex2stdout(cs_bp + 4, cs_len, 0 /* plus ASCII */); } sgj_js_nv_o(jsp, ja2p, NULL, jo3p); } /* end of Constituent specific descriptor loop */ } sgj_js_nv_o(jsp, jap, NULL, jo2p); } /* end Constituent descriptor loop */ } /* VPD_CFA_PROFILE_INFO 0x8c ["cfa"] */ void decode_cga_profile_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k; uint32_t u; sgj_state * jsp = &op->json_st; const uint8_t * bp; sgj_opaque_p jo2p; if (op->do_hex) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } if (len < 4) { pr2serr("VPD page length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0; k < len; k += 4, bp += 4) { jo2p = sgj_new_unattached_object_r(jsp); sgj_haj_vi(jsp, jo2p, 0, "CGA profile supported", SGJ_SEP_COLON_1_SPACE, bp[0], true); u = sg_get_unaligned_be16(bp + 2); sgj_haj_vi_nex(jsp, jo2p, 2, "Sequential write data size", SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } /* Assume index is less than 16 */ static const char * sg_ansi_version_arr[16] = { "no conformance claimed", "SCSI-1", /* obsolete, ANSI X3.131-1986 */ "SCSI-2", /* obsolete, ANSI X3.131-1994 */ "SPC", /* withdrawn, ANSI INCITS 301-1997 */ "SPC-2", /* ANSI INCITS 351-2001, ISO/IEC 14776-452 */ "SPC-3", /* ANSI INCITS 408-2005, ISO/IEC 14776-453 */ "SPC-4", /* ANSI INCITS 513-2015 */ "SPC-5", /* ANSI INCITS 502-2020 */ "ecma=1, [8h]", "ecma=1, [9h]", "ecma=1, [Ah]", "ecma=1, [Bh]", "reserved [Ch]", "reserved [Dh]", "reserved [Eh]", "reserved [Fh]", }; static const char * hot_pluggable_str(int hp) { switch (hp) { case 0: return "No information"; case 1: return "target device designed to be removed from SCSI domain"; case 2: return "target device not designed to be removed from SCSI domain"; default: return "value reserved by T10"; } } static const char * tpgs_str(int tpgs) { switch (tpgs) { case 1: return "only implicit asymmetric logical unit access"; case 2: return "only explicit asymmetric logical unit access"; case 3: return "both explicit and implicit asymmetric logical unit access"; case 0: default: return ns_s; } } sgj_opaque_p std_inq_decode_js(const uint8_t * b, int len, struct opts_t * op, sgj_opaque_p jop) { int tpgs; int pqual = (b[0] & 0xe0) >> 5; int pdt = b[0] & PDT_MASK; int hp = (b[1] >> 4) & 0x3; int ver = b[2]; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p = NULL; char c[256]; static const int clen = sizeof(c); jo2p = sgj_named_subobject_r(jsp, jop, "standard_inquiry_data_format"); sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_qualifier", pqual, NULL, pqual_str(pqual)); sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_device_type", pdt, NULL, sg_get_pdt_str(pdt, clen, c)); sgj_js_nv_ihex_nex(jsp, jo2p, "rmb", !!(b[1] & 0x80), false, "Removable Medium Bit"); sgj_js_nv_ihex_nex(jsp, jo2p, "lu_cong", !!(b[1] & 0x40), false, "Logical Unit Conglomerate"); sgj_js_nv_ihexstr(jsp, jo2p, "hot_pluggable", hp, NULL, hot_pluggable_str(hp)); snprintf(c, clen, "%s", (ver > 0xf) ? "old or reserved version code" : sg_ansi_version_arr[ver]); sgj_js_nv_ihexstr(jsp, jo2p, "version", ver, NULL, c); sgj_js_nv_ihex_nex(jsp, jo2p, "aerc", !!(b[3] & 0x80), false, "Asynchronous Event Reporting Capability (obsolete " "SPC-3)"); sgj_js_nv_ihex_nex(jsp, jo2p, "trmtsk", !!(b[3] & 0x40), false, "Terminate Task (obsolete SPC-2)"); sgj_js_nv_ihex_nex(jsp, jo2p, "normaca", !!(b[3] & 0x20), false, "Normal ACA (Auto Contingent Allegiance)"); sgj_js_nv_ihex_nex(jsp, jo2p, "hisup", !!(b[3] & 0x10), false, "Hierarchial Support"); sgj_js_nv_ihex(jsp, jo2p, "response_data_format", b[3] & 0xf); sgj_js_nv_ihex_nex(jsp, jo2p, "sccs", !!(b[5] & 0x80), false, "SCC (SCSI Storage Commands) Supported"); sgj_js_nv_ihex_nex(jsp, jo2p, "acc", !!(b[5] & 0x40), false, "Access Commands Coordinator (obsolete SPC-5)"); tpgs = (b[5] >> 4) & 0x3; sgj_js_nv_ihexstr_nex(jsp, jo2p, "tpgs", tpgs, false, NULL, tpgs_str(tpgs), "Target Port Group Support"); sgj_js_nv_ihex_nex(jsp, jo2p, "3pc", !!(b[5] & 0x8), false, "Third Party Copy"); sgj_js_nv_ihex(jsp, jo2p, "protect", !!(b[5] & 0x1)); /* Skip SPI specific flags which have been obsolete for a while) */ sgj_js_nv_ihex_nex(jsp, jo2p, "bque", !!(b[6] & 0x80), false, "Basic task management model (obsolete SPC-4)"); sgj_js_nv_ihex_nex(jsp, jo2p, "encserv", !!(b[6] & 0x40), false, "Enclousure Services supported"); sgj_js_nv_ihex_nex(jsp, jo2p, "multip", !!(b[6] & 0x10), false, "Multiple SCSI port"); sgj_js_nv_ihex_nex(jsp, jo2p, "mchngr", !!(b[6] & 0x8), false, "Medium changer (obsolete SPC-4)"); sgj_js_nv_ihex_nex(jsp, jo2p, "reladr", !!(b[7] & 0x80), false, "Relative Addressing (obsolete in SPC-4)"); sgj_js_nv_ihex_nex(jsp, jo2p, "linked", !!(b[7] & 0x8), false, "Linked Commands (obsolete in SPC-4)"); sgj_js_nv_ihex_nex(jsp, jo2p, "cmdque", !!(b[7] & 0x2), false, "Command Management Model (command queuing)"); if (len < 16) return jo2p; snprintf(c, clen, "%.8s", b + 8); sgj_js_nv_s(jsp, jo2p, t10_vendor_id_js, c); if (len < 32) return jo2p; snprintf(c, clen, "%.16s", b + 16); sgj_js_nv_s(jsp, jo2p, product_id_js, c); if (len < 36) return jo2p; snprintf(c, clen, "%.4s", b + 32); sgj_js_nv_s(jsp, jo2p, product_rev_lev_js, c); return jo2p; } static const char * power_unit_arr[] = { "Gigawatts", "Megawatts", "Kilowatts", "Watts", "Milliwatts", "Microwatts", "Unit reserved", "Unit reserved", }; /* VPD_POWER_CONSUMPTION 0x8d ["psm"] */ void decode_power_consumption(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k, bump, pcmp_id, pcmp_unit; unsigned int pcmp_val; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p; const uint8_t * bp; char b[128]; static const int blen = sizeof(b); static const char * pcmp = "power_consumption"; static const char * pci = "Power consumption identifier"; static const char * mpc = "Maximum power consumption"; if ((1 == op->do_hex) || (op->do_hex > 2)) { hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1); return; } if (len < 4) { pr2serr("length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0; k < len; k += bump, bp += bump) { bump = 4; if ((k + bump) > len) { pr2serr("short descriptor length=%d, left=%d\n", bump, (len - k)); return; } if (op->do_hex > 1) hex2stdout(bp, 4, 1); else { jo2p = sgj_new_unattached_object_r(jsp); pcmp_id = bp[0]; pcmp_unit = 0x7 & bp[1]; pcmp_val = sg_get_unaligned_be16(bp + 2); if (jsp->pr_as_json) { sgj_convert_to_snake_name(pci, b, blen); sgj_js_nv_ihex(jsp, jo2p, b, pcmp_id); snprintf(b, blen, "%s_units", pcmp); sgj_js_nv_ihexstr(jsp, jo2p, b, pcmp_unit, NULL, power_unit_arr[pcmp_unit]); snprintf(b, blen, "%s_value", pcmp); sgj_js_nv_ihex(jsp, jo2p, b, pcmp_val); } snprintf(b, blen, " %s: 0x%x", pci, pcmp_id); if (pcmp_val >= 1000 && pcmp_unit > 0) sgj_pr_hr(jsp, "%s %s: %d.%03d %s\n", b, mpc, pcmp_val / 1000, pcmp_val % 1000, power_unit_arr[pcmp_unit - 1]); /* up one unit */ else sgj_pr_hr(jsp, "%s %s: %u %s\n", b, mpc, pcmp_val, power_unit_arr[pcmp_unit]); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } } /* VPD_BLOCK_LIMITS 0xb0 ["bl"] */ void decode_block_limits_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { int wsnz, ugavalid; uint32_t u; uint64_t ull; sgj_state * jsp = &op->json_st; char b[144]; static const int blen = sizeof(b); static const char * mcawl = "Maximum compare and write length"; static const char * otlg = "Optimal transfer length granularity"; static const char * cni = "command not implemented"; static const char * ul = "unlimited"; static const char * mtl = "Maximum transfer length"; static const char * otl = "Optimal transfer length"; static const char * mpl = "Maximum prefetch length"; static const char * mulc = "Maximum unmap LBA count"; static const char * mubdc = "Maximum unmap block descriptor count"; static const char * oug = "Optimal unmap granularity"; static const char * ugav = "Unmap granularity alignment valid"; static const char * uga = "Unmap granularity alignment"; static const char * mwsl = "Maximum write same length"; static const char * matl = "Maximum atomic transfer length"; static const char * aa = "Atomic alignment"; static const char * atlg = "Atomic transfer length granularity"; static const char * matlwab = "Maximum atomic transfer length with " "atomic boundary"; static const char * mabs = "Maximum atomic boundary size"; if (len < 16) { pr2serr("page length too short=%d\n", len); return; } wsnz = !!(buff[4] & 0x1); sgj_pr_hr(jsp, " Write same non-zero (WSNZ): %d\n", wsnz); sgj_js_nv_ihex_nex(jsp, jop, "wsnz", wsnz, false, "Write Same Non-Zero (number of LBs must be > 0)"); u = buff[5]; if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mcawl, cni); sgj_convert_to_snake_name(mcawl, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, cni); } else sgj_haj_vi_nex(jsp, jop, 2, mcawl, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); u = sg_get_unaligned_be16(buff + 6); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", otlg, nr_s); sgj_convert_to_snake_name(otlg, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s); } else sgj_haj_vi_nex(jsp, jop, 2, otlg, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); u = sg_get_unaligned_be32(buff + 8); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mtl, nr_s); sgj_convert_to_snake_name(mtl, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s); } else sgj_haj_vi_nex(jsp, jop, 2, mtl, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); u = sg_get_unaligned_be32(buff + 12); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", otl, nr_s); sgj_convert_to_snake_name(otl, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s); } else sgj_haj_vi_nex(jsp, jop, 2, otl, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); if (len > 19) { /* added in sbc3r09 */ u = sg_get_unaligned_be32(buff + 16); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mpl, nr_s); sgj_convert_to_snake_name(mpl, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s); } else sgj_haj_vi_nex(jsp, jop, 2, mpl, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); } if (len > 27) { /* added in sbc3r18 */ u = sg_get_unaligned_be32(buff + 20); sgj_convert_to_snake_name(mulc, b, blen); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mulc, cni); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, cni); } else if (0xffffffff == u) { sgj_pr_hr(jsp, " %s: %s blocks\n", ul, mulc); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, ul); } else sgj_haj_vi_nex(jsp, jop, 2, mulc, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); u = sg_get_unaligned_be32(buff + 24); sgj_convert_to_snake_name(mulc, b, blen); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 block descriptors [%s]\n", mubdc, cni); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, cni); } else if (0xffffffff == u) { sgj_pr_hr(jsp, " %s: %s block descriptors\n", ul, mubdc); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, ul); } else sgj_haj_vi(jsp, jop, 2, mubdc, SGJ_SEP_COLON_1_SPACE, u, true); } if (len > 35) { /* added in sbc3r19 */ u = sg_get_unaligned_be32(buff + 28); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", oug, nr_s); sgj_convert_to_snake_name(oug, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s); } else sgj_haj_vi_nex(jsp, jop, 2, oug, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); ugavalid = !!(buff[32] & 0x80); sgj_pr_hr(jsp, " %s: %s\n", ugav, ugavalid ? "true" : "false"); sgj_js_nv_i(jsp, jop, ugav, ugavalid); if (ugavalid) { u = 0x7fffffff & sg_get_unaligned_be32(buff + 32); sgj_haj_vi_nex(jsp, jop, 2, uga, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); } } if (len > 43) { /* added in sbc3r26 */ ull = sg_get_unaligned_be64(buff + 36); if (0 == ull) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mwsl, nr_s); sgj_convert_to_snake_name(mwsl, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, ull, NULL, nr_s); } else sgj_haj_vi_nex(jsp, jop, 2, mwsl, SGJ_SEP_COLON_1_SPACE, ull, true, "unit: LB"); } if (len > 47) { /* added in sbc4r02 */ u = sg_get_unaligned_be32(buff + 44); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", matl, nr_s); sgj_convert_to_snake_name(matl, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s); } else sgj_haj_vi_nex(jsp, jop, 2, matl, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); u = sg_get_unaligned_be32(buff + 48); if (0 == u) { static const char * uawp = "unaligned atomic writes permitted"; sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", aa, uawp); sgj_convert_to_snake_name(aa, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, uawp); } else sgj_haj_vi_nex(jsp, jop, 2, aa, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); u = sg_get_unaligned_be32(buff + 52); if (0 == u) { static const char * ngr = "no granularity requirement"; sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", atlg, ngr); sgj_convert_to_snake_name(atlg, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, ngr); } else sgj_haj_vi_nex(jsp, jop, 2, aa, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); } if (len > 56) { u = sg_get_unaligned_be32(buff + 56); if (0 == u) { sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", matlwab, nr_s); sgj_convert_to_snake_name(matlwab, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s); } else sgj_haj_vi_nex(jsp, jop, 2, matlwab, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); u = sg_get_unaligned_be32(buff + 60); if (0 == u) { static const char * cowa1b = "can only write atomic 1 block"; sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mabs, cowa1b); sgj_convert_to_snake_name(mabs, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, cowa1b); } else sgj_haj_vi_nex(jsp, jop, 2, mabs, SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); } } static const char * product_type_arr[] = { "Not specified", "CFast", "CompactFlash", "MemoryStick", "MultiMediaCard", "Secure Digital Card (SD)", "XQD", "Universal Flash Storage Card (UFS)", }; /* ZONED field here replaced by ZONED BLOCK DEVICE EXTENSION field in the * Zoned Block Device Characteristics VPD page. The new field includes * Zone Domains and Realms (see ZBC-2) */ static const char * bdc_zoned_strs[] = { nr_s, "host-aware", "host-managed", rsv_s, }; /* VPD_BLOCK_DEV_CHARS 0xb1 ["bdc"] */ void decode_block_dev_ch_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { int zoned; unsigned int u, k; sgj_state * jsp = &op->json_st; const char * cp; char b[144]; static const char * mrr_j = "medium_rotation_rate"; static const char * mrr_h = "Medium rotation rate"; static const char * nrm = "Non-rotating medium (e.g. solid state)"; static const char * pt_j = "product_type"; if (len < 64) { pr2serr("page length too short=%d\n", len); return; } u = sg_get_unaligned_be16(buff + 4); if (0 == u) { sgj_pr_hr(jsp, " %s is %s\n", mrr_h, nr_s); sgj_js_nv_ihexstr(jsp, jop, mrr_j, 0, NULL, nr_s); } else if (1 == u) { sgj_pr_hr(jsp, " %s\n", nrm); sgj_js_nv_ihexstr(jsp, jop, mrr_j, 1, NULL, nrm); } else if ((u < 0x401) || (0xffff == u)) { sgj_pr_hr(jsp, " %s [0x%x]\n", rsv_s, u); sgj_js_nv_ihexstr(jsp, jop, mrr_j, u, NULL, rsv_s); } else { sgj_js_nv_ihex_nex(jsp, jop, mrr_j, u, true, "unit: rpm; nominal rotation rate"); } u = buff[6]; k = SG_ARRAY_SIZE(product_type_arr); if (u < k) { sgj_pr_hr(jsp, " %s: %s\n", "Product type", product_type_arr[u]); sgj_js_nv_ihexstr(jsp, jop, pt_j, u, NULL, product_type_arr[u]); } else { sgj_pr_hr(jsp, " %s: %s [0x%x]\n", "Product type", (u < 0xf0) ? rsv_s : vs_s, u); sgj_js_nv_ihexstr(jsp, jop, pt_j, u, NULL, (u < 0xf0) ? rsv_s : vs_s); } sgj_haj_vi_nex(jsp, jop, 2, "WABEREQ", SGJ_SEP_EQUAL_NO_SPACE, (buff[7] >> 6) & 0x3, false, "Write After Block Erase REQuired"); sgj_haj_vi_nex(jsp, jop, 2, "WACEREQ", SGJ_SEP_EQUAL_NO_SPACE, (buff[7] >> 4) & 0x3, false, "Write After Cryptographic Erase REQuired"); u = buff[7] & 0xf; switch (u) { case 0: strcpy(b, nr_s); break; case 1: strcpy(b, "5.25 inch"); break; case 2: strcpy(b, "3.5 inch"); break; case 3: strcpy(b, "2.5 inch"); break; case 4: strcpy(b, "1.8 inch"); break; case 5: strcpy(b, "less then 1.8 inch"); break; default: strcpy(b, rsv_s); break; } sgj_pr_hr(jsp, " Nominal form factor: %s\n", b); sgj_js_nv_ihexstr(jsp, jop, "nominal_forn_factor", u, NULL, b); sgj_haj_vi_nex(jsp, jop, 2, "MACT", SGJ_SEP_EQUAL_NO_SPACE, !!(buff[8] & 0x40), false, "Multiple ACTuator"); zoned = (buff[8] >> 4) & 0x3; /* added sbc4r04, obsolete sbc5r01 */ cp = bdc_zoned_strs[zoned]; sgj_pr_hr(jsp, " ZONED=%d [%s]\n", zoned, cp); sgj_js_nv_ihexstr_nex(jsp, jop, "zoned", zoned, false, NULL, cp, "Added in SBC-4, obsolete in SBC-5"); sgj_haj_vi_nex(jsp, jop, 2, "RBWZ", SGJ_SEP_EQUAL_NO_SPACE, !!(buff[8] & 0x4), false, "Background Operation Control Supported"); sgj_haj_vi_nex(jsp, jop, 2, "FUAB", SGJ_SEP_EQUAL_NO_SPACE, !!(buff[8] & 0x2), false, "Force Unit Access Behaviour"); sgj_haj_vi_nex(jsp, jop, 2, "VBULS", SGJ_SEP_EQUAL_NO_SPACE, !!(buff[8] & 0x1), false, "Verify Byte check Unmapped Lba Supported"); u = sg_get_unaligned_be32(buff + 12); sgj_haj_vi_nex(jsp, jop, 2, "DEPOPULATION TIME", SGJ_SEP_COLON_1_SPACE, u, true, "unit: second"); } static const char * prov_type_arr[8] = { "not known or fully provisioned", "resource provisioned", "thin provisioned", rsv_s, rsv_s, rsv_s, rsv_s, rsv_s, }; /* VPD_LB_PROVISIONING 0xb2 ["lbpv"] */ int decode_block_lb_prov_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { unsigned int u, dp, pt, t_exp; sgj_state * jsp = &op->json_st; const char * cp; char b[1024]; static const int blen = sizeof(b); static const char * mp = "Minimum percentage"; static const char * tp = "Threshold percentage"; static const char * pgd = "Provisioning group descriptor"; if (len < 4) { pr2serr("page too short=%d\n", len); return SG_LIB_CAT_MALFORMED; } t_exp = buff[4]; sgj_js_nv_ihexstr(jsp, jop, "threshold_exponent", t_exp, NULL, (0 == t_exp) ? ns_s : NULL); sgj_haj_vi_nex(jsp, jop, 2, "LBPU", SGJ_SEP_EQUAL_NO_SPACE, !!(buff[5] & 0x80), false, "Logical Block Provisioning Unmap command supported"); sgj_haj_vi_nex(jsp, jop, 2, "LBPWS", SGJ_SEP_EQUAL_NO_SPACE, !!(buff[5] & 0x40), false, "Logical Block Provisioning " "Write Same (16) command supported"); sgj_haj_vi_nex(jsp, jop, 2, "LBPWS10", SGJ_SEP_EQUAL_NO_SPACE, !!(buff[5] & 0x20), false, "Logical Block Provisioning " "Write Same (10) command supported"); sgj_haj_vi_nex(jsp, jop, 2, "LBPRZ", SGJ_SEP_EQUAL_NO_SPACE, (0x7 & (buff[5] >> 2)), true, "Logical Block Provisioning Read Zero"); sgj_haj_vi_nex(jsp, jop, 2, "ANC_SUP", SGJ_SEP_EQUAL_NO_SPACE, !!(buff[5] & 0x2), false, "ANChor SUPported"); dp = !!(buff[5] & 0x1); sgj_haj_vi_nex(jsp, jop, 2, "DP", SGJ_SEP_EQUAL_NO_SPACE, dp, false, "Descriptor Present"); u = 0x1f & (buff[6] >> 3); /* minimum percentage */ if (0 == u) sgj_pr_hr(jsp, " %s: 0 [%s]\n", mp, nr_s); else sgj_pr_hr(jsp, " %s: %u\n", mp, u); sgj_convert_to_snake_name(mp, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, (0 == u) ? nr_s : NULL); pt = buff[6] & 0x7; cp = prov_type_arr[pt]; if (pt > 2) snprintf(b, blen, " [%u]", u); else b[0] = '\0'; sgj_pr_hr(jsp, " Provisioning type: %s%s\n", cp, b); sgj_js_nv_ihexstr(jsp, jop, "provisioning_type", pt, NULL, cp); u = buff[7]; /* threshold percentage */ strcpy(b, tp); if (0 == u) sgj_pr_hr(jsp, " %s: 0 [percentages %s]\n", b, ns_s); else sgj_pr_hr(jsp, " %s: %u", b, u); sgj_convert_to_snake_name(tp, b, blen); sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, (0 == u) ? ns_s : NULL); if (dp && (len > 11)) { int i_len; const uint8_t * bp; sgj_opaque_p jo2p; bp = buff + 8; i_len = bp[3]; if (0 == i_len) { pr2serr("%s too short=%d\n", pgd, i_len); return 0; } if (jsp->pr_as_json) { jo2p = sgj_snake_named_subobject_r(jsp, jop, pgd); sgj_js_designation_descriptor(jsp, jo2p, bp, i_len + 4); } sgj_pr_hr(jsp, " %s:\n", pgd); sg_get_designation_descriptor_str(" ", bp, i_len + 4, true, op->do_long, blen, b); if (jsp->pr_as_json && jsp->pr_out_hr) sgj_js_str_out(jsp, b, strlen(b)); else sgj_pr_hr(jsp, "%s", b); } return 0; } /* VPD_REFERRALS 0xb3 ["ref"] */ void decode_referrals_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { uint32_t u; sgj_state * jsp = &op->json_st; char b[64]; if (len < 16) { pr2serr("Referrals VPD page length too short=%d\n", len); return; } u = sg_get_unaligned_be32(buff + 8); strcpy(b, " User data segment size: "); if (0 == u) sgj_pr_hr(jsp, "%s0 [per sense descriptor]\n", b); else sgj_pr_hr(jsp, "%s%u\n", b, u); sgj_js_nv_ihex(jsp, jop, "user_data_segment_size", u); u = sg_get_unaligned_be32(buff + 12); sgj_haj_vi(jsp, jop, 2, "User data segment multiplier", SGJ_SEP_COLON_1_SPACE, u, true); } /* VPD_SUP_BLOCK_LENS 0xb4 ["sbl"] (added sbc4r01) */ void decode_sup_block_lens_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k; unsigned int u; const uint8_t * bp; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p = NULL; if (len < 4) { pr2serr("page length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0; k < len; k += 8, bp += 8) { if (jsp->pr_as_json) jo2p = sgj_new_unattached_object_r(jsp); u = sg_get_unaligned_be32(bp); sgj_haj_vi(jsp, jo2p, 2, "Logical block length", SGJ_SEP_COLON_1_SPACE, u, true); sgj_haj_vi_nex(jsp, jo2p, 4, "P_I_I_SUP", SGJ_SEP_COLON_1_SPACE, !!(bp[4] & 0x40), false, "Protection Information Interval SUPported"); sgj_haj_vi_nex(jsp, jo2p, 4, "NO_PI_CHK", SGJ_SEP_COLON_1_SPACE, !!(bp[4] & 0x8), false, "NO Protection Information CHecKing"); sgj_haj_vi_nex(jsp, jo2p, 4, "GRD_CHK", SGJ_SEP_COLON_1_SPACE, !!(bp[4] & 0x4), false, "GuaRD CHecK"); sgj_haj_vi_nex(jsp, jo2p, 4, "APP_CHK", SGJ_SEP_COLON_1_SPACE, !!(bp[4] & 0x2), false, "APPlication tag CHecK"); sgj_haj_vi_nex(jsp, jo2p, 4, "REF_CHK", SGJ_SEP_COLON_1_SPACE, !!(bp[4] & 0x1), false, "REFerence tag CHecK"); sgj_haj_vi_nex(jsp, jo2p, 4, "T3PS", SGJ_SEP_COLON_1_SPACE, !!(bp[5] & 0x8), false, "Type 3 Protection Supported"); sgj_haj_vi_nex(jsp, jo2p, 4, "T2PS", SGJ_SEP_COLON_1_SPACE, !!(bp[5] & 0x4), false, "Type 2 Protection Supported"); sgj_haj_vi_nex(jsp, jo2p, 4, "T1PS", SGJ_SEP_COLON_1_SPACE, !!(bp[5] & 0x2), false, "Type 1 Protection Supported"); sgj_haj_vi_nex(jsp, jo2p, 4, "T0PS", SGJ_SEP_COLON_1_SPACE, !!(bp[5] & 0x1), false, "Type 0 Protection Supported"); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } /* VPD_BLOCK_DEV_C_EXTENS 0xb5 ["bdce"] (added sbc4r02) */ void decode_block_dev_char_ext_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { bool b_active = false; bool combined = false; int n; uint32_t u; sgj_state * jsp = &op->json_st; const char * utp = null_s; const char * uup = null_s; const char * uip = null_s; char b[128]; static const int blen = sizeof(b); if (len < 16) { pr2serr("page length too short=%d\n", len); return; } switch (buff[5]) { case 1: utp = "Combined writes and reads"; combined = true; break; case 2: utp = "Writes only"; break; case 3: utp = "Separate writes and reads"; b_active = true; break; default: utp = rsv_s; break; } sgj_haj_vistr(jsp, jop, 2, "Utilization type", SGJ_SEP_COLON_1_SPACE, buff[5], true, utp); switch (buff[6]) { case 2: uup = "megabytes"; break; case 3: uup = "gigabytes"; break; case 4: uup = "terabytes"; break; case 5: uup = "petabytes"; break; case 6: uup = "exabytes"; break; default: uup = rsv_s; break; } sgj_haj_vistr(jsp, jop, 2, "Utilization units", SGJ_SEP_COLON_1_SPACE, buff[6], true, uup); switch (buff[7]) { case 0xa: uip = "per day"; break; case 0xe: uip = "per year"; break; default: uip = rsv_s; break; } sgj_haj_vistr(jsp, jop, 2, "Utilization interval", SGJ_SEP_COLON_1_SPACE, buff[7], true, uip); u = sg_get_unaligned_be32(buff + 8); sgj_haj_vistr(jsp, jop, 2, "Utilization B", SGJ_SEP_COLON_1_SPACE, u, true, (b_active ? NULL : rsv_s)); n = sg_scnpr(b, blen, "%s: ", "Designed utilization"); if (b_active) n += sg_scnpr(b + n, blen - n, "%u %s for reads and ", u, uup); u = sg_get_unaligned_be32(buff + 12); sgj_haj_vi(jsp, jop, 2, "Utilization A", SGJ_SEP_COLON_1_SPACE, u, true); n += sg_scnpr(b + n, blen - n, "%u %s for %swrites, %s", u, uup, combined ? "reads and " : null_s, uip); sgj_pr_hr(jsp, " %s\n", b); if (jsp->pr_string) sgj_js_nv_s(jsp, jop, "summary", b); } /* VPD_ZBC_DEV_CHARS 0xb6 ["zdbch"] sbc or zbc [zbc2r04] */ void decode_zbdch_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { uint32_t u, pdt; sgj_state * jsp = &op->json_st; char b[128]; static const int blen = sizeof(b); if (op->do_hex) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } if (len < 64) { pr2serr("Zoned block device characteristics VPD page length too " "short=%d\n", len); return; } pdt = PDT_MASK & buff[0]; sgj_pr_hr(jsp, " Peripheral device type: %s\n", sg_get_pdt_str(pdt, blen, b)); sgj_pr_hr(jsp, " Zoned block device extension: "); u = (buff[4] >> 4) & 0xf; switch (u) { case 0: if (PDT_ZBC == (PDT_MASK & buff[0])) strcpy(b, "host managed zoned block device"); else strcpy(b, nr_s); break; case 1: strcpy(b, "host aware zoned block device model"); break; case 2: strcpy(b, "Domains and realms zoned block device model"); break; default: strcpy(b, rsv_s); break; } sgj_haj_vistr(jsp, jop, 2, "Zoned block device extension", SGJ_SEP_COLON_1_SPACE, u, true, b); sgj_haj_vi_nex(jsp, jop, 2, "AAORB", SGJ_SEP_COLON_1_SPACE, !!(buff[4] & 0x2), false, "Activation Aligned On Realm Boundaries"); sgj_haj_vi_nex(jsp, jop, 2, "URSWRZ", SGJ_SEP_COLON_1_SPACE, !!(buff[4] & 0x1), false, "Unrestricted Read in Sequential Write Required Zone"); u = sg_get_unaligned_be32(buff + 8); sgj_haj_vistr(jsp, jop, 2, "Optimal number of open sequential write " "preferred zones", SGJ_SEP_COLON_1_SPACE, u, true, (SG_LIB_UNBOUNDED_32BIT == u) ? nr_s : NULL); u = sg_get_unaligned_be32(buff + 12); sgj_haj_vistr(jsp, jop, 2, "Optimal number of non-sequentially " "written sequential write preferred zones", SGJ_SEP_COLON_1_SPACE, u, true, (SG_LIB_UNBOUNDED_32BIT == u) ? nr_s : NULL); u = sg_get_unaligned_be32(buff + 16); sgj_haj_vistr(jsp, jop, 2, "Maximum number of open sequential write " "required zones", SGJ_SEP_COLON_1_SPACE, u, true, (SG_LIB_UNBOUNDED_32BIT == u) ? nl_s : NULL); u = buff[23] & 0xf; switch (u) { case 0: strcpy(b, nr_s); break; case 1: strcpy(b, "Zoned starting LBAs aligned using constant zone lengths"); break; case 0x8: strcpy(b, "Zoned starting LBAs potentially non-constant (as " "reported by REPORT ZONES)"); break; default: strcpy(b, rsv_s); break; } sgj_haj_vistr(jsp, jop, 2, "Zoned alignment method", SGJ_SEP_COLON_1_SPACE, u, true, b); sgj_haj_vi(jsp, jop, 2, "Zone starting LBA granularity", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be64(buff + 24), true); } /* VPD_BLOCK_LIMITS_EXT 0xb7 ["ble"] SBC */ void decode_block_limits_ext_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { uint32_t u; sgj_state * jsp = &op->json_st; if (op->do_hex) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } if (len < 12) { pr2serr("page length too short=%d\n", len); return; } u = sg_get_unaligned_be16(buff + 6); sgj_haj_vistr(jsp, jop, 2, "Maximum number of streams", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u) ? "Stream control not supported" : NULL); u = sg_get_unaligned_be16(buff + 8); sgj_haj_vi_nex(jsp, jop, 2, "Optimal stream write size", SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB"); u = sg_get_unaligned_be32(buff + 10); sgj_haj_vi_nex(jsp, jop, 2, "Stream granularity size", SGJ_SEP_COLON_1_SPACE, u, true, "unit: number of optimal stream write size blocks"); if (len < 28) return; u = sg_get_unaligned_be32(buff + 16); sgj_haj_vistr_nex(jsp, jop, 2, "Maximum scattered LBA range transfer " "length", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u ? nlr_s : NULL), "unit: LB (in a single LBA range descriptor)"); u = sg_get_unaligned_be16(buff + 22); sgj_haj_vistr(jsp, jop, 2, "Maximum scattered LBA range descriptor " "count", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u ? nlr_s : NULL)); u = sg_get_unaligned_be32(buff + 24); sgj_haj_vistr_nex(jsp, jop, 2, "Maximum scattered transfer length", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u ? nlr_s : NULL), "unit: LB (per single Write Scattered command)"); } static const char * sch_type_arr[8] = { rsv_s, "non-zoned", "host aware zoned", "host managed zoned", "zone domain and realms zoned", rsv_s, rsv_s, rsv_s, }; static char * get_zone_align_method(uint8_t val, char * b, int blen) { assert(blen > 32); switch (val) { case 0: strcpy(b, nr_s); break; case 1: strcpy(b, "using constant zone lengths"); break; case 8: strcpy(b, "taking gap zones into account"); break; default: strcpy(b, rsv_s); break; } return b; } /* VPD_FORMAT_PRESETS 0xb8 ["fp"] (added sbc4r18) */ void decode_format_presets_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { uint8_t sch_type; int k; uint32_t u; uint64_t ul; sgj_state * jsp = &op->json_st; const uint8_t * bp; sgj_opaque_p jo2p, jo3p; const char * cp; char b[128]; char d[64]; static const int blen = sizeof(b); static const int dlen = sizeof(d); static const char * llczp = "Low LBA conventional zones percentage"; static const char * hlczp = "High LBA conventional zones percentage"; static const char * ztzd = "Zone type for zone domain"; if (op->do_hex) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } if (len < 4) { pr2serr("VPD page length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0; k < len; k += 64, bp += 64) { jo2p = sgj_new_unattached_object_r(jsp); sgj_haj_vi(jsp, jo2p, 2, "Preset identifier", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be64(bp + 0), true); sch_type = bp[4]; if (sch_type < 8) { cp = sch_type_arr[sch_type]; if (rsv_s != cp) snprintf(b, blen, "%s block device", cp); else snprintf(b, blen, "%s", cp); } else strcpy(b, rsv_s); sgj_haj_vistr(jsp, jo2p, 4, "Schema type", SGJ_SEP_COLON_1_SPACE, sch_type, true, b); sgj_haj_vi(jsp, jo2p, 4, "Logical blocks per physical block " "exponent", SGJ_SEP_COLON_1_SPACE, 0xf & bp[7], true); sgj_haj_vi_nex(jsp, jo2p, 4, "Logical block length", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be32(bp + 8), true, "unit: byte"); sgj_haj_vi(jsp, jo2p, 4, "Designed last Logical Block Address", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be64(bp + 16), true); sgj_haj_vi_nex(jsp, jo2p, 4, "FMTPINFO", SGJ_SEP_COLON_1_SPACE, (bp[38] >> 6) & 0x3, false, "ForMaT Protection INFOrmation (see Format Unit)"); sgj_haj_vi(jsp, jo2p, 4, "Protection field usage", SGJ_SEP_COLON_1_SPACE, bp[38] & 0x7, false); sgj_haj_vi(jsp, jo2p, 4, "Protection interval exponent", SGJ_SEP_COLON_1_SPACE, bp[39] & 0xf, true); jo3p = sgj_named_subobject_r(jsp, jo2p, "schema_type_specific_information"); switch (sch_type) { case 2: sgj_pr_hr(jsp, " Defines zones for host aware device:\n"); u = bp[40 + 0]; sgj_pr_hr(jsp, " %s: %u.%u %%\n", llczp, u / 10, u % 10); sgj_convert_to_snake_name(llczp, b, blen); sgj_js_nv_ihex_nex(jsp, jo3p, b, u, true, "unit: 1/10 of a " "percent"); u = bp[40 + 1]; sgj_pr_hr(jsp, " %s: %u.%u %%\n", hlczp, u / 10, u % 10); sgj_convert_to_snake_name(hlczp, b, blen); sgj_js_nv_ihex_nex(jsp, jo3p, b, u, true, "unit: 1/10 of a " "percent"); u = sg_get_unaligned_be32(bp + 40 + 12); sgj_haj_vistr(jsp, jo3p, 6, "Logical blocks per zone", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u ? rsv_s : NULL)); break; case 3: sgj_pr_hr(jsp, " Defines zones for host managed device:\n"); u = bp[40 + 0]; sgj_pr_hr(jsp, " %s: %u.%u %%\n", llczp, u / 10, u % 10); sgj_convert_to_snake_name(llczp, b, blen); sgj_js_nv_ihex_nex(jsp, jo3p, b, u, true, "unit: 1/10 of a " "percent"); u = bp[40 + 1]; sgj_pr_hr(jsp, " %s: %u.%u %%\n", hlczp, u / 10, u % 10); sgj_convert_to_snake_name(hlczp, b, blen); sgj_js_nv_ihex_nex(jsp, jo3p, b, u, true, "unit: 1/10 of a " "percent"); u = bp[40 + 3] & 0x7; sgj_haj_vistr(jsp, jo3p, 6, "Designed zone alignment method", SGJ_SEP_COLON_1_SPACE, u, true, get_zone_align_method(u, d, dlen)); ul = sg_get_unaligned_be64(bp + 40 + 4); sgj_haj_vi_nex(jsp, jo3p, 6, "Designed zone starting LBA " "granularity", SGJ_SEP_COLON_1_SPACE, ul, true, "unit: LB"); u = sg_get_unaligned_be32(bp + 40 + 12); sgj_haj_vistr(jsp, jo3p, 6, "Logical blocks per zone", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u ? rsv_s : NULL)); break; case 4: sgj_pr_hr(jsp, " Defines zones for zone domains and realms " "device:\n"); snprintf(b, blen, "%s 0", ztzd); u = bp[40 + 0]; sg_get_zone_type_str((u >> 4) & 0xf, dlen, d); sgj_haj_vistr(jsp, jo3p, 6, b, SGJ_SEP_COLON_1_SPACE, u, true, d); snprintf(b, blen, "%s 1", ztzd); sg_get_zone_type_str(u & 0xf, dlen, d); sgj_haj_vistr(jsp, jo3p, 6, b, SGJ_SEP_COLON_1_SPACE, u, true, d); snprintf(b, blen, "%s 2", ztzd); u = bp[40 + 1]; sg_get_zone_type_str((u >> 4) & 0xf, dlen, d); sgj_haj_vistr(jsp, jo3p, 6, b, SGJ_SEP_COLON_1_SPACE, u, true, d); snprintf(b, blen, "%s 3", ztzd); sg_get_zone_type_str(u & 0xf, dlen, d); sgj_haj_vistr(jsp, jo3p, 6, b, SGJ_SEP_COLON_1_SPACE, u, true, d); u = bp[40 + 3] & 0x7; sgj_haj_vistr(jsp, jo3p, 6, "Designed zone alignment method", SGJ_SEP_COLON_1_SPACE, u, true, get_zone_align_method(u, d, dlen)); ul = sg_get_unaligned_be64(bp + 40 + 4); sgj_haj_vi_nex(jsp, jo3p, 6, "Designed zone starting LBA " "granularity", SGJ_SEP_COLON_1_SPACE, ul, true, "unit: LB"); u = sg_get_unaligned_be32(bp + 40 + 12); sgj_haj_vistr(jsp, jo3p, 6, "Logical blocks per zone", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u ? rsv_s : NULL)); ul = sg_get_unaligned_be64(bp + 40 + 16); sgj_haj_vi_nex(jsp, jo3p, 6, "Designed zone maximum address", SGJ_SEP_COLON_1_SPACE, ul, true, "unit: LBA"); break; default: sgj_pr_hr(jsp, " No schema type specific information\n"); break; } sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } /* VPD_CON_POS_RANGE 0xb9 (added sbc5r01) */ void decode_con_pos_range_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k; uint32_t u; sgj_state * jsp = &op->json_st; const uint8_t * bp; sgj_opaque_p jo2p; if (op->do_hex) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } if (len < 64) { pr2serr("VPD page length too short=%d\n", len); return; } len -= 64; bp = buff + 64; for (k = 0; k < len; k += 32, bp += 32) { jo2p = sgj_new_unattached_object_r(jsp); sgj_haj_vi(jsp, jo2p, 2, "LBA range number", SGJ_SEP_COLON_1_SPACE, bp[0], true); u = bp[1]; sgj_haj_vistr(jsp, jo2p, 4, "Number of storage elements", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u ? nr_s : NULL)); sgj_haj_vi(jsp, jo2p, 4, "Starting LBA", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be64(bp + 8), true); sgj_haj_vi(jsp, jo2p, 4, "Number of LBAs", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be64(bp + 16), true); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } /* This is xcopy(LID4) related: "ROD" == Representation Of Data * Used by VPD_3PARTY_COPY 0x8f ["tpc"] */ static void decode_rod_descriptor(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { uint8_t pdt; uint32_t u; int k, bump; uint64_t ull; const uint8_t * bp = buff; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p; char b[80]; static const int blen = sizeof(b); static const char * ab_pdt = "abnormal use of 'pdt'"; for (k = 0; k < len; k += bump, bp += bump) { jo2p = sgj_new_unattached_object_r(jsp); bump = sg_get_unaligned_be16(bp + 2) + 4; pdt = 0x1f & bp[0]; u = (bp[0] >> 5) & 0x7; sgj_js_nv_i(jsp, jo2p, "descriptor_format", u); if (0 != u) { sgj_pr_hr(jsp, " Unhandled descriptor (format %u, device type " "%u)\n", u, pdt); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); break; } switch (pdt) { case 0: /* Block ROD device type specific descriptor */ sgj_js_nv_ihexstr_nex(jsp, jo2p, "peripheral_device_type", pdt, false, NULL, "Block ROD device " "type specific descriptor", ab_pdt); sgj_haj_vi_nex(jsp, jo2p, 4, "Optimal block ROD length " "granularity", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be16(bp + 6), true, "unit: LB"); ull = sg_get_unaligned_be64(bp + 8); sgj_haj_vi(jsp, jo2p, 4, "Maximum bytes in block ROD", SGJ_SEP_COLON_1_SPACE, ull, true); ull = sg_get_unaligned_be64(bp + 16); sgj_haj_vistr(jsp, jo2p, 4, "Optimal Bytes in block ROD " "transfer", SGJ_SEP_COLON_1_SPACE, ull, true, (SG_LIB_UNBOUNDED_64BIT == ull) ? nl_s : NULL); ull = sg_get_unaligned_be64(bp + 24); sgj_haj_vistr(jsp, jo2p, 4, "Optimal Bytes to token per " "segment", SGJ_SEP_COLON_1_SPACE, ull, true, (SG_LIB_UNBOUNDED_64BIT == ull) ? nl_s : NULL); ull = sg_get_unaligned_be64(bp + 32); sgj_haj_vistr(jsp, jo2p, 4, "Optimal Bytes from token per " "segment", SGJ_SEP_COLON_1_SPACE, ull, true, (SG_LIB_UNBOUNDED_64BIT == ull) ? nl_s : NULL); break; case 1: /* Stream ROD device type specific descriptor */ sgj_js_nv_ihexstr_nex(jsp, jo2p, "peripheral_device_type", pdt, false, NULL, "Stream ROD device " "type specific descriptor", ab_pdt); ull = sg_get_unaligned_be64(bp + 8); sgj_haj_vi(jsp, jo2p, 4, "Maximum bytes in stream ROD", SGJ_SEP_COLON_1_SPACE, ull, true); ull = sg_get_unaligned_be64(bp + 16); snprintf(b, blen, " Optimal Bytes in stream ROD transfer: "); if (SG_LIB_UNBOUNDED_64BIT == ull) sgj_pr_hr(jsp, "%s-1 [no limit]\n", b); else sgj_pr_hr(jsp, "%s%" PRIu64 "\n", b, ull); break; case 3: /* Copy manager ROD device type specific descriptor */ sgj_js_nv_ihexstr_nex(jsp, jo2p, "peripheral_device_type", pdt, false, NULL, "Copy manager ROD " "device type specific descriptor", ab_pdt); sgj_pr_hr(jsp, " Maximum Bytes in processor ROD: %" PRIu64 "\n", sg_get_unaligned_be64(bp + 8)); ull = sg_get_unaligned_be64(bp + 16); snprintf(b, blen, " Optimal Bytes in processor ROD transfer: "); if (SG_LIB_UNBOUNDED_64BIT == ull) sgj_pr_hr(jsp, "%s-1 [no limit]\n", b); else sgj_pr_hr(jsp, "%s%" PRIu64 "\n", b, ull); break; default: sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_device_type", pdt, NULL, "unknown"); break; } sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } struct tpc_desc_type { uint8_t code; const char * name; }; static struct tpc_desc_type tpc_desc_arr[] = { {0x0, "block -> stream"}, {0x1, "stream -> block"}, {0x2, "block -> block"}, {0x3, "stream -> stream"}, {0x4, "inline -> stream"}, {0x5, "embedded -> stream"}, {0x6, "stream -> discard"}, {0x7, "verify CSCD"}, {0x8, "block -> stream"}, {0x9, "stream -> block"}, {0xa, "block -> block"}, {0xb, "block -> stream & application_client"}, {0xc, "stream -> block & application_client"}, {0xd, "block -> block & application_client"}, {0xe, "stream -> stream&application_client"}, {0xf, "stream -> discard&application_client"}, {0x10, "filemark -> tape"}, {0x11, "space -> tape"}, /* obsolete: spc5r02 */ {0x12, "locate -> tape"}, /* obsolete: spc5r02 */ {0x13, "tape -> tape"}, {0x14, "register persistent reservation key"}, {0x15, "third party persistent reservation source I_T nexus"}, {0x16, "block -> block"}, {0x17, "positioning -> tape"}, /* this and next added spc5r02 */ {0x18, "tape -> tape"}, /* loi: logical object identifier */ {0xbe, "ROD <- block range(n)"}, {0xbf, "ROD <- block range(1)"}, {0xe0, "CSCD: FC N_Port_Name"}, {0xe1, "CSCD: FC N_Port_ID"}, {0xe2, "CSCD: FC N_Port_ID with N_Port_Name, checking"}, {0xe3, "CSCD: Parallel interface: I_T"}, {0xe4, "CSCD: Identification Descriptor"}, {0xe5, "CSCD: IPv4"}, {0xe6, "CSCD: Alias"}, {0xe7, "CSCD: RDMA"}, {0xe8, "CSCD: IEEE 1394 EUI-64"}, {0xe9, "CSCD: SAS SSP"}, {0xea, "CSCD: IPv6"}, {0xeb, "CSCD: IP copy service"}, {0xfe, "CSCD: ROD"}, {0xff, "CSCD: extension"}, {0x0, NULL}, }; static const char * get_tpc_desc_name(uint8_t code) { const struct tpc_desc_type * dtp; for (dtp = tpc_desc_arr; dtp->name; ++dtp) { if (code == dtp->code) return dtp->name; } return ""; } struct tpc_rod_type { uint32_t type; const char * name; }; static struct tpc_rod_type tpc_rod_arr[] = { {0x0, "copy manager internal"}, {0x10000, "access upon reference"}, {0x800000, "point in time copy - default"}, {0x800001, "point in time copy - change vulnerable"}, {0x800002, "point in time copy - persistent"}, {0x80ffff, "point in time copy - any"}, {0xffff0001, "block device zero"}, {0x0, NULL}, }; static const char * get_tpc_rod_name(uint32_t rod_type) { const struct tpc_rod_type * rtp; for (rtp = tpc_rod_arr; rtp->name; ++rtp) { if (rod_type == rtp->type) return rtp->name; } return ""; } struct cscd_desc_id_t { uint16_t id; const char * name; }; static struct cscd_desc_id_t cscd_desc_id_arr[] = { /* only values higher than 0x7ff are listed */ {0xc000, "copy src or dst null LU, pdt=0"}, {0xc001, "copy src or dst null LU, pdt=1"}, {0xf800, "copy src or dst in ROD token"}, {0xffff, "copy src or dst is copy manager LU"}, {0x0, NULL}, }; static const char * get_cscd_desc_id_name(uint16_t cscd_desc_id) { const struct cscd_desc_id_t * cdip; for (cdip = cscd_desc_id_arr; cdip->name; ++cdip) { if (cscd_desc_id == cdip->id) return cdip->name; } return ""; } static const char * get_tpc_desc_type_s(uint32_t desc_type) { switch(desc_type) { case 0: return "Block Device ROD Limits"; case 1: return "Supported Commands"; case 4: return "Parameter Data"; case 8: return "Supported Descriptors"; case 0xc: return "Supported CSCD Descriptor IDs"; case 0xd: return "Copy Group Identifier"; case 0x106: return "ROD Token Features"; case 0x108: return "Supported ROD Token and ROD Types"; case 0x8001: return "General Copy Operations"; case 0x9101: return "Stream Copy Operations"; case 0xC001: return "Held Data"; default: if ((desc_type >= 0xE000) && (desc_type <= 0xEFFF)) return "Restricted"; else return "Reserved"; } } /* VPD_3PARTY_COPY 3PC, third party copy 0x8f ["tpc"] */ void decode_3party_copy_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int j, k, m, bump, desc_type, desc_len, sa_len, pdt; uint32_t u, v; uint64_t ull; const uint8_t * bp; const char * cp; const char * dtp; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p = NULL; sgj_opaque_p ja2p = NULL; sgj_opaque_p jo3p = NULL; char b[144]; static const int blen = sizeof(b); if (len < 4) { pr2serr("VPD page length too short=%d\n", len); return; } if (3 == op->do_hex) { hex2stdout(buff, len, -1); return; } pdt = buff[0] & PDT_MASK; len -= 4; bp = buff + 4; for (k = 0; k < len; k += bump, bp += bump) { jo2p = sgj_new_unattached_object_r(jsp); desc_type = sg_get_unaligned_be16(bp); desc_len = sg_get_unaligned_be16(bp + 2); if (op->verbose) sgj_pr_hr(jsp, "Descriptor type=%d [0x%x] , len %d\n", desc_type, desc_type, desc_len); bump = 4 + desc_len; if ((k + bump) > len) { pr2serr("VPD page, short descriptor length=%d, left=%d\n", bump, (len - k)); break; } if (0 == desc_len) goto skip; /* continue plus attach jo2p */ if (2 == op->do_hex) hex2stdout(bp + 4, desc_len, 1); else if (op->do_hex > 2) hex2stdout(bp, bump, 1); else { int csll; dtp = get_tpc_desc_type_s(desc_type); sgj_js_nv_ihexstr(jsp, jo2p, "third_party_copy_descriptor_type", desc_type, NULL, dtp); sgj_js_nv_ihex(jsp, jo2p, "third_party_copy_descriptor_length", desc_len); switch (desc_type) { case 0x0000: /* Required if POPULATE TOKEN (or friend) used */ sgj_pr_hr(jsp, " %s:\n", dtp); u = sg_get_unaligned_be16(bp + 10); sgj_haj_vistr(jsp, jo2p, 2, "Maximum range descriptors", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u) ? nr_s : NULL); u = sg_get_unaligned_be32(bp + 12); if (0 == u) cp = nr_s; else if (SG_LIB_UNBOUNDED_32BIT == u) cp = "No maximum given"; else cp = NULL; sgj_haj_vistr_nex(jsp, jo2p, 2, "Maximum inactivity timeout", SGJ_SEP_COLON_1_SPACE, u, true, cp, "unit: second"); u = sg_get_unaligned_be32(bp + 16); sgj_haj_vistr_nex(jsp, jo2p, 2, "Default inactivity timeout", SGJ_SEP_COLON_1_SPACE, u, true, (0 == u) ? nr_s : NULL, "unit: second"); ull = sg_get_unaligned_be64(bp + 20); sgj_haj_vistr_nex(jsp, jo2p, 2, "Maximum token transfer size", SGJ_SEP_COLON_1_SPACE, ull, true, (0 == ull) ? nr_s : NULL, "unit: LB"); ull = sg_get_unaligned_be64(bp + 28); sgj_haj_vistr_nex(jsp, jo2p, 2, "Optimal transfer count", SGJ_SEP_COLON_1_SPACE, ull, true, (0 == ull) ? nr_s : NULL, "unit: LB"); break; case 0x0001: /* Mandatory (SPC-4) */ sgj_pr_hr(jsp, " %s:\n", "Commands supported list"); ja2p = sgj_named_subarray_r(jsp, jo2p, "commands_supported_list"); j = 0; csll = bp[4]; if (csll >= desc_len) { pr2serr("Command supported list length (%d) >= " "descriptor length (%d), wrong so trim\n", csll, desc_len); csll = desc_len - 1; } while (j < csll) { uint8_t opc, sa; static const char * soc = "supported_operation_code"; static const char * ssa = "supported_service_action"; jo3p = NULL; opc = bp[5 + j]; sa_len = bp[6 + j]; for (m = 0; (m < sa_len) && ((j + m) < csll); ++m) { jo3p = sgj_new_unattached_object_r(jsp); sa = bp[7 + j + m]; sg_get_opcode_sa_name(opc, sa, pdt, blen, b); sgj_pr_hr(jsp, " %s\n", b); sgj_js_nv_s(jsp, jo3p, "name", b); sgj_js_nv_ihex(jsp, jo3p, soc, opc); sgj_js_nv_ihex(jsp, jo3p, ssa, sa); sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p); } if (0 == sa_len) { jo3p = sgj_new_unattached_object_r(jsp); sg_get_opcode_name(opc, pdt, blen, b); sgj_pr_hr(jsp, " %s\n", b); sgj_js_nv_s(jsp, jo3p, "name", b); sgj_js_nv_ihex(jsp, jo3p, soc, opc); sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p); } else if (m < sa_len) pr2serr("Supported service actions list length (%d) " "is too large\n", sa_len); j += m + 2; } break; case 0x0004: sgj_pr_hr(jsp, " %s:\n", dtp); sgj_haj_vi(jsp, jo2p, 2, "Maximum CSCD descriptor count", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be16(bp + 8), true); sgj_haj_vi(jsp, jo2p, 2, "Maximum segment descriptor count", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be16(bp + 10), true); sgj_haj_vi(jsp, jo2p, 2, "Maximum descriptor list length", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be32(bp + 12), true); sgj_haj_vi(jsp, jo2p, 2, "Maximum inline data length", SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be32(bp + 17), true); break; case 0x0008: sgj_pr_hr(jsp, " Supported descriptors:\n"); ja2p = sgj_named_subarray_r(jsp, jo2p, "supported_descriptor_list"); for (j = 0; j < bp[4]; j++) { bool found_name; jo3p = sgj_new_unattached_object_r(jsp); u = bp[5 + j]; cp = get_tpc_desc_name(u); found_name = (strlen(cp) > 0); if (found_name) sgj_pr_hr(jsp, " %s [0x%x]\n", cp, u); else sgj_pr_hr(jsp, " 0x%x\n", u); sgj_js_nv_s(jsp, jo3p, "name", found_name ? cp : nr_s); sgj_js_nv_ihex(jsp, jo3p, "code", u); sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p); } break; case 0x000C: sgj_pr_hr(jsp, " Supported CSCD IDs (above 0x7ff):\n"); ja2p = sgj_named_subarray_r(jsp, jo2p, "supported_cscd_" "descriptor_id_list"); v = sg_get_unaligned_be16(bp + 4); for (j = 0; j < (int)v; j += 2) { bool found_name; jo3p = sgj_new_unattached_object_r(jsp); u = sg_get_unaligned_be16(bp + 6 + j); cp = get_cscd_desc_id_name(u); found_name = (strlen(cp) > 0); if (found_name) sgj_pr_hr(jsp, " %s [0x%04x]\n", cp, u); else sgj_pr_hr(jsp, " 0x%04x\n", u); sgj_js_nv_s(jsp, jo3p, "name", found_name ? cp : nr_s); sgj_js_nv_ihex(jsp, jo3p, "id", u); sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p); } break; case 0x000D: sgj_pr_hr(jsp, " Copy group identifier:\n"); u = bp[4]; sg_t10_uuid_desig2str(bp + 5, u, 1 /* c_set */, false, true, NULL, blen, b); sgj_pr_hr(jsp, " Locally assigned UUID: %s", b); sgj_js_nv_s(jsp, jo2p, "locally_assigned_uuid", b); break; case 0x0106: sgj_pr_hr(jsp, " ROD token features:\n"); sgj_haj_vi(jsp, jo2p, 2, "Remote tokens", SGJ_SEP_COLON_1_SPACE, bp[4] & 0x0f, true); u = sg_get_unaligned_be32(bp + 16); sgj_pr_hr(jsp, " Minimum token lifetime: %u seconds\n", u); sgj_js_nv_ihex_nex(jsp, jo2p, "minimum_token_lifetime", u, true, "unit: second"); u = sg_get_unaligned_be32(bp + 20); sgj_pr_hr(jsp, " Maximum token lifetime: %u seconds\n", u); sgj_js_nv_ihex_nex(jsp, jo2p, "maximum_token_lifetime", u, true, "unit: second"); u = sg_get_unaligned_be32(bp + 24); sgj_haj_vi_nex(jsp, jo2p, 2, "Maximum token inactivity " "timeout", SGJ_SEP_COLON_1_SPACE, u, true, "unit: second"); u = sg_get_unaligned_be16(bp + 46); ja2p = sgj_named_subarray_r(jsp, jo2p, "rod_device_type_specific_features_descriptor_list"); decode_rod_descriptor(bp + 48, u, op, ja2p); break; case 0x0108: sgj_pr_hr(jsp, " Supported ROD token and ROD types:\n"); ja2p = sgj_named_subarray_r(jsp, jo2p, "rod_type_" "descriptor_list"); for (j = 0; j < sg_get_unaligned_be16(bp + 6); j+= 64) { bool found_name; jo3p = sgj_new_unattached_object_r(jsp); u = sg_get_unaligned_be32(bp + 8 + j); cp = get_tpc_rod_name(u); found_name = (strlen(cp) > 0); if (found_name > 0) sgj_pr_hr(jsp, " ROD type: %s [0x%x]\n", cp, u); else sgj_pr_hr(jsp, " ROD type: 0x%x\n", u); sgj_js_nv_ihexstr(jsp, jo3p, "rod_type", u, NULL, found_name ? cp : NULL); u = bp[8 + j + 4]; sgj_pr_hr(jsp, " ECPY_INT: %s\n", (u & 0x80) ? y_s : n_s); sgj_js_nv_ihex_nex(jsp, jo3p, "ecpy_int", !!(0x80 & u), false, "Extended CoPY INTernal rods"); sgj_pr_hr(jsp, " Token in: %s\n", (u & 0x2) ? y_s : n_s); sgj_js_nv_i(jsp, jo3p, "token_in", !!(0x2 & u)); sgj_pr_hr(jsp, " Token out: %s\n", (u & 0x1) ? y_s : n_s); sgj_js_nv_i(jsp, jo3p, "token_out", !!(0x2 & u)); u = sg_get_unaligned_be16(bp + 8 + j + 6); sgj_haj_vi(jsp, jo3p, 4, "Preference indicator", SGJ_SEP_COLON_1_SPACE, u, true); sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p); } break; case 0x8001: /* Mandatory (SPC-4) */ sgj_pr_hr(jsp, " General copy operations:\n"); u = sg_get_unaligned_be32(bp + 4); sgj_haj_vi(jsp, jo2p, 2, "Total concurrent copies", SGJ_SEP_COLON_1_SPACE, u, true); u = sg_get_unaligned_be32(bp + 8); sgj_haj_vi(jsp, jo2p, 2, "Maximum identified concurrent " "copies", SGJ_SEP_COLON_1_SPACE, u, true); u = sg_get_unaligned_be32(bp + 12); sgj_haj_vi_nex(jsp, jo2p, 2, "Maximum segment length", SGJ_SEP_COLON_1_SPACE, u, true, "unit: byte"); u = bp[16]; /* field is power of 2 */ sgj_haj_vi_nex(jsp, jo2p, 2, "Data segment granularity", SGJ_SEP_COLON_1_SPACE, u, true, "unit: 2^val LB"); u = bp[17]; /* field is power of 2 */ sgj_haj_vi_nex(jsp, jo2p, 2, "Inline data granularity", SGJ_SEP_COLON_1_SPACE, u, true, "unit: 2^val LB"); break; case 0x9101: sgj_pr_hr(jsp, " Stream copy operations:\n"); u = sg_get_unaligned_be32(bp + 4); sgj_haj_vi_nex(jsp, jo2p, 2, "Maximum stream device transfer " "size", SGJ_SEP_COLON_1_SPACE, u, true, "unit: byte"); break; case 0xC001: sgj_pr_hr(jsp, " Held data:\n"); u = sg_get_unaligned_be32(bp + 4); sgj_haj_vi_nex(jsp, jo2p, 2, "Held data limit", SGJ_SEP_COLON_1_SPACE, u, true, "unit: byte; (lower limit: minimum)"); sgj_haj_vi_nex(jsp, jo2p, 2, "Held data granularity", SGJ_SEP_COLON_1_SPACE, bp[8], true, "unit: 2^val byte"); break; default: pr2serr("Unexpected type=%d\n", desc_type); hex2stderr(bp, bump, 1); break; } } skip: sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); jo2p = NULL; } if (jo2p) sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } /* VPD_PROTO_LU 0x90 ["pslu"] */ void decode_proto_lu_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k, bump, rel_port, desc_len, proto; const uint8_t * bp; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p = NULL; char b[128]; static const int blen = sizeof(b); if ((1 == op->do_hex) || (op->do_hex > 2)) { hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1); return; } if (len < 4) { pr2serr("VPD page length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0; k < len; k += bump, bp += bump) { jo2p = sgj_new_unattached_object_r(jsp); rel_port = sg_get_unaligned_be16(bp); sgj_haj_vi(jsp, jo2p, 2, "Relative port", SGJ_SEP_COLON_1_SPACE, rel_port, true); proto = bp[2] & 0xf; sg_get_trans_proto_str(proto, blen, b); sgj_haj_vistr(jsp, jo2p, 4, "Protocol identifier", SGJ_SEP_COLON_1_SPACE, proto, false, b); desc_len = sg_get_unaligned_be16(bp + 6); bump = 8 + desc_len; if ((k + bump) > len) { pr2serr("Protocol-specific logical unit information VPD page, " "short descriptor length=%d, left=%d\n", bump, (len - k)); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); return; } if (0 == desc_len) goto again; if (2 == op->do_hex) { hex2stdout(bp + 8, desc_len, 1); goto again; } switch (proto) { case TPROTO_SAS: sgj_haj_vi(jsp, jo2p, 2, "TLR control supported", SGJ_SEP_COLON_1_SPACE, !!(bp[8] & 0x1), false); break; default: pr2serr("Unexpected proto=%d\n", proto); hex2stderr(bp, bump, 1); break; } again: sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } /* VPD_PROTO_PORT 0x91 ["pspo"] */ void decode_proto_port_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { bool pds, ssp_pers; int k, j, bump, rel_port, desc_len, proto, phy; const uint8_t * bp; const uint8_t * pidp; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p = NULL; sgj_opaque_p ja2p = NULL; sgj_opaque_p jo3p = NULL; char b[128]; static const int blen = sizeof(b); if ((1 == op->do_hex) || (op->do_hex > 2)) { hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1); return; } if (len < 4) { pr2serr("VPD page length too short=%d\n", len); return; } len -= 4; bp = buff + 4; for (k = 0; k < len; k += bump, bp += bump) { jo2p = sgj_new_unattached_object_r(jsp); rel_port = sg_get_unaligned_be16(bp); sgj_haj_vi(jsp, jo2p, 2, "Relative port", SGJ_SEP_COLON_1_SPACE, rel_port, true); proto = bp[2] & 0xf; sg_get_trans_proto_str(proto, blen, b); sgj_haj_vistr(jsp, jo2p, 4, "Protocol identifier", SGJ_SEP_COLON_1_SPACE, proto, false, b); desc_len = sg_get_unaligned_be16(bp + 6); bump = 8 + desc_len; if ((k + bump) > len) { pr2serr("VPD page, short descriptor length=%d, left=%d\n", bump, (len - k)); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); return; } if (0 == desc_len) goto again; if (2 == op->do_hex) { hex2stdout(bp + 8, desc_len, 1); goto again; } switch (proto) { case TPROTO_SAS: /* page added in spl3r02 */ pds = !!(bp[3] & 0x1); sgj_pr_hr(jsp, " power disable supported (pwr_d_s)=%d\n", pds); sgj_js_nv_ihex_nex(jsp, jo2p, "pwr_d_s", pds, false, "PoWeR Disable Supported"); ja2p = sgj_named_subarray_r(jsp, jo2p, "sas_phy_information_descriptor_list"); pidp = bp + 8; for (j = 0; j < desc_len; j += 4, pidp += 4) { jo3p = sgj_new_unattached_object_r(jsp); phy = pidp[1]; ssp_pers = !!(0x1 & pidp[2]); sgj_pr_hr(jsp, " phy id=%d, SSP persistent capable=%d\n", phy, ssp_pers); sgj_js_nv_ihex(jsp, jo3p, "phy_identifier", phy); sgj_js_nv_i(jsp, jo3p, "ssp_persistent_capable", ssp_pers); sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p); } break; default: pr2serr("Unexpected proto=%d\n", proto); hex2stderr(bp, bump, 1); break; } again: sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } /* VPD_LB_PROTECTION 0xb5 (SSC) [added in ssc5r02a] */ void decode_lb_protection_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap) { int k, bump; const uint8_t * bp; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p = NULL; if ((1 == op->do_hex) || (op->do_hex > 2)) { hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); return; } if (len < 8) { pr2serr("VPD page length too short=%d\n", len); return; } len -= 8; bp = buff + 8; for (k = 0; k < len; k += bump, bp += bump) { jo2p = sgj_new_unattached_object_r(jsp); bump = 1 + bp[0]; sgj_pr_hr(jsp, " method: %d, info_len: %d, LBP_W_C=%d, LBP_R_C=%d, " "RBDP_C=%d\n", bp[1], 0x3f & bp[2], !!(0x80 & bp[3]), !!(0x40 & bp[3]), !!(0x20 & bp[3])); sgj_js_nv_ihex(jsp, jo2p, "logical_block_protection_method", bp[1]); sgj_js_nv_ihex_nex(jsp, jo2p, "logical_block_protection_information_length", 0x3f & bp[2], true, "unit: byte"); sgj_js_nv_ihex_nex(jsp, jo2p, "lbp_w_c", !!(0x80 & bp[3]), false, "Logical Blocks Protected during Write supported"); sgj_js_nv_ihex_nex(jsp, jo2p, "lbp_r_c", !!(0x40 & bp[3]), false, "Logical Blocks Protected during Read supported"); sgj_js_nv_ihex_nex(jsp, jo2p, "rbdp_c", !!(0x20 & bp[3]), false, "Recover Buffered Data Protected supported"); if ((k + bump) > len) { pr2serr("Logical block protection VPD page, short " "descriptor length=%d, left=%d\n", bump, (len - k)); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); return; } sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } } /* VPD_TA_SUPPORTED 0xb2 ["tas"] */ void decode_tapealert_supported_vpd(const uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { bool have_ta_strs = !! sg_lib_tapealert_strs[0]; int k, mod, div, n; unsigned int supp; sgj_state * jsp = &op->json_st; char b[144]; char d[64]; static const int blen = sizeof(b); if (len < 12) { pr2serr("VPD page length too short=%d\n", len); return; } b[0] ='\0'; for (k = 1, n = 0; k < 0x41; ++k) { mod = ((k - 1) % 8); div = (k - 1) / 8; supp = !! (buff[4 + div] & (1 << (7 - mod))); if (jsp->pr_as_json) { snprintf(d, sizeof(d), "flag%02xh", k); if (have_ta_strs) sgj_js_nv_ihex_nex(jsp, jop, d, supp, false, sg_lib_tapealert_strs[k]); else sgj_js_nv_i(jsp, jop, d, supp); } if (0 == mod) { if (div > 0) { sgj_pr_hr(jsp, "%s\n", b); n = 0; } n += sg_scnpr(b + n, blen - n, " Flag%02Xh: %d", k, supp); } else n += sg_scnpr(b + n, blen - n, " %02Xh: %d", k, supp); } sgj_pr_hr(jsp, "%s\n", b); } /* * Some of the vendor specific VPD pages are common as well. So place them here * to save on code duplication. */ static const char * lun_state_arr[] = { "LUN not bound or LUN_Z report", "LUN bound, but not owned by this SP", "LUN bound and owned by this SP", }; static const char * ip_mgmt_arr[] = { "No IP access", "Reserved (undefined)", "via IPv4", "via IPv6", }; static const char * sp_arr[] = { "SP A", "SP B", }; static const char * lun_op_arr[] = { "Normal operations", "I/O Operations being rejected, SP reboot or NDU in progress", }; static const char * failover_mode_arr[] = { "Legacy mode 0", "Unknown mode (1)", "Unknown mode (2)", "Unknown mode (3)", "Active/Passive (PNR) mode 1", "Unknown mode (5)", "Active/Active (ALUA) mode 4", "Unknown mode (7)", "Legacy mode 2", "Unknown mode (9)", "Unknown mode (10)", "Unknown mode (11)", "Unknown mode (12)", "Unknown mode (13)", "AIX Active/Passive (PAR) mode 3", "Unknown mode (15)", }; /* VPD_UPR_EMC,VPD_V_UPR_EMC 0xc0 ["upr","upr"] */ void decode_upr_vpd_c0_emc(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { uint8_t uc; int k, n, ip_mgmt, vpp80, lun_z; sgj_state * jsp = &op->json_st; const char * cp; const char * c2p; char b[256]; static const int blen = sizeof(b); if (len < 3) { pr2serr("EMC upr VPD page [0xc0]: length too short=%d\n", len); return; } if (op->do_hex) { hex2stdout(buff, len, no_ascii_4hex(op)); return; } if (buff[9] != 0x00) { pr2serr("Unsupported page revision %d, decoding not possible.\n", buff[9]); return; } for (k = 0, n = 0; k < 16; ++k) n += sg_scnpr(b + n, blen - n, "%02x", buff[10 + k]); sgj_haj_vs(jsp, jop, 2, "LUN WWN", SGJ_SEP_COLON_1_SPACE, b); snprintf(b, blen, "%.*s", buff[49], buff + 50); sgj_haj_vs(jsp, jop, 2, "Array Serial Number", SGJ_SEP_COLON_1_SPACE, b); if (buff[4] > 0x02) snprintf(b, blen, "Unknown (%x)", buff[4]); else snprintf(b, blen, "%s", lun_state_arr[buff[4]]); sgj_haj_vistr(jsp, jop, 2, "LUN State", SGJ_SEP_COLON_1_SPACE, buff[4], true, b); uc = buff[8]; n = 0; if (uc > 0x01) n += sg_scnpr(b + n, blen - n, "Unknown SP (%x)", uc); else n += sg_scnpr(b + n, blen - n, "%s", sp_arr[uc]); sgj_js_nv_ihexstr(jsp, jop, "path_connects_to", uc, NULL, b); n += sg_scnpr(b + n, blen - n, ", Port Number: %u", buff[7]); sgj_pr_hr(jsp, " This path connects to: %s\n", b); sgj_js_nv_ihex(jsp, jop, "port_number", buff[7]); if (buff[5] > 0x01) snprintf(b, blen, "Unknown (%x)\n", buff[5]); else snprintf(b, blen, "%s\n", sp_arr[buff[5]]); sgj_haj_vistr(jsp, jop, 2, "Default owner", SGJ_SEP_COLON_1_SPACE, buff[5], true, b); cp = (buff[6] & 0x40) ? "supported" : "not supported"; sgj_pr_hr(jsp, " NO_ATF: %s, Access Logix: %s\n", buff[6] & 0x80 ? "set" : "not set", cp); sgj_js_nv_i(jsp, jop, "no_atf", !! (buff[6] & 0x80)); sgj_js_nv_istr(jsp, jop, "access_logix", !! (buff[6] & 0x40), NULL, cp); ip_mgmt = (buff[6] >> 4) & 0x3; cp = ip_mgmt_arr[ip_mgmt]; sgj_pr_hr(jsp, " SP IP Management Mode: %s\n", cp); sgj_js_nv_istr(jsp, jop, "sp_ip_management_mode", !! ip_mgmt, NULL, cp); if (ip_mgmt == 2) { snprintf(b, blen, "%u.%u.%u.%u", buff[44], buff[45], buff[46], buff[47]); sgj_pr_hr(jsp, " SP IPv4 address: %s\n", b); sgj_js_nv_s(jsp, jop, "sp_ipv4_address", b); } else if (ip_mgmt == 3) { printf(" SP IPv6 address: "); n = 0; for (k = 0; k < 16; ++k) n += sg_scnpr(b + n, blen - n, "%02x", buff[32 + k]); sgj_pr_hr(jsp, " SP IPv6 address: %s\n", b); sgj_js_nv_hex_bytes(jsp, jop, "sp_ipv6_address", buff + 32, 16); } k = buff[28] & 0x0f; sgj_pr_hr(jsp, " System Type: %x, Failover mode: %s\n", buff[27], failover_mode_arr[k]); sgj_js_nv_ihex(jsp, jop, "system_type", buff[27]); sgj_js_nv_ihexstr(jsp, jop, "failover_mode", k, NULL, failover_mode_arr[k]); vpp80 = buff[30] & 0x08; lun_z = buff[30] & 0x04; cp = vpp80 ? "array serial#" : "LUN serial#"; c2p = lun_z ? "Set to 1" : "Unknown"; sgj_pr_hr(jsp, " Inquiry VPP 0x80 returns: %s, Arraycommpath: %s\n", cp, c2p); sgj_js_nv_istr(jsp, jop, "inquiry_vpp_0x80_returns", !! vpp80, NULL, cp); sgj_js_nv_istr(jsp, jop, "arraycommpath", !! lun_z, NULL, c2p); cp = buff[48] > 1 ? "undefined" : lun_op_arr[buff[48]]; sgj_pr_hr(jsp, " Lun operations: %s\n", cp); sgj_js_nv_istr(jsp, jop, "lun_operations", 0x1 & buff[48], NULL, cp); return; } /* VPD_RDAC_VERS,VPD_V_SVER_RDAC 0xc2 ["rdac_vers", "swr4"] */ void decode_rdac_vpd_c2(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { int i, n, v, r, m, p, d, y, num_part; sgj_state * jsp = &op->json_st; sgj_opaque_p jo2p = NULL; sgj_opaque_p jap = NULL; // const char * cp; // const char * c2p; char b[256]; static const int blen = sizeof(b); char part[5]; if (len < 3) { pr2serr("Software Version VPD page length too short=%d\n", len); return; } if (op->do_hex) { hex2stdout(buff, len, no_ascii_4hex(op)); return; } if (buff[4] != 's' && buff[5] != 'w' && buff[6] != 'r') { pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n", buff[4], buff[5], buff[6], buff[7]); return; } snprintf(b, blen, "%02x.%02x.%02x", buff[8], buff[9], buff[10]); sgj_haj_vs(jsp, jop, 2, "Software Version", SGJ_SEP_COLON_1_SPACE, b); snprintf(b, blen, "%02d/%02d/%02d\n", buff[11], buff[12], buff[13]); sgj_haj_vs(jsp, jop, 2, "Software Date", SGJ_SEP_COLON_1_SPACE, b); n = 0; n += sg_scnpr(b + n, blen - n, " Features:"); if (buff[14] & 0x01) n += sg_scnpr(b + n, blen - n, " Dual Active,"); if (buff[14] & 0x02) n += sg_scnpr(b + n, blen - n, " Series 3,"); if (buff[14] & 0x04) n += sg_scnpr(b + n, blen - n, " Multiple Sub-enclosures,"); if (buff[14] & 0x08) n += sg_scnpr(b + n, blen - n, " DCE/DRM/DSS/DVE,"); if (buff[14] & 0x10) n += sg_scnpr(b + n, blen - n, " Asymmetric Logical Unit Access,"); sgj_pr_hr(jsp, "%s\n", b); if (jsp->pr_as_json) { jo2p = sgj_snake_named_subobject_r(jsp, jop, "features"); sgj_js_nv_i(jsp, jo2p, "dual_active", !! (buff[14] & 0x01)); sgj_js_nv_i(jsp, jo2p, "series_3", !! (buff[14] & 0x02)); sgj_js_nv_i(jsp, jo2p, "multiple_sub_enclosures", !! (buff[14] & 0x04)); sgj_js_nv_i(jsp, jo2p, "dcm_drm_dss_dve", !! (buff[14] & 0x08)); sgj_js_nv_i(jsp, jo2p, "asymmetric_logical_unit_access", !! (buff[14] & 0x10)); } sgj_haj_vi(jsp, jop, 2, "Maximum number of LUNS", SGJ_SEP_COLON_1_SPACE, buff[15], true); num_part = (len - 12) / 16; n = 16; printf(" Partitions: %d\n", num_part); sgj_haj_vi(jsp, jop, 2, "Partitions", SGJ_SEP_COLON_1_SPACE, num_part, true); if (num_part > 0) jap = sgj_named_subarray_r(jsp, jop, "partition_list"); for (i = 0; i < num_part; i++) { memset(part,0, 5); memcpy(part, &buff[n], 4); sgj_pr_hr(jsp, " Name: %s\n", part); if (jsp->pr_as_json) { jo2p = sgj_new_unattached_object_r(jsp); sgj_js_nv_s(jsp, jo2p, "name", part); } n += 4; v = buff[n++]; r = buff[n++]; m = buff[n++]; p = buff[n++]; snprintf(b, blen, "%d.%d.%d.%d", v, r, m, p); sgj_pr_hr(jsp, " Version: %s\n", b); if (jsp->pr_as_json) sgj_js_nv_s(jsp, jo2p, "version", b); m = buff[n++]; d = buff[n++]; y = buff[n++]; snprintf(b, blen, "%d/%d/%d\n", m, d, y); sgj_pr_hr(jsp, " Date: %s\n", b); if (jsp->pr_as_json) { sgj_js_nv_s(jsp, jo2p, "date", b); sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); } n += 5; } return; } static char * decode_rdac_vpd_c9_aas_s(uint8_t aas, char * b, int blen) { // snprintf(" Asymmetric Access State:"); switch(aas & 0x0F) { case 0x0: snprintf(b, blen, "Active/Optimized"); break; case 0x1: snprintf(b, blen, "Active/Non-Optimized"); break; case 0x2: snprintf(b, blen, "Standby"); break; case 0x3: snprintf(b, blen, "Unavailable"); break; case 0xE: snprintf(b, blen, "Offline"); break; case 0xF: snprintf(b, blen, "Transitioning"); break; default: snprintf(b, blen, "(unknown)"); break; } return b; } static char * decode_rdac_vpd_c9_vs_s(uint8_t vendor, char * b, int blen) { // printf(" Vendor Specific Field:"); switch(vendor) { case 0x01: snprintf(b, blen, "Operating normally"); break; case 0x02: snprintf(b, blen, "Non-responsive to queries"); break; case 0x03: snprintf(b, blen, "Controller being held in reset"); break; case 0x04: snprintf(b, blen, "Performing controller firmware download (1st " "controller)"); break; case 0x05: snprintf(b, blen, "Performing controller firmware download (2nd " "controller)"); break; case 0x06: snprintf(b, blen, "Quiesced as a result of an administrative request"); break; case 0x07: snprintf(b, blen, "Service mode as a result of an administrative request"); break; case 0xFF: snprintf(b, blen, "Details are not available"); break; default: snprintf(b, blen, "(unknown)"); break; } return b; } /* VPD_RDAC_VAC,VPD_V_VAC_RDAC 0xc9 ["rdac_vac", "vac1"] */ void decode_rdac_vpd_c9(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) { bool vav; int n, n_hold; sgj_state * jsp = &op->json_st; char b[196]; static const int blen = sizeof(b); if (len < 3) { pr2serr("Volume Access Control VPD page length too short=%d\n", len); return; } if (op->do_hex) { hex2stdout(buff, len, no_ascii_4hex(op)); return; } if (buff[4] != 'v' && buff[5] != 'a' && buff[6] != 'c') { pr2serr("Invalid page identifier %c%c%c%c, decoding " "not possible.\n" , buff[4], buff[5], buff[6], buff[7]); return; } if (buff[7] != '1') { pr2serr("Invalid page version '%c' (should be 1)\n", buff[7]); } n = ((buff[8] & 0xE0) == 0xE0 ); if (n) { sgj_pr_hr(jsp, " IOShipping (ALUA): Enabled\n"); sgj_js_nv_ihexstr_nex(jsp, jop, "ioshipping", n, true, NULL, "Enabled", "a.k.a. ALUA (Asymmetric Logical Unit Access)"); } else { n = 0; n = snprintf(b, blen, " AVT:"); n_hold = n; if (buff[8] & 0x80) { n += sg_scnpr(b + n, blen - n, " Enabled"); if (buff[8] & 0x40) n += sg_scnpr(b + n, blen - n, " (Allow reads on sector 0)"); sgj_pr_hr(jsp, "%s\n", b); sgj_js_nv_ihexstr(jsp, jop, "avt", buff[8], NULL, b + n_hold); } else { sgj_pr_hr(jsp, "%s: Disabled\n", b); sgj_js_nv_ihexstr(jsp, jop, "avt", buff[8], NULL, "Disabled"); } } vav = !! (0x1 & buff[8]); sgj_haj_vistr(jsp, jop, 2, "Volume access via", SGJ_SEP_COLON_1_SPACE, (int)vav, false, (vav ? "primary controller" : "alternate controller")); if (buff[8] & 0x08) { n = buff[15] & 0xf; // printf(" Path priority: %d ", n); switch (n) { case 0x1: snprintf(b, blen, "(preferred path)"); break; case 0x2: snprintf(b, blen, "(secondary path)"); break; default: snprintf(b, blen, "(unknown)"); break; } sgj_haj_vistr(jsp, jop, 2, "Path priority", SGJ_SEP_COLON_1_SPACE, n, true, b); // printf(" Preferred Path Auto Changeable:"); n = buff[14] & 0x3C; switch (n) { case 0x14: snprintf(b, blen, "No (User Disabled and Host Type Restricted)"); break; case 0x18: snprintf(b, blen, "No (User Disabled)"); break; case 0x24: snprintf(b, blen, "No (Host Type Restricted)"); break; case 0x28: snprintf(b, blen, "Yes"); break; default: snprintf(b, blen, "(Unknown)"); break; } sgj_haj_vistr(jsp, jop, 2, "Preferred path auto changeable", SGJ_SEP_COLON_1_SPACE, n, true, b); n = buff[14] & 0x03; // printf(" Implicit Failback:"); switch (n) { case 0x1: snprintf(b, blen, "Disabled"); break; case 0x2: snprintf(b, blen, "Enabled"); break; default: snprintf(b, blen, "(Unknown)"); break; } sgj_haj_vistr(jsp, jop, 2, "Implicit failback", SGJ_SEP_COLON_1_SPACE, n, false, b); } else { n = buff[9] & 0xf; // printf(" Path priority: %d ", buff[9] & 0xf); switch (n) { case 0x1: snprintf(b, blen, "(preferred path)"); break; case 0x2: snprintf(b, blen, "(secondary path)"); break; default: snprintf(b, blen, "(unknown)"); break; } sgj_haj_vistr(jsp, jop, 2, "Path priority", SGJ_SEP_COLON_1_SPACE, n, false, b); } n = !! (buff[8] & 0x80); sgj_haj_vi(jsp, jop, 2, "Target port group present", SGJ_SEP_COLON_1_SPACE, n, false); if (n) { sgj_opaque_p jo2p = NULL; sgj_opaque_p jo3p = NULL; static const char * tpg_s = "Target port group data"; static const char * aas_s = "Asymmetric access state"; static const char * vs_s = "Vendor specific field"; char d1[80]; char d2[80]; sgj_pr_hr(jsp, " Target Port Group Data (This controller):\n"); decode_rdac_vpd_c9_aas_s(buff[10], d1, sizeof(d1)); decode_rdac_vpd_c9_vs_s(buff[11], d2, sizeof(d2)); sgj_pr_hr(jsp, " %s: %s\n", aas_s, d1); sgj_pr_hr(jsp, " %s: %s\n", vs_s, d2); if (jsp->pr_as_json) { jo2p = sgj_snake_named_subobject_r(jsp, jop, tpg_s); jo3p = sgj_snake_named_subobject_r(jsp, jo2p, "this_controller"); sgj_convert_to_snake_name(aas_s, b, blen); sgj_js_nv_ihexstr(jsp, jo3p, b, buff[10], NULL, d1); sgj_convert_to_snake_name(vs_s, b, blen); sgj_js_nv_ihexstr(jsp, jo3p, b, buff[11], NULL, d2); } sgj_pr_hr(jsp, " Target Port Group Data (Alternate controller):\n"); // decode_rdac_vpd_c9_rtpg_data(buff[12], buff[13]); decode_rdac_vpd_c9_aas_s(buff[12], d1, sizeof(d1)); decode_rdac_vpd_c9_vs_s(buff[13], d2, sizeof(d2)); sgj_pr_hr(jsp, " %s: %s\n", aas_s, d1); sgj_pr_hr(jsp, " %s: %s\n", vs_s, d2); if (jsp->pr_as_json) { jo2p = sgj_snake_named_subobject_r(jsp, jop, tpg_s); jo3p = sgj_snake_named_subobject_r(jsp, jo2p, "alternate_controller"); sgj_convert_to_snake_name(aas_s, b, blen); sgj_js_nv_ihexstr(jsp, jo3p, b, buff[12], NULL, d1); sgj_convert_to_snake_name(vs_s, b, blen); sgj_js_nv_ihexstr(jsp, jo3p, b, buff[13], NULL, d2); } } }