diff options
Diffstat (limited to 'freebsd/sys/cam/scsi/scsi_all.c')
-rw-r--r-- | freebsd/sys/cam/scsi/scsi_all.c | 2726 |
1 files changed, 2604 insertions, 122 deletions
diff --git a/freebsd/sys/cam/scsi/scsi_all.c b/freebsd/sys/cam/scsi/scsi_all.c index 7bb0425d..0cb7118a 100644 --- a/freebsd/sys/cam/scsi/scsi_all.c +++ b/freebsd/sys/cam/scsi/scsi_all.c @@ -50,11 +50,13 @@ __FBSDID("$FreeBSD$"); #include <sys/malloc.h> #include <sys/mutex.h> #include <sys/sysctl.h> +#include <sys/ctype.h> #else #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <ctype.h> #endif #include <cam/cam.h> @@ -116,6 +118,7 @@ static void fetchtableentries(int sense_key, int asc, int ascq, struct scsi_inquiry_data *, const struct sense_key_table_entry **, const struct asc_table_entry **); + #ifdef _KERNEL static void init_scsi_delay(void); static int sysctl_scsi_delay(SYSCTL_HANDLER_ARGS); @@ -159,7 +162,7 @@ static struct scsi_op_quirk_entry scsi_op_quirk_table[] = { * feel free to change this quirk entry. */ {T_CDROM, SIP_MEDIA_REMOVABLE, "PLEXTOR", "CD-ROM PX*", "*"}, - sizeof(plextor_cd_ops)/sizeof(struct op_table_entry), + nitems(plextor_cd_ops), plextor_cd_ops } }; @@ -180,7 +183,7 @@ static struct op_table_entry scsi_op_codes[] = { * * SCSI Operation Codes * Numeric Sorted Listing - * as of 3/11/08 + * as of 5/26/15 * * D - DIRECT ACCESS DEVICE (SBC-2) device column key * .T - SEQUENTIAL ACCESS DEVICE (SSC-2) ----------------- @@ -478,7 +481,8 @@ static struct op_table_entry scsi_op_codes[] = { */ /* 88 MM O O O READ(16) */ { 0x88, D | T | W | O | B, "READ(16)" }, - /* 89 */ + /* 89 O COMPARE AND WRITE*/ + { 0x89, D, "COMPARE AND WRITE" }, /* 8A OM O O O WRITE(16) */ { 0x8A, D | T | W | O | B, "WRITE(16)" }, /* 8B O ORWRITE */ @@ -505,20 +509,25 @@ static struct op_table_entry scsi_op_codes[] = { { 0x93, D, "WRITE SAME(16)" }, /* 93 M ERASE(16) */ { 0x93, T, "ERASE(16)" }, - /* 94 [usage proposed by SCSI Socket Services project] */ - /* 95 [usage proposed by SCSI Socket Services project] */ - /* 96 [usage proposed by SCSI Socket Services project] */ - /* 97 [usage proposed by SCSI Socket Services project] */ + /* 94 O ZBC OUT */ + { 0x94, ALL, "ZBC OUT" }, + /* 95 O ZBC IN */ + { 0x95, ALL, "ZBC IN" }, + /* 96 */ + /* 97 */ /* 98 */ /* 99 */ - /* 9A */ - /* 9B */ - /* 9C */ - /* 9D */ + /* 9A O WRITE STREAM(16) */ + { 0x9A, D, "WRITE STREAM(16)" }, + /* 9B OOOOOOOOOO OOO READ BUFFER(16) */ + { 0x9B, ALL & ~(B) , "READ BUFFER(16)" }, + /* 9C O WRITE ATOMIC(16) */ + { 0x9C, D, "WRITE ATOMIC(16)" }, + /* 9D SERVICE ACTION BIDIRECTIONAL */ + { 0x9D, ALL, "SERVICE ACTION BIDIRECTIONAL" }, /* XXX KDM ALL for this? op-num.txt defines it for none.. */ /* 9E SERVICE ACTION IN(16) */ { 0x9E, ALL, "SERVICE ACTION IN(16)" }, - /* XXX KDM ALL for this? op-num.txt defines it for ADC.. */ /* 9F M SERVICE ACTION OUT(16) */ { 0x9F, ALL, "SERVICE ACTION OUT(16)" }, /* A0 MMOOO OMMM OMO REPORT LUNS */ @@ -643,8 +652,7 @@ scsi_op_desc(u_int16_t opcode, struct scsi_inquiry_data *inq_data) match = cam_quirkmatch((caddr_t)inq_data, (caddr_t)scsi_op_quirk_table, - sizeof(scsi_op_quirk_table)/ - sizeof(*scsi_op_quirk_table), + nitems(scsi_op_quirk_table), sizeof(*scsi_op_quirk_table), scsi_inquiry_match); } @@ -653,7 +661,7 @@ scsi_op_desc(u_int16_t opcode, struct scsi_inquiry_data *inq_data) table[0] = ((struct scsi_op_quirk_entry *)match)->op_table; num_ops[0] = ((struct scsi_op_quirk_entry *)match)->num_ops; table[1] = scsi_op_codes; - num_ops[1] = sizeof(scsi_op_codes)/sizeof(scsi_op_codes[0]); + num_ops[1] = nitems(scsi_op_codes); num_tables = 2; } else { /* @@ -664,7 +672,7 @@ scsi_op_desc(u_int16_t opcode, struct scsi_inquiry_data *inq_data) return("Vendor Specific Command"); table[0] = scsi_op_codes; - num_ops[0] = sizeof(scsi_op_codes)/sizeof(scsi_op_codes[0]); + num_ops[0] = nitems(scsi_op_codes); num_tables = 1; } @@ -672,6 +680,12 @@ scsi_op_desc(u_int16_t opcode, struct scsi_inquiry_data *inq_data) if (pd_type == T_RBC) pd_type = T_DIRECT; + /* + * Host managed drives are direct access for the most part. + */ + if (pd_type == T_ZBC_HM) + pd_type = T_DIRECT; + /* Map NODEVICE to Direct Access Device to handle REPORT LUNS, etc. */ if (pd_type == T_NODEVICE) pd_type = T_DIRECT; @@ -735,9 +749,6 @@ const struct sense_key_table_entry sense_key_table[] = { SSD_KEY_COMPLETED, SS_NOP, "COMPLETED" } }; -const int sense_key_table_size = - sizeof(sense_key_table)/sizeof(sense_key_table[0]); - static struct asc_table_entry quantum_fireball_entries[] = { { SST(0x04, 0x0b, SS_START | SSQ_DECREMENT_COUNT | ENXIO, "Logical unit not ready, initializing cmd. required") } @@ -922,7 +933,7 @@ static struct scsi_sense_quirk_entry sense_quirk_table[] = { */ {T_DIRECT, SIP_MEDIA_FIXED, "QUANTUM", "FIREBALL S*", "*"}, /*num_sense_keys*/0, - sizeof(quantum_fireball_entries)/sizeof(struct asc_table_entry), + nitems(quantum_fireball_entries), /*sense key entries*/NULL, quantum_fireball_entries }, @@ -933,7 +944,7 @@ static struct scsi_sense_quirk_entry sense_quirk_table[] = { */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "SONY", "SMO-*", "*"}, /*num_sense_keys*/0, - sizeof(sony_mo_entries)/sizeof(struct asc_table_entry), + nitems(sony_mo_entries), /*sense key entries*/NULL, sony_mo_entries }, @@ -943,7 +954,7 @@ static struct scsi_sense_quirk_entry sense_quirk_table[] = { */ {T_DIRECT, SIP_MEDIA_FIXED, "HGST", "*", "*"}, /*num_sense_keys*/0, - sizeof(hgst_entries)/sizeof(struct asc_table_entry), + nitems(hgst_entries), /*sense key entries*/NULL, hgst_entries }, @@ -953,14 +964,13 @@ static struct scsi_sense_quirk_entry sense_quirk_table[] = { */ {T_DIRECT, SIP_MEDIA_FIXED, "SEAGATE", "*", "*"}, /*num_sense_keys*/0, - sizeof(seagate_entries)/sizeof(struct asc_table_entry), + nitems(seagate_entries), /*sense key entries*/NULL, seagate_entries } }; -const int sense_quirk_table_size = - sizeof(sense_quirk_table)/sizeof(sense_quirk_table[0]); +const u_int sense_quirk_table_size = nitems(sense_quirk_table); static struct asc_table_entry asc_table[] = { /* @@ -972,7 +982,7 @@ static struct asc_table_entry asc_table[] = { * * SCSI ASC/ASCQ Assignments * Numeric Sorted Listing - * as of 5/20/12 + * as of 8/12/15 * * D - DIRECT ACCESS DEVICE (SBC-2) device column key * .T - SEQUENTIAL ACCESS DEVICE (SSC) ------------------- @@ -1064,6 +1074,9 @@ static struct asc_table_entry asc_table[] = { /* DT P B */ { SST(0x00, 0x20, SS_RDEF, /* XXX TBD */ "Extended copy information available") }, + /* D */ + { SST(0x00, 0x21, SS_RDEF, /* XXX TBD */ + "Atomic command aborted due to ACA") }, /* D W O BK */ { SST(0x01, 0x00, SS_RDEF, "No index/sector signal") }, @@ -1083,7 +1096,7 @@ static struct asc_table_entry asc_table[] = { { SST(0x04, 0x00, SS_RDEF, "Logical unit not ready, cause not reportable") }, /* DTLPWROMAEBKVF */ - { SST(0x04, 0x01, SS_TUR | SSQ_MANY | SSQ_DECREMENT_COUNT | EBUSY, + { SST(0x04, 0x01, SS_WAIT | EBUSY, "Logical unit is in process of becoming ready") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x02, SS_START | SSQ_DECREMENT_COUNT | ENXIO, @@ -1110,7 +1123,7 @@ static struct asc_table_entry asc_table[] = { { SST(0x04, 0x09, SS_RDEF, /* XXX TBD */ "Logical unit not ready, self-test in progress") }, /* DTLPWROMAEBKVF */ - { SST(0x04, 0x0A, SS_TUR | SSQ_MANY | SSQ_DECREMENT_COUNT | ENXIO, + { SST(0x04, 0x0A, SS_WAIT | ENXIO, "Logical unit not accessible, asymmetric access state transition")}, /* DTLPWROMAEBKVF */ { SST(0x04, 0x0B, SS_FATAL | ENXIO, @@ -1121,11 +1134,14 @@ static struct asc_table_entry asc_table[] = { /* F */ { SST(0x04, 0x0D, SS_RDEF, /* XXX TBD */ "Logical unit not ready, structure check required") }, + /* DTL WR MAEBKVF */ + { SST(0x04, 0x0E, SS_RDEF, /* XXX TBD */ + "Logical unit not ready, security session in progress") }, /* DT WROM B */ { SST(0x04, 0x10, SS_RDEF, /* XXX TBD */ "Logical unit not ready, auxiliary memory not accessible") }, /* DT WRO AEB VF */ - { SST(0x04, 0x11, SS_TUR | SSQ_MANY | SSQ_DECREMENT_COUNT | EBUSY, + { SST(0x04, 0x11, SS_WAIT | EBUSY, "Logical unit not ready, notify (enable spinup) required") }, /* M V */ { SST(0x04, 0x12, SS_RDEF, /* XXX TBD */ @@ -1160,6 +1176,24 @@ static struct asc_table_entry asc_table[] = { /* DT MAEB */ { SST(0x04, 0x1C, SS_RDEF, /* XXX TBD */ "Logical unit not ready, additional power use not yet granted") }, + /* D */ + { SST(0x04, 0x1D, SS_RDEF, /* XXX TBD */ + "Logical unit not ready, configuration in progress") }, + /* D */ + { SST(0x04, 0x1E, SS_FATAL | ENXIO, + "Logical unit not ready, microcode activation required") }, + /* DTLPWROMAEBKVF */ + { SST(0x04, 0x1F, SS_FATAL | ENXIO, + "Logical unit not ready, microcode download required") }, + /* DTLPWROMAEBKVF */ + { SST(0x04, 0x20, SS_RDEF, /* XXX TBD */ + "Logical unit not ready, logical unit reset required") }, + /* DTLPWROMAEBKVF */ + { SST(0x04, 0x21, SS_RDEF, /* XXX TBD */ + "Logical unit not ready, hard reset required") }, + /* DTLPWROMAEBKVF */ + { SST(0x04, 0x22, SS_RDEF, /* XXX TBD */ + "Logical unit not ready, power cycle required") }, /* DTL WROMAEBKVF */ { SST(0x05, 0x00, SS_RDEF, "Logical unit does not respond to selection") }, @@ -1199,6 +1233,9 @@ static struct asc_table_entry asc_table[] = { /* DT WRO B */ { SST(0x09, 0x04, SS_RDEF, "Head select fault") }, + /* DT RO B */ + { SST(0x09, 0x05, SS_RDEF, + "Vibration induced tracking error") }, /* DTLPWROMAEBKVF */ { SST(0x0A, 0x00, SS_FATAL | ENOSPC, "Error log overflow") }, @@ -1232,6 +1269,30 @@ static struct asc_table_entry asc_table[] = { /* D */ { SST(0x0B, 0x09, SS_RDEF, /* XXX TBD */ "Warning - device statistics notification available") }, + /* DTLPWROMAEBKVF */ + { SST(0x0B, 0x0A, SS_RDEF, /* XXX TBD */ + "Warning - High critical temperature limit exceeded") }, + /* DTLPWROMAEBKVF */ + { SST(0x0B, 0x0B, SS_RDEF, /* XXX TBD */ + "Warning - Low critical temperature limit exceeded") }, + /* DTLPWROMAEBKVF */ + { SST(0x0B, 0x0C, SS_RDEF, /* XXX TBD */ + "Warning - High operating temperature limit exceeded") }, + /* DTLPWROMAEBKVF */ + { SST(0x0B, 0x0D, SS_RDEF, /* XXX TBD */ + "Warning - Low operating temperature limit exceeded") }, + /* DTLPWROMAEBKVF */ + { SST(0x0B, 0x0E, SS_RDEF, /* XXX TBD */ + "Warning - High citical humidity limit exceeded") }, + /* DTLPWROMAEBKVF */ + { SST(0x0B, 0x0F, SS_RDEF, /* XXX TBD */ + "Warning - Low citical humidity limit exceeded") }, + /* DTLPWROMAEBKVF */ + { SST(0x0B, 0x10, SS_RDEF, /* XXX TBD */ + "Warning - High operating humidity limit exceeded") }, + /* DTLPWROMAEBKVF */ + { SST(0x0B, 0x11, SS_RDEF, /* XXX TBD */ + "Warning - Low operating humidity limit exceeded") }, /* T R */ { SST(0x0C, 0x00, SS_RDEF, "Write error") }, @@ -1280,6 +1341,15 @@ static struct asc_table_entry asc_table[] = { /* R */ { SST(0x0C, 0x0F, SS_RDEF, /* XXX TBD */ "Defects in error window") }, + /* D */ + { SST(0x0C, 0x10, SS_RDEF, /* XXX TBD */ + "Incomplete multiple atomic write operations") }, + /* D */ + { SST(0x0C, 0x11, SS_RDEF, /* XXX TBD */ + "Write error - recovery scan needed") }, + /* D */ + { SST(0x0C, 0x12, SS_RDEF, /* XXX TBD */ + "Write error - insufficient zone resources") }, /* DTLPWRO A K */ { SST(0x0D, 0x00, SS_RDEF, /* XXX TBD */ "Error detected by third party temporary initiator") }, @@ -1391,6 +1461,9 @@ static struct asc_table_entry asc_table[] = { /* D */ { SST(0x11, 0x14, SS_RDEF, /* XXX TBD */ "Read error - LBA marked bad by application client") }, + /* D */ + { SST(0x11, 0x15, SS_RDEF, /* XXX TBD */ + "Write after sanitize required") }, /* D W O BK */ { SST(0x12, 0x00, SS_RDEF, "Address mark not found for ID field") }, @@ -1593,40 +1666,52 @@ static struct asc_table_entry asc_table[] = { { SST(0x21, 0x03, SS_RDEF, /* XXX TBD */ "Invalid write crossing layer jump") }, /* D */ + { SST(0x21, 0x04, SS_RDEF, /* XXX TBD */ + "Unaligned write command") }, + /* D */ + { SST(0x21, 0x05, SS_RDEF, /* XXX TBD */ + "Write boundary violation") }, + /* D */ + { SST(0x21, 0x06, SS_RDEF, /* XXX TBD */ + "Attempt to read invalid data") }, + /* D */ + { SST(0x21, 0x07, SS_RDEF, /* XXX TBD */ + "Read boundary violation") }, + /* D */ { SST(0x22, 0x00, SS_FATAL | EINVAL, "Illegal function (use 20 00, 24 00, or 26 00)") }, /* DT P B */ - { SST(0x23, 0x00, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x00, SS_FATAL | EINVAL, "Invalid token operation, cause not reportable") }, /* DT P B */ - { SST(0x23, 0x01, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x01, SS_FATAL | EINVAL, "Invalid token operation, unsupported token type") }, /* DT P B */ - { SST(0x23, 0x02, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x02, SS_FATAL | EINVAL, "Invalid token operation, remote token usage not supported") }, /* DT P B */ - { SST(0x23, 0x03, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x03, SS_FATAL | EINVAL, "Invalid token operation, remote ROD token creation not supported") }, /* DT P B */ - { SST(0x23, 0x04, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x04, SS_FATAL | EINVAL, "Invalid token operation, token unknown") }, /* DT P B */ - { SST(0x23, 0x05, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x05, SS_FATAL | EINVAL, "Invalid token operation, token corrupt") }, /* DT P B */ - { SST(0x23, 0x06, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x06, SS_FATAL | EINVAL, "Invalid token operation, token revoked") }, /* DT P B */ - { SST(0x23, 0x07, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x07, SS_FATAL | EINVAL, "Invalid token operation, token expired") }, /* DT P B */ - { SST(0x23, 0x08, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x08, SS_FATAL | EINVAL, "Invalid token operation, token cancelled") }, /* DT P B */ - { SST(0x23, 0x09, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x09, SS_FATAL | EINVAL, "Invalid token operation, token deleted") }, /* DT P B */ - { SST(0x23, 0x0A, SS_RDEF, /* XXX TBD */ + { SST(0x23, 0x0A, SS_FATAL | EINVAL, "Invalid token operation, invalid token length") }, /* DTLPWROMAEBKVF */ { SST(0x24, 0x00, SS_FATAL | EINVAL, @@ -1677,28 +1762,28 @@ static struct asc_table_entry asc_table[] = { { SST(0x26, 0x05, SS_RDEF, /* XXX TBD */ "Data decryption error") }, /* DTLPWRO K */ - { SST(0x26, 0x06, SS_RDEF, /* XXX TBD */ + { SST(0x26, 0x06, SS_FATAL | EINVAL, "Too many target descriptors") }, /* DTLPWRO K */ - { SST(0x26, 0x07, SS_RDEF, /* XXX TBD */ + { SST(0x26, 0x07, SS_FATAL | EINVAL, "Unsupported target descriptor type code") }, /* DTLPWRO K */ - { SST(0x26, 0x08, SS_RDEF, /* XXX TBD */ + { SST(0x26, 0x08, SS_FATAL | EINVAL, "Too many segment descriptors") }, /* DTLPWRO K */ - { SST(0x26, 0x09, SS_RDEF, /* XXX TBD */ + { SST(0x26, 0x09, SS_FATAL | EINVAL, "Unsupported segment descriptor type code") }, /* DTLPWRO K */ - { SST(0x26, 0x0A, SS_RDEF, /* XXX TBD */ + { SST(0x26, 0x0A, SS_FATAL | EINVAL, "Unexpected inexact segment") }, /* DTLPWRO K */ - { SST(0x26, 0x0B, SS_RDEF, /* XXX TBD */ + { SST(0x26, 0x0B, SS_FATAL | EINVAL, "Inline data length exceeded") }, /* DTLPWRO K */ - { SST(0x26, 0x0C, SS_RDEF, /* XXX TBD */ + { SST(0x26, 0x0C, SS_FATAL | EINVAL, "Invalid operation for copy source or destination") }, /* DTLPWRO K */ - { SST(0x26, 0x0D, SS_RDEF, /* XXX TBD */ + { SST(0x26, 0x0D, SS_FATAL | EINVAL, "Copy segment granularity violation") }, /* DT PWROMAEBK */ { SST(0x26, 0x0E, SS_RDEF, /* XXX TBD */ @@ -1715,6 +1800,9 @@ static struct asc_table_entry asc_table[] = { /* T */ { SST(0x26, 0x12, SS_RDEF, /* XXX TBD */ "Vendor specific key reference not found") }, + /* D */ + { SST(0x26, 0x13, SS_RDEF, /* XXX TBD */ + "Application tag mode page is invalid") }, /* DT WRO BK */ { SST(0x27, 0x00, SS_FATAL | EACCES, "Write protected") }, @@ -1737,8 +1825,11 @@ static struct asc_table_entry asc_table[] = { { SST(0x27, 0x06, SS_RDEF, /* XXX TBD */ "Conditional write protect") }, /* D B */ - { SST(0x27, 0x07, SS_RDEF, /* XXX TBD */ + { SST(0x27, 0x07, SS_FATAL | ENOSPC, "Space allocation failed write protect") }, + /* D */ + { SST(0x27, 0x08, SS_FATAL | EACCES, + "Zone is read only") }, /* DTLPWROMAEBKVF */ { SST(0x28, 0x00, SS_FATAL | ENXIO, "Not ready to ready change, medium may have changed") }, @@ -1882,12 +1973,33 @@ static struct asc_table_entry asc_table[] = { /* D */ { SST(0x2C, 0x0C, SS_RDEF, /* XXX TBD */ "ORWRITE generation does not match") }, + /* D */ + { SST(0x2C, 0x0D, SS_RDEF, /* XXX TBD */ + "Reset write pointer not allowed") }, + /* D */ + { SST(0x2C, 0x0E, SS_RDEF, /* XXX TBD */ + "Zone is offline") }, + /* D */ + { SST(0x2C, 0x0F, SS_RDEF, /* XXX TBD */ + "Stream not open") }, + /* D */ + { SST(0x2C, 0x10, SS_RDEF, /* XXX TBD */ + "Unwritten data in zone") }, /* T */ { SST(0x2D, 0x00, SS_RDEF, "Overwrite error on update in place") }, /* R */ { SST(0x2E, 0x00, SS_RDEF, /* XXX TBD */ "Insufficient time for operation") }, + /* D */ + { SST(0x2E, 0x01, SS_RDEF, /* XXX TBD */ + "Command timeout before processing") }, + /* D */ + { SST(0x2E, 0x02, SS_RDEF, /* XXX TBD */ + "Command timeout during processing") }, + /* D */ + { SST(0x2E, 0x03, SS_RDEF, /* XXX TBD */ + "Command timeout during processing due to error recovery") }, /* DTLPWROMAEBKVF */ { SST(0x2F, 0x00, SS_RDEF, "Commands cleared by another initiator") }, @@ -1897,6 +2009,9 @@ static struct asc_table_entry asc_table[] = { /* DTLPWROMAEBKVF */ { SST(0x2F, 0x02, SS_RDEF, /* XXX TBD */ "Commands cleared by device server") }, + /* DTLPWROMAEBKVF */ + { SST(0x2F, 0x03, SS_RDEF, /* XXX TBD */ + "Some commands cleared by queuing layer event") }, /* DT WROM BK */ { SST(0x30, 0x00, SS_RDEF, "Incompatible medium installed") }, @@ -2194,6 +2309,15 @@ static struct asc_table_entry asc_table[] = { /* DTLPWR MAEBK F */ { SST(0x3F, 0x14, SS_RDEF, /* XXX TBD */ "iSCSI IP address changed") }, + /* DTLPWR MAEBK */ + { SST(0x3F, 0x15, SS_RDEF, /* XXX TBD */ + "Inspect referrals sense descriptors") }, + /* DTLPWROMAEBKVF */ + { SST(0x3F, 0x16, SS_RDEF, /* XXX TBD */ + "Microcode has been changed without reset") }, + /* D */ + { SST(0x3F, 0x17, SS_RDEF, /* XXX TBD */ + "Zone transition to full") }, /* D */ { SST(0x40, 0x00, SS_RDEF, "RAM failure") }, /* deprecated - use 40 NN instead */ @@ -2303,6 +2427,30 @@ static struct asc_table_entry asc_table[] = { /* DT PWROMAEBK F */ { SST(0x4B, 0x0D, SS_RDEF, /* XXX TBD */ "Data-out buffer error") }, + /* DT PWROMAEBK F */ + { SST(0x4B, 0x0E, SS_RDEF, /* XXX TBD */ + "PCIe fabric error") }, + /* DT PWROMAEBK F */ + { SST(0x4B, 0x0F, SS_RDEF, /* XXX TBD */ + "PCIe completion timeout") }, + /* DT PWROMAEBK F */ + { SST(0x4B, 0x10, SS_RDEF, /* XXX TBD */ + "PCIe completer abort") }, + /* DT PWROMAEBK F */ + { SST(0x4B, 0x11, SS_RDEF, /* XXX TBD */ + "PCIe poisoned TLP received") }, + /* DT PWROMAEBK F */ + { SST(0x4B, 0x12, SS_RDEF, /* XXX TBD */ + "PCIe ECRC check failed") }, + /* DT PWROMAEBK F */ + { SST(0x4B, 0x13, SS_RDEF, /* XXX TBD */ + "PCIe unsupported request") }, + /* DT PWROMAEBK F */ + { SST(0x4B, 0x14, SS_RDEF, /* XXX TBD */ + "PCIe ACS violation") }, + /* DT PWROMAEBK F */ + { SST(0x4B, 0x15, SS_RDEF, /* XXX TBD */ + "PCIe TLP prefix blocket") }, /* DTLPWROMAEBKVF */ { SST(0x4C, 0x00, SS_RDEF, "Logical unit failed self-configuration") }, @@ -2360,6 +2508,21 @@ static struct asc_table_entry asc_table[] = { /* M */ { SST(0x53, 0x08, SS_RDEF, /* XXX TBD */ "Element status unknown") }, + /* M */ + { SST(0x53, 0x09, SS_RDEF, /* XXX TBD */ + "Data transfer device error - load failed") }, + /* M */ + { SST(0x53, 0x0A, SS_RDEF, /* XXX TBD */ + "Data transfer device error - unload failed") }, + /* M */ + { SST(0x53, 0x0B, SS_RDEF, /* XXX TBD */ + "Data transfer device error - unload missing") }, + /* M */ + { SST(0x53, 0x0C, SS_RDEF, /* XXX TBD */ + "Data transfer device error - eject failed") }, + /* M */ + { SST(0x53, 0x0D, SS_RDEF, /* XXX TBD */ + "Data transfer device error - library communication failed") }, /* P */ { SST(0x54, 0x00, SS_RDEF, "SCSI to host system interface failure") }, @@ -2405,6 +2568,15 @@ static struct asc_table_entry asc_table[] = { /* DT P B */ { SST(0x55, 0x0D, SS_RDEF, /* XXX TBD */ "Insufficient resources to create ROD token") }, + /* D */ + { SST(0x55, 0x0E, SS_RDEF, /* XXX TBD */ + "Insufficient zone resources") }, + /* D */ + { SST(0x55, 0x0F, SS_RDEF, /* XXX TBD */ + "Insufficient zone resources to complete write") }, + /* D */ + { SST(0x55, 0x10, SS_RDEF, /* XXX TBD */ + "Maximum number of streams open") }, /* R */ { SST(0x57, 0x00, SS_RDEF, "Unable to recover table-of-contents") }, @@ -2825,6 +2997,9 @@ static struct asc_table_entry asc_table[] = { /* A */ { SST(0x68, 0x00, SS_RDEF, "Logical unit not configured") }, + /* D */ + { SST(0x68, 0x01, SS_RDEF, + "Subsidiary logical unit not configured") }, /* A */ { SST(0x69, 0x00, SS_RDEF, "Data loss on logical unit") }, @@ -3031,7 +3206,7 @@ static struct asc_table_entry asc_table[] = { "Security conflict in translated device") } }; -const int asc_table_size = sizeof(asc_table)/sizeof(asc_table[0]); +const u_int asc_table_size = nitems(asc_table); struct asc_key { @@ -3124,14 +3299,14 @@ fetchtableentries(int sense_key, int asc, int ascq, sense_tables[0] = quirk->sense_key_info; sense_tables_size[0] = quirk->num_sense_keys; sense_tables[1] = sense_key_table; - sense_tables_size[1] = sense_key_table_size; + sense_tables_size[1] = nitems(sense_key_table); num_sense_tables = 2; } else { asc_tables[0] = asc_table; asc_tables_size[0] = asc_table_size; num_asc_tables = 1; sense_tables[0] = sense_key_table; - sense_tables_size[0] = sense_key_table_size; + sense_tables_size[0] = nitems(sense_key_table); num_sense_tables = 1; } @@ -3297,14 +3472,32 @@ scsi_error_action(struct ccb_scsiio *csio, struct scsi_inquiry_data *inq_data, char * scsi_cdb_string(u_int8_t *cdb_ptr, char *cdb_string, size_t len) { + struct sbuf sb; + int error; + + if (len == 0) + return (""); + + sbuf_new(&sb, cdb_string, len, SBUF_FIXEDLEN); + + scsi_cdb_sbuf(cdb_ptr, &sb); + + /* ENOMEM just means that the fixed buffer is full, OK to ignore */ + error = sbuf_finish(&sb); + if (error != 0 && error != ENOMEM) + return (""); + + return(sbuf_data(&sb)); +} + +void +scsi_cdb_sbuf(u_int8_t *cdb_ptr, struct sbuf *sb) +{ u_int8_t cdb_len; int i; if (cdb_ptr == NULL) - return(""); - - /* Silence warnings */ - cdb_len = 0; + return; /* * This is taken from the SCSI-3 draft spec. @@ -3341,12 +3534,11 @@ scsi_cdb_string(u_int8_t *cdb_ptr, char *cdb_string, size_t len) cdb_len = 12; break; } - *cdb_string = '\0'; + for (i = 0; i < cdb_len; i++) - snprintf(cdb_string + strlen(cdb_string), - len - strlen(cdb_string), "%02hhx ", cdb_ptr[i]); + sbuf_printf(sb, "%02hhx ", cdb_ptr[i]); - return(cdb_string); + return; } const char * @@ -3395,7 +3587,6 @@ scsi_command_string(struct cam_device *device, struct ccb_scsiio *csio, #endif /* _KERNEL/!_KERNEL */ { struct scsi_inquiry_data *inq_data; - char cdb_str[(SCSI_MAX_CDBLEN * 3) + 1]; #ifdef _KERNEL struct ccb_getdev *cgd; #endif /* _KERNEL */ @@ -3428,15 +3619,13 @@ scsi_command_string(struct cam_device *device, struct ccb_scsiio *csio, #endif /* _KERNEL/!_KERNEL */ if ((csio->ccb_h.flags & CAM_CDB_POINTER) != 0) { - sbuf_printf(sb, "%s. CDB: %s", - scsi_op_desc(csio->cdb_io.cdb_ptr[0], inq_data), - scsi_cdb_string(csio->cdb_io.cdb_ptr, cdb_str, - sizeof(cdb_str))); + sbuf_printf(sb, "%s. CDB: ", + scsi_op_desc(csio->cdb_io.cdb_ptr[0], inq_data)); + scsi_cdb_sbuf(csio->cdb_io.cdb_ptr, sb); } else { - sbuf_printf(sb, "%s. CDB: %s", - scsi_op_desc(csio->cdb_io.cdb_bytes[0], inq_data), - scsi_cdb_string(csio->cdb_io.cdb_bytes, cdb_str, - sizeof(cdb_str))); + sbuf_printf(sb, "%s. CDB: ", + scsi_op_desc(csio->cdb_io.cdb_bytes[0], inq_data)); + scsi_cdb_sbuf(csio->cdb_io.cdb_bytes, sb); } #ifdef _KERNEL @@ -3468,7 +3657,7 @@ scsi_desc_iterate(struct scsi_sense_data_desc *sense, u_int sense_len, /* * The length of data actually returned may be different than the - * extra_len recorded in the sturcture. + * extra_len recorded in the structure. */ desc_len = sense_len -offsetof(struct scsi_sense_data_desc, sense_desc); @@ -3808,8 +3997,6 @@ scsi_set_sense_data_va(struct scsi_sense_data *sense_data, */ sense->extra_len = 10; sense_len = (int)va_arg(ap, int); - len_to_copy = MIN(sense_len, SSD_EXTRA_MAX - - sense->extra_len); data = (uint8_t *)va_arg(ap, uint8_t *); switch (elem_type) { @@ -3827,10 +4014,14 @@ scsi_set_sense_data_va(struct scsi_sense_data *sense_data, uint8_t *data_dest; int i; - if (elem_type == SSD_ELEM_COMMAND) + if (elem_type == SSD_ELEM_COMMAND) { data_dest = &sense->cmd_spec_info[0]; - else { + len_to_copy = MIN(sense_len, + sizeof(sense->cmd_spec_info)); + } else { data_dest = &sense->info[0]; + len_to_copy = MIN(sense_len, + sizeof(sense->info)); /* * We're setting the info field, so * set the valid bit. @@ -4083,6 +4274,7 @@ scsi_get_block_info(struct scsi_sense_data *sense_data, u_int sense_len, switch (SID_TYPE(inq_data)) { case T_DIRECT: case T_RBC: + case T_ZBC_HM: break; default: goto bailout; @@ -4524,10 +4716,9 @@ scsi_sense_desc_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { - int i; + u_int i; - for (i = 0; i < (sizeof(scsi_sense_printers) / - sizeof(scsi_sense_printers[0])); i++) { + for (i = 0; i < nitems(scsi_sense_printers); i++) { struct scsi_sense_desc_printer *printer; printer = &scsi_sense_printers[i]; @@ -5236,6 +5427,9 @@ scsi_print_inquiry(struct scsi_inquiry_data *inq_data) case T_ADC: dtype = "Automation/Drive Interface"; break; + case T_ZBC_HM: + dtype = "Host Managed Zoned Block"; + break; case T_NODEVICE: dtype = "Uninstalled"; break; @@ -5304,8 +5498,8 @@ static struct { u_int scsi_calc_syncsrate(u_int period_factor) { - int i; - int num_syncrates; + u_int i; + u_int num_syncrates; /* * It's a bug if period is zero, but if it is anyway, don't @@ -5316,7 +5510,7 @@ scsi_calc_syncsrate(u_int period_factor) return (3300); } - num_syncrates = sizeof(scsi_syncrates) / sizeof(scsi_syncrates[0]); + num_syncrates = nitems(scsi_syncrates); /* See if the period is in the "exception" table */ for (i = 0; i < num_syncrates; i++) { @@ -5334,21 +5528,21 @@ scsi_calc_syncsrate(u_int period_factor) } /* - * Return the SCSI sync parameter that corresponsd to + * Return the SCSI sync parameter that corresponds to * the passed in period in 10ths of ns. */ u_int scsi_calc_syncparam(u_int period) { - int i; - int num_syncrates; + u_int i; + u_int num_syncrates; if (period == 0) return (~0); /* Async */ /* Adjust for exception table being in 100ths. */ period *= 10; - num_syncrates = sizeof(scsi_syncrates) / sizeof(scsi_syncrates[0]); + num_syncrates = nitems(scsi_syncrates); /* See if the period is in the "exception" table */ for (i = 0; i < num_syncrates; i++) { @@ -5450,33 +5644,1853 @@ scsi_devid_is_lun_name(uint8_t *bufp) return 1; } +int +scsi_devid_is_port_naa(uint8_t *bufp) +{ + struct scsi_vpd_id_descriptor *descr; + + descr = (struct scsi_vpd_id_descriptor *)bufp; + if ((descr->id_type & SVPD_ID_ASSOC_MASK) != SVPD_ID_ASSOC_PORT) + return 0; + if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_NAA) + return 0; + return 1; +} + struct scsi_vpd_id_descriptor * -scsi_get_devid(struct scsi_vpd_device_id *id, uint32_t page_len, +scsi_get_devid_desc(struct scsi_vpd_id_descriptor *desc, uint32_t len, scsi_devid_checkfn_t ck_fn) { - struct scsi_vpd_id_descriptor *desc; - uint8_t *page_end; uint8_t *desc_buf_end; - page_end = (uint8_t *)id + page_len; - if (page_end < id->desc_list) - return (NULL); - - desc_buf_end = MIN(id->desc_list + scsi_2btoul(id->length), page_end); + desc_buf_end = (uint8_t *)desc + len; - for (desc = (struct scsi_vpd_id_descriptor *)id->desc_list; - desc->identifier <= desc_buf_end - && desc->identifier + desc->length <= desc_buf_end; - desc = (struct scsi_vpd_id_descriptor *)(desc->identifier + for (; desc->identifier <= desc_buf_end && + desc->identifier + desc->length <= desc_buf_end; + desc = (struct scsi_vpd_id_descriptor *)(desc->identifier + desc->length)) { if (ck_fn == NULL || ck_fn((uint8_t *)desc) != 0) return (desc); } + return (NULL); +} + +struct scsi_vpd_id_descriptor * +scsi_get_devid(struct scsi_vpd_device_id *id, uint32_t page_len, + scsi_devid_checkfn_t ck_fn) +{ + uint32_t len; + + if (page_len < sizeof(*id)) + return (NULL); + len = MIN(scsi_2btoul(id->length), page_len - sizeof(*id)); + return (scsi_get_devid_desc((struct scsi_vpd_id_descriptor *) + id->desc_list, len, ck_fn)); +} + +int +scsi_transportid_sbuf(struct sbuf *sb, struct scsi_transportid_header *hdr, + uint32_t valid_len) +{ + switch (hdr->format_protocol & SCSI_TRN_PROTO_MASK) { + case SCSI_PROTO_FC: { + struct scsi_transportid_fcp *fcp; + uint64_t n_port_name; + + fcp = (struct scsi_transportid_fcp *)hdr; + + n_port_name = scsi_8btou64(fcp->n_port_name); + + sbuf_printf(sb, "FCP address: 0x%.16jx",(uintmax_t)n_port_name); + break; + } + case SCSI_PROTO_SPI: { + struct scsi_transportid_spi *spi; + + spi = (struct scsi_transportid_spi *)hdr; + + sbuf_printf(sb, "SPI address: %u,%u", + scsi_2btoul(spi->scsi_addr), + scsi_2btoul(spi->rel_trgt_port_id)); + break; + } + case SCSI_PROTO_SSA: + /* + * XXX KDM there is no transport ID defined in SPC-4 for + * SSA. + */ + break; + case SCSI_PROTO_1394: { + struct scsi_transportid_1394 *sbp; + uint64_t eui64; + + sbp = (struct scsi_transportid_1394 *)hdr; + + eui64 = scsi_8btou64(sbp->eui64); + sbuf_printf(sb, "SBP address: 0x%.16jx", (uintmax_t)eui64); + break; + } + case SCSI_PROTO_RDMA: { + struct scsi_transportid_rdma *rdma; + unsigned int i; + + rdma = (struct scsi_transportid_rdma *)hdr; + + sbuf_printf(sb, "RDMA address: 0x"); + for (i = 0; i < sizeof(rdma->initiator_port_id); i++) + sbuf_printf(sb, "%02x", rdma->initiator_port_id[i]); + break; + } + case SCSI_PROTO_ISCSI: { + uint32_t add_len, i; + uint8_t *iscsi_name = NULL; + int nul_found = 0; + + sbuf_printf(sb, "iSCSI address: "); + if ((hdr->format_protocol & SCSI_TRN_FORMAT_MASK) == + SCSI_TRN_ISCSI_FORMAT_DEVICE) { + struct scsi_transportid_iscsi_device *dev; + + dev = (struct scsi_transportid_iscsi_device *)hdr; + + /* + * Verify how much additional data we really have. + */ + add_len = scsi_2btoul(dev->additional_length); + add_len = MIN(add_len, valid_len - + __offsetof(struct scsi_transportid_iscsi_device, + iscsi_name)); + iscsi_name = &dev->iscsi_name[0]; + + } else if ((hdr->format_protocol & SCSI_TRN_FORMAT_MASK) == + SCSI_TRN_ISCSI_FORMAT_PORT) { + struct scsi_transportid_iscsi_port *port; + + port = (struct scsi_transportid_iscsi_port *)hdr; + + add_len = scsi_2btoul(port->additional_length); + add_len = MIN(add_len, valid_len - + __offsetof(struct scsi_transportid_iscsi_port, + iscsi_name)); + iscsi_name = &port->iscsi_name[0]; + } else { + sbuf_printf(sb, "unknown format %x", + (hdr->format_protocol & + SCSI_TRN_FORMAT_MASK) >> + SCSI_TRN_FORMAT_SHIFT); + break; + } + if (add_len == 0) { + sbuf_printf(sb, "not enough data"); + break; + } + /* + * This is supposed to be a NUL-terminated ASCII + * string, but you never know. So we're going to + * check. We need to do this because there is no + * sbuf equivalent of strncat(). + */ + for (i = 0; i < add_len; i++) { + if (iscsi_name[i] == '\0') { + nul_found = 1; + break; + } + } + /* + * If there is a NUL in the name, we can just use + * sbuf_cat(). Otherwise we need to use sbuf_bcat(). + */ + if (nul_found != 0) + sbuf_cat(sb, iscsi_name); + else + sbuf_bcat(sb, iscsi_name, add_len); + break; + } + case SCSI_PROTO_SAS: { + struct scsi_transportid_sas *sas; + uint64_t sas_addr; + + sas = (struct scsi_transportid_sas *)hdr; + + sas_addr = scsi_8btou64(sas->sas_address); + sbuf_printf(sb, "SAS address: 0x%.16jx", (uintmax_t)sas_addr); + break; + } + case SCSI_PROTO_ADITP: + case SCSI_PROTO_ATA: + case SCSI_PROTO_UAS: + /* + * No Transport ID format for ADI, ATA or USB is defined in + * SPC-4. + */ + sbuf_printf(sb, "No known Transport ID format for protocol " + "%#x", hdr->format_protocol & SCSI_TRN_PROTO_MASK); + break; + case SCSI_PROTO_SOP: { + struct scsi_transportid_sop *sop; + struct scsi_sop_routing_id_norm *rid; + + sop = (struct scsi_transportid_sop *)hdr; + rid = (struct scsi_sop_routing_id_norm *)sop->routing_id; + + /* + * Note that there is no alternate format specified in SPC-4 + * for the PCIe routing ID, so we don't really have a way + * to know whether the second byte of the routing ID is + * a device and function or just a function. So we just + * assume bus,device,function. + */ + sbuf_printf(sb, "SOP Routing ID: %u,%u,%u", + rid->bus, rid->devfunc >> SCSI_TRN_SOP_DEV_SHIFT, + rid->devfunc & SCSI_TRN_SOP_FUNC_NORM_MAX); + break; + } + case SCSI_PROTO_NONE: + default: + sbuf_printf(sb, "Unknown protocol %#x", + hdr->format_protocol & SCSI_TRN_PROTO_MASK); + break; + } + + return (0); +} + +struct scsi_nv scsi_proto_map[] = { + { "fcp", SCSI_PROTO_FC }, + { "spi", SCSI_PROTO_SPI }, + { "ssa", SCSI_PROTO_SSA }, + { "sbp", SCSI_PROTO_1394 }, + { "1394", SCSI_PROTO_1394 }, + { "srp", SCSI_PROTO_RDMA }, + { "rdma", SCSI_PROTO_RDMA }, + { "iscsi", SCSI_PROTO_ISCSI }, + { "iqn", SCSI_PROTO_ISCSI }, + { "sas", SCSI_PROTO_SAS }, + { "aditp", SCSI_PROTO_ADITP }, + { "ata", SCSI_PROTO_ATA }, + { "uas", SCSI_PROTO_UAS }, + { "usb", SCSI_PROTO_UAS }, + { "sop", SCSI_PROTO_SOP } +}; + +const char * +scsi_nv_to_str(struct scsi_nv *table, int num_table_entries, uint64_t value) +{ + int i; + + for (i = 0; i < num_table_entries; i++) { + if (table[i].value == value) + return (table[i].name); + } return (NULL); } +/* + * Given a name/value table, find a value matching the given name. + * Return values: + * SCSI_NV_FOUND - match found + * SCSI_NV_AMBIGUOUS - more than one match, none of them exact + * SCSI_NV_NOT_FOUND - no match found + */ +scsi_nv_status +scsi_get_nv(struct scsi_nv *table, int num_table_entries, + char *name, int *table_entry, scsi_nv_flags flags) +{ + int i, num_matches = 0; + + for (i = 0; i < num_table_entries; i++) { + size_t table_len, name_len; + + table_len = strlen(table[i].name); + name_len = strlen(name); + + if ((((flags & SCSI_NV_FLAG_IG_CASE) != 0) + && (strncasecmp(table[i].name, name, name_len) == 0)) + || (((flags & SCSI_NV_FLAG_IG_CASE) == 0) + && (strncmp(table[i].name, name, name_len) == 0))) { + *table_entry = i; + + /* + * Check for an exact match. If we have the same + * number of characters in the table as the argument, + * and we already know they're the same, we have + * an exact match. + */ + if (table_len == name_len) + return (SCSI_NV_FOUND); + + /* + * Otherwise, bump up the number of matches. We'll + * see later how many we have. + */ + num_matches++; + } + } + + if (num_matches > 1) + return (SCSI_NV_AMBIGUOUS); + else if (num_matches == 1) + return (SCSI_NV_FOUND); + else + return (SCSI_NV_NOT_FOUND); +} + +/* + * Parse transport IDs for Fibre Channel, 1394 and SAS. Since these are + * all 64-bit numbers, the code is similar. + */ +int +scsi_parse_transportid_64bit(int proto_id, char *id_str, + struct scsi_transportid_header **hdr, + unsigned int *alloc_len, +#ifdef _KERNEL + struct malloc_type *type, int flags, +#endif + char *error_str, int error_str_len) +{ + uint64_t value; + char *endptr; + int retval; + size_t alloc_size; + + retval = 0; + + value = strtouq(id_str, &endptr, 0); + if (*endptr != '\0') { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: error " + "parsing ID %s, 64-bit number required", + __func__, id_str); + } + retval = 1; + goto bailout; + } + + switch (proto_id) { + case SCSI_PROTO_FC: + alloc_size = sizeof(struct scsi_transportid_fcp); + break; + case SCSI_PROTO_1394: + alloc_size = sizeof(struct scsi_transportid_1394); + break; + case SCSI_PROTO_SAS: + alloc_size = sizeof(struct scsi_transportid_sas); + break; + default: + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: unsupported " + "protocol %d", __func__, proto_id); + } + retval = 1; + goto bailout; + break; /* NOTREACHED */ + } +#ifdef _KERNEL + *hdr = malloc(alloc_size, type, flags); +#else /* _KERNEL */ + *hdr = malloc(alloc_size); +#endif /*_KERNEL */ + if (*hdr == NULL) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: unable to " + "allocate %zu bytes", __func__, alloc_size); + } + retval = 1; + goto bailout; + } + + *alloc_len = alloc_size; + + bzero(*hdr, alloc_size); + + switch (proto_id) { + case SCSI_PROTO_FC: { + struct scsi_transportid_fcp *fcp; + + fcp = (struct scsi_transportid_fcp *)(*hdr); + fcp->format_protocol = SCSI_PROTO_FC | + SCSI_TRN_FCP_FORMAT_DEFAULT; + scsi_u64to8b(value, fcp->n_port_name); + break; + } + case SCSI_PROTO_1394: { + struct scsi_transportid_1394 *sbp; + + sbp = (struct scsi_transportid_1394 *)(*hdr); + sbp->format_protocol = SCSI_PROTO_1394 | + SCSI_TRN_1394_FORMAT_DEFAULT; + scsi_u64to8b(value, sbp->eui64); + break; + } + case SCSI_PROTO_SAS: { + struct scsi_transportid_sas *sas; + + sas = (struct scsi_transportid_sas *)(*hdr); + sas->format_protocol = SCSI_PROTO_SAS | + SCSI_TRN_SAS_FORMAT_DEFAULT; + scsi_u64to8b(value, sas->sas_address); + break; + } + default: + break; + } +bailout: + return (retval); +} + +/* + * Parse a SPI (Parallel SCSI) address of the form: id,rel_tgt_port + */ +int +scsi_parse_transportid_spi(char *id_str, struct scsi_transportid_header **hdr, + unsigned int *alloc_len, +#ifdef _KERNEL + struct malloc_type *type, int flags, +#endif + char *error_str, int error_str_len) +{ + unsigned long scsi_addr, target_port; + struct scsi_transportid_spi *spi; + char *tmpstr, *endptr; + int retval; + + retval = 0; + + tmpstr = strsep(&id_str, ","); + if (tmpstr == NULL) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, + "%s: no ID found", __func__); + } + retval = 1; + goto bailout; + } + scsi_addr = strtoul(tmpstr, &endptr, 0); + if (*endptr != '\0') { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: error " + "parsing SCSI ID %s, number required", + __func__, tmpstr); + } + retval = 1; + goto bailout; + } + + if (id_str == NULL) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: no relative " + "target port found", __func__); + } + retval = 1; + goto bailout; + } + + target_port = strtoul(id_str, &endptr, 0); + if (*endptr != '\0') { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: error " + "parsing relative target port %s, number " + "required", __func__, id_str); + } + retval = 1; + goto bailout; + } +#ifdef _KERNEL + spi = malloc(sizeof(*spi), type, flags); +#else + spi = malloc(sizeof(*spi)); +#endif + if (spi == NULL) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: unable to " + "allocate %zu bytes", __func__, + sizeof(*spi)); + } + retval = 1; + goto bailout; + } + *alloc_len = sizeof(*spi); + bzero(spi, sizeof(*spi)); + + spi->format_protocol = SCSI_PROTO_SPI | SCSI_TRN_SPI_FORMAT_DEFAULT; + scsi_ulto2b(scsi_addr, spi->scsi_addr); + scsi_ulto2b(target_port, spi->rel_trgt_port_id); + + *hdr = (struct scsi_transportid_header *)spi; +bailout: + return (retval); +} + +/* + * Parse an RDMA/SRP Initiator Port ID string. This is 32 hexadecimal digits, + * optionally prefixed by "0x" or "0X". + */ +int +scsi_parse_transportid_rdma(char *id_str, struct scsi_transportid_header **hdr, + unsigned int *alloc_len, +#ifdef _KERNEL + struct malloc_type *type, int flags, +#endif + char *error_str, int error_str_len) +{ + struct scsi_transportid_rdma *rdma; + int retval; + size_t id_len, rdma_id_size; + uint8_t rdma_id[SCSI_TRN_RDMA_PORT_LEN]; + char *tmpstr; + unsigned int i, j; + + retval = 0; + id_len = strlen(id_str); + rdma_id_size = SCSI_TRN_RDMA_PORT_LEN; + + /* + * Check the size. It needs to be either 32 or 34 characters long. + */ + if ((id_len != (rdma_id_size * 2)) + && (id_len != ((rdma_id_size * 2) + 2))) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: RDMA ID " + "must be 32 hex digits (0x prefix " + "optional), only %zu seen", __func__, id_len); + } + retval = 1; + goto bailout; + } + + tmpstr = id_str; + /* + * If the user gave us 34 characters, the string needs to start + * with '0x'. + */ + if (id_len == ((rdma_id_size * 2) + 2)) { + if ((tmpstr[0] == '0') + && ((tmpstr[1] == 'x') || (tmpstr[1] == 'X'))) { + tmpstr += 2; + } else { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: RDMA " + "ID prefix, if used, must be \"0x\", " + "got %s", __func__, tmpstr); + } + retval = 1; + goto bailout; + } + } + bzero(rdma_id, sizeof(rdma_id)); + + /* + * Convert ASCII hex into binary bytes. There is no standard + * 128-bit integer type, and so no strtou128t() routine to convert + * from hex into a large integer. In the end, we're not going to + * an integer, but rather to a byte array, so that and the fact + * that we require the user to give us 32 hex digits simplifies the + * logic. + */ + for (i = 0; i < (rdma_id_size * 2); i++) { + int cur_shift; + unsigned char c; + + /* Increment the byte array one for every 2 hex digits */ + j = i >> 1; + + /* + * The first digit in every pair is the most significant + * 4 bits. The second is the least significant 4 bits. + */ + if ((i % 2) == 0) + cur_shift = 4; + else + cur_shift = 0; + + c = tmpstr[i]; + /* Convert the ASCII hex character into a number */ + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: " + "RDMA ID must be hex digits, got " + "invalid character %c", __func__, + tmpstr[i]); + } + retval = 1; + goto bailout; + } + /* + * The converted number can't be less than 0; the type is + * unsigned, and the subtraction logic will not give us + * a negative number. So we only need to make sure that + * the value is not greater than 0xf. (i.e. make sure the + * user didn't give us a value like "0x12jklmno"). + */ + if (c > 0xf) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: " + "RDMA ID must be hex digits, got " + "invalid character %c", __func__, + tmpstr[i]); + } + retval = 1; + goto bailout; + } + + rdma_id[j] |= c << cur_shift; + } + +#ifdef _KERNEL + rdma = malloc(sizeof(*rdma), type, flags); +#else + rdma = malloc(sizeof(*rdma)); +#endif + if (rdma == NULL) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: unable to " + "allocate %zu bytes", __func__, + sizeof(*rdma)); + } + retval = 1; + goto bailout; + } + *alloc_len = sizeof(*rdma); + bzero(rdma, *alloc_len); + + rdma->format_protocol = SCSI_PROTO_RDMA | SCSI_TRN_RDMA_FORMAT_DEFAULT; + bcopy(rdma_id, rdma->initiator_port_id, SCSI_TRN_RDMA_PORT_LEN); + + *hdr = (struct scsi_transportid_header *)rdma; + +bailout: + return (retval); +} + +/* + * Parse an iSCSI name. The format is either just the name: + * + * iqn.2012-06.com.example:target0 + * or the name, separator and initiator session ID: + * + * iqn.2012-06.com.example:target0,i,0x123 + * + * The separator format is exact. + */ +int +scsi_parse_transportid_iscsi(char *id_str, struct scsi_transportid_header **hdr, + unsigned int *alloc_len, +#ifdef _KERNEL + struct malloc_type *type, int flags, +#endif + char *error_str, int error_str_len) +{ + size_t id_len, sep_len, id_size, name_len; + int retval; + unsigned int i, sep_pos, sep_found; + const char *sep_template = ",i,0x"; + const char *iqn_prefix = "iqn."; + struct scsi_transportid_iscsi_device *iscsi; + + retval = 0; + sep_found = 0; + + id_len = strlen(id_str); + sep_len = strlen(sep_template); + + /* + * The separator is defined as exactly ',i,0x'. Any other commas, + * or any other form, is an error. So look for a comma, and once + * we find that, the next few characters must match the separator + * exactly. Once we get through the separator, there should be at + * least one character. + */ + for (i = 0, sep_pos = 0; i < id_len; i++) { + if (sep_pos == 0) { + if (id_str[i] == sep_template[sep_pos]) + sep_pos++; + + continue; + } + if (sep_pos < sep_len) { + if (id_str[i] == sep_template[sep_pos]) { + sep_pos++; + continue; + } + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: " + "invalid separator in iSCSI name " + "\"%s\"", + __func__, id_str); + } + retval = 1; + goto bailout; + } else { + sep_found = 1; + break; + } + } + + /* + * Check to see whether we have a separator but no digits after it. + */ + if ((sep_pos != 0) + && (sep_found == 0)) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: no digits " + "found after separator in iSCSI name \"%s\"", + __func__, id_str); + } + retval = 1; + goto bailout; + } + + /* + * The incoming ID string has the "iqn." prefix stripped off. We + * need enough space for the base structure (the structures are the + * same for the two iSCSI forms), the prefix, the ID string and a + * terminating NUL. + */ + id_size = sizeof(*iscsi) + strlen(iqn_prefix) + id_len + 1; + +#ifdef _KERNEL + iscsi = malloc(id_size, type, flags); +#else + iscsi = malloc(id_size); +#endif + if (iscsi == NULL) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: unable to " + "allocate %zu bytes", __func__, id_size); + } + retval = 1; + goto bailout; + } + *alloc_len = id_size; + bzero(iscsi, id_size); + + iscsi->format_protocol = SCSI_PROTO_ISCSI; + if (sep_found == 0) + iscsi->format_protocol |= SCSI_TRN_ISCSI_FORMAT_DEVICE; + else + iscsi->format_protocol |= SCSI_TRN_ISCSI_FORMAT_PORT; + name_len = id_size - sizeof(*iscsi); + scsi_ulto2b(name_len, iscsi->additional_length); + snprintf(iscsi->iscsi_name, name_len, "%s%s", iqn_prefix, id_str); + + *hdr = (struct scsi_transportid_header *)iscsi; + +bailout: + return (retval); +} + +/* + * Parse a SCSI over PCIe (SOP) identifier. The Routing ID can either be + * of the form 'bus,device,function' or 'bus,function'. + */ +int +scsi_parse_transportid_sop(char *id_str, struct scsi_transportid_header **hdr, + unsigned int *alloc_len, +#ifdef _KERNEL + struct malloc_type *type, int flags, +#endif + char *error_str, int error_str_len) +{ + struct scsi_transportid_sop *sop; + unsigned long bus, device, function; + char *tmpstr, *endptr; + int retval, device_spec; + + retval = 0; + device_spec = 0; + device = 0; + + tmpstr = strsep(&id_str, ","); + if ((tmpstr == NULL) + || (*tmpstr == '\0')) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: no ID found", + __func__); + } + retval = 1; + goto bailout; + } + bus = strtoul(tmpstr, &endptr, 0); + if (*endptr != '\0') { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: error " + "parsing PCIe bus %s, number required", + __func__, tmpstr); + } + retval = 1; + goto bailout; + } + if ((id_str == NULL) + || (*id_str == '\0')) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: no PCIe " + "device or function found", __func__); + } + retval = 1; + goto bailout; + } + tmpstr = strsep(&id_str, ","); + function = strtoul(tmpstr, &endptr, 0); + if (*endptr != '\0') { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: error " + "parsing PCIe device/function %s, number " + "required", __func__, tmpstr); + } + retval = 1; + goto bailout; + } + /* + * Check to see whether the user specified a third value. If so, + * the second is the device. + */ + if (id_str != NULL) { + if (*id_str == '\0') { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: " + "no PCIe function found", __func__); + } + retval = 1; + goto bailout; + } + device = function; + device_spec = 1; + function = strtoul(id_str, &endptr, 0); + if (*endptr != '\0') { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: " + "error parsing PCIe function %s, " + "number required", __func__, id_str); + } + retval = 1; + goto bailout; + } + } + if (bus > SCSI_TRN_SOP_BUS_MAX) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: bus value " + "%lu greater than maximum %u", __func__, + bus, SCSI_TRN_SOP_BUS_MAX); + } + retval = 1; + goto bailout; + } + + if ((device_spec != 0) + && (device > SCSI_TRN_SOP_DEV_MASK)) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: device value " + "%lu greater than maximum %u", __func__, + device, SCSI_TRN_SOP_DEV_MAX); + } + retval = 1; + goto bailout; + } + + if (((device_spec != 0) + && (function > SCSI_TRN_SOP_FUNC_NORM_MAX)) + || ((device_spec == 0) + && (function > SCSI_TRN_SOP_FUNC_ALT_MAX))) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: function value " + "%lu greater than maximum %u", __func__, + function, (device_spec == 0) ? + SCSI_TRN_SOP_FUNC_ALT_MAX : + SCSI_TRN_SOP_FUNC_NORM_MAX); + } + retval = 1; + goto bailout; + } + +#ifdef _KERNEL + sop = malloc(sizeof(*sop), type, flags); +#else + sop = malloc(sizeof(*sop)); +#endif + if (sop == NULL) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: unable to " + "allocate %zu bytes", __func__, sizeof(*sop)); + } + retval = 1; + goto bailout; + } + *alloc_len = sizeof(*sop); + bzero(sop, sizeof(*sop)); + sop->format_protocol = SCSI_PROTO_SOP | SCSI_TRN_SOP_FORMAT_DEFAULT; + if (device_spec != 0) { + struct scsi_sop_routing_id_norm rid; + + rid.bus = bus; + rid.devfunc = (device << SCSI_TRN_SOP_DEV_SHIFT) | function; + bcopy(&rid, sop->routing_id, MIN(sizeof(rid), + sizeof(sop->routing_id))); + } else { + struct scsi_sop_routing_id_alt rid; + + rid.bus = bus; + rid.function = function; + bcopy(&rid, sop->routing_id, MIN(sizeof(rid), + sizeof(sop->routing_id))); + } + + *hdr = (struct scsi_transportid_header *)sop; +bailout: + return (retval); +} + +/* + * transportid_str: NUL-terminated string with format: protcol,id + * The ID is protocol specific. + * hdr: Storage will be allocated for the transport ID. + * alloc_len: The amount of memory allocated is returned here. + * type: Malloc bucket (kernel only). + * flags: Malloc flags (kernel only). + * error_str: If non-NULL, it will contain error information (without + * a terminating newline) if an error is returned. + * error_str_len: Allocated length of the error string. + * + * Returns 0 for success, non-zero for failure. + */ +int +scsi_parse_transportid(char *transportid_str, + struct scsi_transportid_header **hdr, + unsigned int *alloc_len, +#ifdef _KERNEL + struct malloc_type *type, int flags, +#endif + char *error_str, int error_str_len) +{ + char *tmpstr; + scsi_nv_status status; + u_int num_proto_entries; + int retval, table_entry; + + retval = 0; + table_entry = 0; + + /* + * We do allow a period as well as a comma to separate the protocol + * from the ID string. This is to accommodate iSCSI names, which + * start with "iqn.". + */ + tmpstr = strsep(&transportid_str, ",."); + if (tmpstr == NULL) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, + "%s: transportid_str is NULL", __func__); + } + retval = 1; + goto bailout; + } + + num_proto_entries = nitems(scsi_proto_map); + status = scsi_get_nv(scsi_proto_map, num_proto_entries, tmpstr, + &table_entry, SCSI_NV_FLAG_IG_CASE); + if (status != SCSI_NV_FOUND) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: %s protocol " + "name %s", __func__, + (status == SCSI_NV_AMBIGUOUS) ? "ambiguous" : + "invalid", tmpstr); + } + retval = 1; + goto bailout; + } + switch (scsi_proto_map[table_entry].value) { + case SCSI_PROTO_FC: + case SCSI_PROTO_1394: + case SCSI_PROTO_SAS: + retval = scsi_parse_transportid_64bit( + scsi_proto_map[table_entry].value, transportid_str, hdr, + alloc_len, +#ifdef _KERNEL + type, flags, +#endif + error_str, error_str_len); + break; + case SCSI_PROTO_SPI: + retval = scsi_parse_transportid_spi(transportid_str, hdr, + alloc_len, +#ifdef _KERNEL + type, flags, +#endif + error_str, error_str_len); + break; + case SCSI_PROTO_RDMA: + retval = scsi_parse_transportid_rdma(transportid_str, hdr, + alloc_len, +#ifdef _KERNEL + type, flags, +#endif + error_str, error_str_len); + break; + case SCSI_PROTO_ISCSI: + retval = scsi_parse_transportid_iscsi(transportid_str, hdr, + alloc_len, +#ifdef _KERNEL + type, flags, +#endif + error_str, error_str_len); + break; + case SCSI_PROTO_SOP: + retval = scsi_parse_transportid_sop(transportid_str, hdr, + alloc_len, +#ifdef _KERNEL + type, flags, +#endif + error_str, error_str_len); + break; + case SCSI_PROTO_SSA: + case SCSI_PROTO_ADITP: + case SCSI_PROTO_ATA: + case SCSI_PROTO_UAS: + case SCSI_PROTO_NONE: + default: + /* + * There is no format defined for a Transport ID for these + * protocols. So even if the user gives us something, we + * have no way to turn it into a standard SCSI Transport ID. + */ + retval = 1; + if (error_str != NULL) { + snprintf(error_str, error_str_len, "%s: no Transport " + "ID format exists for protocol %s", + __func__, tmpstr); + } + goto bailout; + break; /* NOTREACHED */ + } +bailout: + return (retval); +} + +struct scsi_attrib_table_entry scsi_mam_attr_table[] = { + { SMA_ATTR_REM_CAP_PARTITION, SCSI_ATTR_FLAG_NONE, + "Remaining Capacity in Partition", + /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf,/*parse_str*/ NULL }, + { SMA_ATTR_MAX_CAP_PARTITION, SCSI_ATTR_FLAG_NONE, + "Maximum Capacity in Partition", + /*suffix*/"MB", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, + { SMA_ATTR_TAPEALERT_FLAGS, SCSI_ATTR_FLAG_HEX, + "TapeAlert Flags", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, + { SMA_ATTR_LOAD_COUNT, SCSI_ATTR_FLAG_NONE, + "Load Count", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, + { SMA_ATTR_MAM_SPACE_REMAINING, SCSI_ATTR_FLAG_NONE, + "MAM Space Remaining", + /*suffix*/"bytes", /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_DEV_ASSIGNING_ORG, SCSI_ATTR_FLAG_NONE, + "Assigning Organization", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_FORMAT_DENSITY_CODE, SCSI_ATTR_FLAG_HEX, + "Format Density Code", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, + { SMA_ATTR_INITIALIZATION_COUNT, SCSI_ATTR_FLAG_NONE, + "Initialization Count", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, + { SMA_ATTR_VOLUME_ID, SCSI_ATTR_FLAG_NONE, + "Volume Identifier", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_VOLUME_CHANGE_REF, SCSI_ATTR_FLAG_HEX, + "Volume Change Reference", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_DEV_SERIAL_LAST_LOAD, SCSI_ATTR_FLAG_NONE, + "Device Vendor/Serial at Last Load", + /*suffix*/NULL, /*to_str*/ scsi_attrib_vendser_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_DEV_SERIAL_LAST_LOAD_1, SCSI_ATTR_FLAG_NONE, + "Device Vendor/Serial at Last Load - 1", + /*suffix*/NULL, /*to_str*/ scsi_attrib_vendser_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_DEV_SERIAL_LAST_LOAD_2, SCSI_ATTR_FLAG_NONE, + "Device Vendor/Serial at Last Load - 2", + /*suffix*/NULL, /*to_str*/ scsi_attrib_vendser_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_DEV_SERIAL_LAST_LOAD_3, SCSI_ATTR_FLAG_NONE, + "Device Vendor/Serial at Last Load - 3", + /*suffix*/NULL, /*to_str*/ scsi_attrib_vendser_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_TOTAL_MB_WRITTEN_LT, SCSI_ATTR_FLAG_NONE, + "Total MB Written in Medium Life", + /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_TOTAL_MB_READ_LT, SCSI_ATTR_FLAG_NONE, + "Total MB Read in Medium Life", + /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_TOTAL_MB_WRITTEN_CUR, SCSI_ATTR_FLAG_NONE, + "Total MB Written in Current/Last Load", + /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_TOTAL_MB_READ_CUR, SCSI_ATTR_FLAG_NONE, + "Total MB Read in Current/Last Load", + /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_FIRST_ENC_BLOCK, SCSI_ATTR_FLAG_NONE, + "Logical Position of First Encrypted Block", + /*suffix*/ NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_NEXT_UNENC_BLOCK, SCSI_ATTR_FLAG_NONE, + "Logical Position of First Unencrypted Block after First " + "Encrypted Block", + /*suffix*/ NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MEDIUM_USAGE_HIST, SCSI_ATTR_FLAG_NONE, + "Medium Usage History", + /*suffix*/ NULL, /*to_str*/ NULL, + /*parse_str*/ NULL }, + { SMA_ATTR_PART_USAGE_HIST, SCSI_ATTR_FLAG_NONE, + "Partition Usage History", + /*suffix*/ NULL, /*to_str*/ NULL, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_MANUF, SCSI_ATTR_FLAG_NONE, + "Medium Manufacturer", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_SERIAL, SCSI_ATTR_FLAG_NONE, + "Medium Serial Number", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_LENGTH, SCSI_ATTR_FLAG_NONE, + "Medium Length", + /*suffix*/"m", /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_WIDTH, SCSI_ATTR_FLAG_FP | SCSI_ATTR_FLAG_DIV_10 | + SCSI_ATTR_FLAG_FP_1DIGIT, + "Medium Width", + /*suffix*/"mm", /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_ASSIGNING_ORG, SCSI_ATTR_FLAG_NONE, + "Assigning Organization", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_DENSITY_CODE, SCSI_ATTR_FLAG_HEX, + "Medium Density Code", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_MANUF_DATE, SCSI_ATTR_FLAG_NONE, + "Medium Manufacture Date", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MAM_CAPACITY, SCSI_ATTR_FLAG_NONE, + "MAM Capacity", + /*suffix*/"bytes", /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_TYPE, SCSI_ATTR_FLAG_HEX, + "Medium Type", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_TYPE_INFO, SCSI_ATTR_FLAG_HEX, + "Medium Type Information", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MED_SERIAL_NUM, SCSI_ATTR_FLAG_NONE, + "Medium Serial Number", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_APP_VENDOR, SCSI_ATTR_FLAG_NONE, + "Application Vendor", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_APP_NAME, SCSI_ATTR_FLAG_NONE, + "Application Name", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_APP_VERSION, SCSI_ATTR_FLAG_NONE, + "Application Version", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_USER_MED_TEXT_LABEL, SCSI_ATTR_FLAG_NONE, + "User Medium Text Label", + /*suffix*/NULL, /*to_str*/ scsi_attrib_text_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_LAST_WRITTEN_TIME, SCSI_ATTR_FLAG_NONE, + "Date and Time Last Written", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_TEXT_LOCAL_ID, SCSI_ATTR_FLAG_HEX, + "Text Localization Identifier", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_BARCODE, SCSI_ATTR_FLAG_NONE, + "Barcode", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_HOST_OWNER_NAME, SCSI_ATTR_FLAG_NONE, + "Owning Host Textual Name", + /*suffix*/NULL, /*to_str*/ scsi_attrib_text_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_MEDIA_POOL, SCSI_ATTR_FLAG_NONE, + "Media Pool", + /*suffix*/NULL, /*to_str*/ scsi_attrib_text_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_PART_USER_LABEL, SCSI_ATTR_FLAG_NONE, + "Partition User Text Label", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_LOAD_UNLOAD_AT_PART, SCSI_ATTR_FLAG_NONE, + "Load/Unload at Partition", + /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_APP_FORMAT_VERSION, SCSI_ATTR_FLAG_NONE, + "Application Format Version", + /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, + /*parse_str*/ NULL }, + { SMA_ATTR_VOL_COHERENCY_INFO, SCSI_ATTR_FLAG_NONE, + "Volume Coherency Information", + /*suffix*/NULL, /*to_str*/ scsi_attrib_volcoh_sbuf, + /*parse_str*/ NULL }, + { 0x0ff1, SCSI_ATTR_FLAG_NONE, + "Spectra MLM Creation", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x0ff2, SCSI_ATTR_FLAG_NONE, + "Spectra MLM C3", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x0ff3, SCSI_ATTR_FLAG_NONE, + "Spectra MLM RW", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x0ff4, SCSI_ATTR_FLAG_NONE, + "Spectra MLM SDC List", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x0ff7, SCSI_ATTR_FLAG_NONE, + "Spectra MLM Post Scan", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x0ffe, SCSI_ATTR_FLAG_NONE, + "Spectra MLM Checksum", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x17f1, SCSI_ATTR_FLAG_NONE, + "Spectra MLM Creation", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x17f2, SCSI_ATTR_FLAG_NONE, + "Spectra MLM C3", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x17f3, SCSI_ATTR_FLAG_NONE, + "Spectra MLM RW", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x17f4, SCSI_ATTR_FLAG_NONE, + "Spectra MLM SDC List", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x17f7, SCSI_ATTR_FLAG_NONE, + "Spectra MLM Post Scan", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, + { 0x17ff, SCSI_ATTR_FLAG_NONE, + "Spectra MLM Checksum", + /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, + /*parse_str*/ NULL }, +}; + +/* + * Print out Volume Coherency Information (Attribute 0x080c). + * This field has two variable length members, including one at the + * beginning, so it isn't practical to have a fixed structure definition. + * This is current as of SSC4r03 (see section 4.2.21.3), dated March 25, + * 2013. + */ +int +scsi_attrib_volcoh_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, + uint32_t valid_len, uint32_t flags, + uint32_t output_flags, char *error_str, + int error_str_len) +{ + size_t avail_len; + uint32_t field_size; + uint64_t tmp_val; + uint8_t *cur_ptr; + int retval; + int vcr_len, as_len; + + retval = 0; + tmp_val = 0; + + field_size = scsi_2btoul(hdr->length); + avail_len = valid_len - sizeof(*hdr); + if (field_size > avail_len) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "Available " + "length of attribute ID 0x%.4x %zu < field " + "length %u", scsi_2btoul(hdr->id), avail_len, + field_size); + } + retval = 1; + goto bailout; + } else if (field_size == 0) { + /* + * It isn't clear from the spec whether a field length of + * 0 is invalid here. It probably is, but be lenient here + * to avoid inconveniencing the user. + */ + goto bailout; + } + cur_ptr = hdr->attribute; + vcr_len = *cur_ptr; + cur_ptr++; + + sbuf_printf(sb, "\n\tVolume Change Reference Value:"); + + switch (vcr_len) { + case 0: + if (error_str != NULL) { + snprintf(error_str, error_str_len, "Volume Change " + "Reference value has length of 0"); + } + retval = 1; + goto bailout; + break; /*NOTREACHED*/ + case 1: + tmp_val = *cur_ptr; + break; + case 2: + tmp_val = scsi_2btoul(cur_ptr); + break; + case 3: + tmp_val = scsi_3btoul(cur_ptr); + break; + case 4: + tmp_val = scsi_4btoul(cur_ptr); + break; + case 8: + tmp_val = scsi_8btou64(cur_ptr); + break; + default: + sbuf_printf(sb, "\n"); + sbuf_hexdump(sb, cur_ptr, vcr_len, NULL, 0); + break; + } + if (vcr_len <= 8) + sbuf_printf(sb, " 0x%jx\n", (uintmax_t)tmp_val); + + cur_ptr += vcr_len; + tmp_val = scsi_8btou64(cur_ptr); + sbuf_printf(sb, "\tVolume Coherency Count: %ju\n", (uintmax_t)tmp_val); + + cur_ptr += sizeof(tmp_val); + tmp_val = scsi_8btou64(cur_ptr); + sbuf_printf(sb, "\tVolume Coherency Set Identifier: 0x%jx\n", + (uintmax_t)tmp_val); + + /* + * Figure out how long the Application Client Specific Information + * is and produce a hexdump. + */ + cur_ptr += sizeof(tmp_val); + as_len = scsi_2btoul(cur_ptr); + cur_ptr += sizeof(uint16_t); + sbuf_printf(sb, "\tApplication Client Specific Information: "); + if (((as_len == SCSI_LTFS_VER0_LEN) + || (as_len == SCSI_LTFS_VER1_LEN)) + && (strncmp(cur_ptr, SCSI_LTFS_STR_NAME, SCSI_LTFS_STR_LEN) == 0)) { + sbuf_printf(sb, "LTFS\n"); + cur_ptr += SCSI_LTFS_STR_LEN + 1; + if (cur_ptr[SCSI_LTFS_UUID_LEN] != '\0') + cur_ptr[SCSI_LTFS_UUID_LEN] = '\0'; + sbuf_printf(sb, "\tLTFS UUID: %s\n", cur_ptr); + cur_ptr += SCSI_LTFS_UUID_LEN + 1; + /* XXX KDM check the length */ + sbuf_printf(sb, "\tLTFS Version: %d\n", *cur_ptr); + } else { + sbuf_printf(sb, "Unknown\n"); + sbuf_hexdump(sb, cur_ptr, as_len, NULL, 0); + } + +bailout: + return (retval); +} + +int +scsi_attrib_vendser_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, + uint32_t valid_len, uint32_t flags, + uint32_t output_flags, char *error_str, + int error_str_len) +{ + size_t avail_len; + uint32_t field_size; + struct scsi_attrib_vendser *vendser; + cam_strvis_flags strvis_flags; + int retval = 0; + + field_size = scsi_2btoul(hdr->length); + avail_len = valid_len - sizeof(*hdr); + if (field_size > avail_len) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "Available " + "length of attribute ID 0x%.4x %zu < field " + "length %u", scsi_2btoul(hdr->id), avail_len, + field_size); + } + retval = 1; + goto bailout; + } else if (field_size == 0) { + /* + * A field size of 0 doesn't make sense here. The device + * can at least give you the vendor ID, even if it can't + * give you the serial number. + */ + if (error_str != NULL) { + snprintf(error_str, error_str_len, "The length of " + "attribute ID 0x%.4x is 0", + scsi_2btoul(hdr->id)); + } + retval = 1; + goto bailout; + } + vendser = (struct scsi_attrib_vendser *)hdr->attribute; + + switch (output_flags & SCSI_ATTR_OUTPUT_NONASCII_MASK) { + case SCSI_ATTR_OUTPUT_NONASCII_TRIM: + strvis_flags = CAM_STRVIS_FLAG_NONASCII_TRIM; + break; + case SCSI_ATTR_OUTPUT_NONASCII_RAW: + strvis_flags = CAM_STRVIS_FLAG_NONASCII_RAW; + break; + case SCSI_ATTR_OUTPUT_NONASCII_ESC: + default: + strvis_flags = CAM_STRVIS_FLAG_NONASCII_ESC; + break;; + } + cam_strvis_sbuf(sb, vendser->vendor, sizeof(vendser->vendor), + strvis_flags); + sbuf_putc(sb, ' '); + cam_strvis_sbuf(sb, vendser->serial_num, sizeof(vendser->serial_num), + strvis_flags); +bailout: + return (retval); +} + +int +scsi_attrib_hexdump_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, + uint32_t valid_len, uint32_t flags, + uint32_t output_flags, char *error_str, + int error_str_len) +{ + uint32_t field_size; + ssize_t avail_len; + uint32_t print_len; + uint8_t *num_ptr; + int retval = 0; + + field_size = scsi_2btoul(hdr->length); + avail_len = valid_len - sizeof(*hdr); + print_len = MIN(avail_len, field_size); + num_ptr = hdr->attribute; + + if (print_len > 0) { + sbuf_printf(sb, "\n"); + sbuf_hexdump(sb, num_ptr, print_len, NULL, 0); + } + + return (retval); +} + +int +scsi_attrib_int_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, + uint32_t valid_len, uint32_t flags, + uint32_t output_flags, char *error_str, + int error_str_len) +{ + uint64_t print_number; + size_t avail_len; + uint32_t number_size; + int retval = 0; + + number_size = scsi_2btoul(hdr->length); + + avail_len = valid_len - sizeof(*hdr); + if (avail_len < number_size) { + if (error_str != NULL) { + snprintf(error_str, error_str_len, "Available " + "length of attribute ID 0x%.4x %zu < field " + "length %u", scsi_2btoul(hdr->id), avail_len, + number_size); + } + retval = 1; + goto bailout; + } + + switch (number_size) { + case 0: + /* + * We don't treat this as an error, since there may be + * scenarios where a device reports a field but then gives + * a length of 0. See the note in scsi_attrib_ascii_sbuf(). + */ + goto bailout; + break; /*NOTREACHED*/ + case 1: + print_number = hdr->attribute[0]; + break; + case 2: + print_number = scsi_2btoul(hdr->attribute); + break; + case 3: + print_number = scsi_3btoul(hdr->attribute); + break; + case 4: + print_number = scsi_4btoul(hdr->attribute); + break; + case 8: + print_number = scsi_8btou64(hdr->attribute); + break; + default: + /* + * If we wind up here, the number is too big to print + * normally, so just do a hexdump. + */ + retval = scsi_attrib_hexdump_sbuf(sb, hdr, valid_len, + flags, output_flags, + error_str, error_str_len); + goto bailout; + break; + } + + if (flags & SCSI_ATTR_FLAG_FP) { +#ifndef _KERNEL + long double num_float; + + num_float = (long double)print_number; + + if (flags & SCSI_ATTR_FLAG_DIV_10) + num_float /= 10; + + sbuf_printf(sb, "%.*Lf", (flags & SCSI_ATTR_FLAG_FP_1DIGIT) ? + 1 : 0, num_float); +#else /* _KERNEL */ + sbuf_printf(sb, "%ju", (flags & SCSI_ATTR_FLAG_DIV_10) ? + (print_number / 10) : print_number); +#endif /* _KERNEL */ + } else if (flags & SCSI_ATTR_FLAG_HEX) { + sbuf_printf(sb, "0x%jx", (uintmax_t)print_number); + } else + sbuf_printf(sb, "%ju", (uintmax_t)print_number); + +bailout: + return (retval); +} + +int +scsi_attrib_ascii_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, + uint32_t valid_len, uint32_t flags, + uint32_t output_flags, char *error_str, + int error_str_len) +{ + size_t avail_len; + uint32_t field_size, print_size; + int retval = 0; + + avail_len = valid_len - sizeof(*hdr); + field_size = scsi_2btoul(hdr->length); + print_size = MIN(avail_len, field_size); + + if (print_size > 0) { + cam_strvis_flags strvis_flags; + + switch (output_flags & SCSI_ATTR_OUTPUT_NONASCII_MASK) { + case SCSI_ATTR_OUTPUT_NONASCII_TRIM: + strvis_flags = CAM_STRVIS_FLAG_NONASCII_TRIM; + break; + case SCSI_ATTR_OUTPUT_NONASCII_RAW: + strvis_flags = CAM_STRVIS_FLAG_NONASCII_RAW; + break; + case SCSI_ATTR_OUTPUT_NONASCII_ESC: + default: + strvis_flags = CAM_STRVIS_FLAG_NONASCII_ESC; + break; + } + cam_strvis_sbuf(sb, hdr->attribute, print_size, strvis_flags); + } else if (avail_len < field_size) { + /* + * We only report an error if the user didn't allocate + * enough space to hold the full value of this field. If + * the field length is 0, that is allowed by the spec. + * e.g. in SPC-4r37, section 7.4.2.2.5, VOLUME IDENTIFIER + * "This attribute indicates the current volume identifier + * (see SMC-3) of the medium. If the device server supports + * this attribute but does not have access to the volume + * identifier, the device server shall report this attribute + * with an attribute length value of zero." + */ + if (error_str != NULL) { + snprintf(error_str, error_str_len, "Available " + "length of attribute ID 0x%.4x %zu < field " + "length %u", scsi_2btoul(hdr->id), avail_len, + field_size); + } + retval = 1; + } + + return (retval); +} + +int +scsi_attrib_text_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, + uint32_t valid_len, uint32_t flags, + uint32_t output_flags, char *error_str, + int error_str_len) +{ + size_t avail_len; + uint32_t field_size, print_size; + int retval = 0; + int esc_text = 1; + + avail_len = valid_len - sizeof(*hdr); + field_size = scsi_2btoul(hdr->length); + print_size = MIN(avail_len, field_size); + + if ((output_flags & SCSI_ATTR_OUTPUT_TEXT_MASK) == + SCSI_ATTR_OUTPUT_TEXT_RAW) + esc_text = 0; + + if (print_size > 0) { + uint32_t i; + + for (i = 0; i < print_size; i++) { + if (hdr->attribute[i] == '\0') + continue; + else if (((unsigned char)hdr->attribute[i] < 0x80) + || (esc_text == 0)) + sbuf_putc(sb, hdr->attribute[i]); + else + sbuf_printf(sb, "%%%02x", + (unsigned char)hdr->attribute[i]); + } + } else if (avail_len < field_size) { + /* + * We only report an error if the user didn't allocate + * enough space to hold the full value of this field. + */ + if (error_str != NULL) { + snprintf(error_str, error_str_len, "Available " + "length of attribute ID 0x%.4x %zu < field " + "length %u", scsi_2btoul(hdr->id), avail_len, + field_size); + } + retval = 1; + } + + return (retval); +} + +struct scsi_attrib_table_entry * +scsi_find_attrib_entry(struct scsi_attrib_table_entry *table, + size_t num_table_entries, uint32_t id) +{ + uint32_t i; + + for (i = 0; i < num_table_entries; i++) { + if (table[i].id == id) + return (&table[i]); + } + + return (NULL); +} + +struct scsi_attrib_table_entry * +scsi_get_attrib_entry(uint32_t id) +{ + return (scsi_find_attrib_entry(scsi_mam_attr_table, + nitems(scsi_mam_attr_table), id)); +} + +int +scsi_attrib_value_sbuf(struct sbuf *sb, uint32_t valid_len, + struct scsi_mam_attribute_header *hdr, uint32_t output_flags, + char *error_str, size_t error_str_len) +{ + int retval; + + switch (hdr->byte2 & SMA_FORMAT_MASK) { + case SMA_FORMAT_ASCII: + retval = scsi_attrib_ascii_sbuf(sb, hdr, valid_len, + SCSI_ATTR_FLAG_NONE, output_flags, error_str,error_str_len); + break; + case SMA_FORMAT_BINARY: + if (scsi_2btoul(hdr->length) <= 8) + retval = scsi_attrib_int_sbuf(sb, hdr, valid_len, + SCSI_ATTR_FLAG_NONE, output_flags, error_str, + error_str_len); + else + retval = scsi_attrib_hexdump_sbuf(sb, hdr, valid_len, + SCSI_ATTR_FLAG_NONE, output_flags, error_str, + error_str_len); + break; + case SMA_FORMAT_TEXT: + retval = scsi_attrib_text_sbuf(sb, hdr, valid_len, + SCSI_ATTR_FLAG_NONE, output_flags, error_str, + error_str_len); + break; + default: + if (error_str != NULL) { + snprintf(error_str, error_str_len, "Unknown attribute " + "format 0x%x", hdr->byte2 & SMA_FORMAT_MASK); + } + retval = 1; + goto bailout; + break; /*NOTREACHED*/ + } + + sbuf_trim(sb); + +bailout: + + return (retval); +} + +void +scsi_attrib_prefix_sbuf(struct sbuf *sb, uint32_t output_flags, + struct scsi_mam_attribute_header *hdr, + uint32_t valid_len, const char *desc) +{ + int need_space = 0; + uint32_t len; + uint32_t id; + + /* + * We can't do anything if we don't have enough valid data for the + * header. + */ + if (valid_len < sizeof(*hdr)) + return; + + id = scsi_2btoul(hdr->id); + /* + * Note that we print out the value of the attribute listed in the + * header, regardless of whether we actually got that many bytes + * back from the device through the controller. A truncated result + * could be the result of a failure to ask for enough data; the + * header indicates how many bytes are allocated for this attribute + * in the MAM. + */ + len = scsi_2btoul(hdr->length); + + if ((output_flags & SCSI_ATTR_OUTPUT_FIELD_MASK) == + SCSI_ATTR_OUTPUT_FIELD_NONE) + return; + + if ((output_flags & SCSI_ATTR_OUTPUT_FIELD_DESC) + && (desc != NULL)) { + sbuf_printf(sb, "%s", desc); + need_space = 1; + } + + if (output_flags & SCSI_ATTR_OUTPUT_FIELD_NUM) { + sbuf_printf(sb, "%s(0x%.4x)", (need_space) ? " " : "", id); + need_space = 0; + } + + if (output_flags & SCSI_ATTR_OUTPUT_FIELD_SIZE) { + sbuf_printf(sb, "%s[%d]", (need_space) ? " " : "", len); + need_space = 0; + } + if (output_flags & SCSI_ATTR_OUTPUT_FIELD_RW) { + sbuf_printf(sb, "%s(%s)", (need_space) ? " " : "", + (hdr->byte2 & SMA_READ_ONLY) ? "RO" : "RW"); + } + sbuf_printf(sb, ": "); +} + +int +scsi_attrib_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, + uint32_t valid_len, struct scsi_attrib_table_entry *user_table, + size_t num_user_entries, int prefer_user_table, + uint32_t output_flags, char *error_str, int error_str_len) +{ + int retval; + struct scsi_attrib_table_entry *table1 = NULL, *table2 = NULL; + struct scsi_attrib_table_entry *entry = NULL; + size_t table1_size = 0, table2_size = 0; + uint32_t id; + + retval = 0; + + if (valid_len < sizeof(*hdr)) { + retval = 1; + goto bailout; + } + + id = scsi_2btoul(hdr->id); + + if (user_table != NULL) { + if (prefer_user_table != 0) { + table1 = user_table; + table1_size = num_user_entries; + table2 = scsi_mam_attr_table; + table2_size = nitems(scsi_mam_attr_table); + } else { + table1 = scsi_mam_attr_table; + table1_size = nitems(scsi_mam_attr_table); + table2 = user_table; + table2_size = num_user_entries; + } + } else { + table1 = scsi_mam_attr_table; + table1_size = nitems(scsi_mam_attr_table); + } + + entry = scsi_find_attrib_entry(table1, table1_size, id); + if (entry != NULL) { + scsi_attrib_prefix_sbuf(sb, output_flags, hdr, valid_len, + entry->desc); + if (entry->to_str == NULL) + goto print_default; + retval = entry->to_str(sb, hdr, valid_len, entry->flags, + output_flags, error_str, error_str_len); + goto bailout; + } + if (table2 != NULL) { + entry = scsi_find_attrib_entry(table2, table2_size, id); + if (entry != NULL) { + if (entry->to_str == NULL) + goto print_default; + + scsi_attrib_prefix_sbuf(sb, output_flags, hdr, + valid_len, entry->desc); + retval = entry->to_str(sb, hdr, valid_len, entry->flags, + output_flags, error_str, + error_str_len); + goto bailout; + } + } + + scsi_attrib_prefix_sbuf(sb, output_flags, hdr, valid_len, NULL); + +print_default: + retval = scsi_attrib_value_sbuf(sb, valid_len, hdr, output_flags, + error_str, error_str_len); +bailout: + if (retval == 0) { + if ((entry != NULL) + && (entry->suffix != NULL)) + sbuf_printf(sb, " %s", entry->suffix); + + sbuf_trim(sb); + sbuf_printf(sb, "\n"); + } + + return (retval); +} + void scsi_test_unit_ready(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), @@ -5500,7 +7514,6 @@ scsi_test_unit_ready(struct ccb_scsiio *csio, u_int32_t retries, scsi_cmd->opcode = TEST_UNIT_READY; } -#ifndef __rtems__ void scsi_request_sense(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), @@ -5525,7 +7538,6 @@ scsi_request_sense(struct ccb_scsiio *csio, u_int32_t retries, scsi_cmd->opcode = REQUEST_SENSE; scsi_cmd->length = dxfer_len; } -#endif /* __rtems__ */ void scsi_inquiry(struct ccb_scsiio *csio, u_int32_t retries, @@ -5557,7 +7569,6 @@ scsi_inquiry(struct ccb_scsiio *csio, u_int32_t retries, scsi_ulto2b(inq_len, scsi_cmd->length); } -#ifndef __rtems__ void scsi_mode_sense(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), @@ -5790,7 +7801,6 @@ scsi_prevent(struct ccb_scsiio *csio, u_int32_t retries, scsi_cmd->opcode = PREVENT_ALLOW; scsi_cmd->how = action; } -#endif /* __rtems__ */ /* XXX allow specification of address and PMI bit and LBA */ void @@ -5823,8 +7833,8 @@ void scsi_read_capacity_16(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, uint64_t lba, int reladr, int pmi, - struct scsi_read_capacity_data_long *rcap_buf, - uint8_t sense_len, uint32_t timeout) + uint8_t *rcap_buf, int rcap_buf_len, uint8_t sense_len, + uint32_t timeout) { struct scsi_read_capacity_16 *scsi_cmd; @@ -5835,7 +7845,7 @@ scsi_read_capacity_16(struct ccb_scsiio *csio, uint32_t retries, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/(u_int8_t *)rcap_buf, - /*dxfer_len*/sizeof(*rcap_buf), + /*dxfer_len*/rcap_buf_len, sense_len, sizeof(*scsi_cmd), timeout); @@ -5844,7 +7854,7 @@ scsi_read_capacity_16(struct ccb_scsiio *csio, uint32_t retries, scsi_cmd->opcode = SERVICE_ACTION_IN; scsi_cmd->service_action = SRC16_SERVICE_ACTION; scsi_u64to8b(lba, scsi_cmd->addr); - scsi_ulto4b(sizeof(*rcap_buf), scsi_cmd->alloc_len); + scsi_ulto4b(rcap_buf_len, scsi_cmd->alloc_len); if (pmi) reladr |= SRC16_PMI; if (reladr) @@ -6152,23 +8162,30 @@ scsi_ata_identify(struct ccb_scsiio *csio, u_int32_t retries, u_int16_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { - scsi_ata_pass_16(csio, - retries, - cbfcnp, - /*flags*/CAM_DIR_IN, - tag_action, - /*protocol*/AP_PROTO_PIO_IN, - /*ata_flags*/AP_FLAG_TDIR_FROM_DEV| - AP_FLAG_BYT_BLOK_BYTES|AP_FLAG_TLEN_SECT_CNT, - /*features*/0, - /*sector_count*/dxfer_len, - /*lba*/0, - /*command*/ATA_ATA_IDENTIFY, - /*control*/0, - data_ptr, - dxfer_len, - sense_len, - timeout); + scsi_ata_pass(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_IN, + tag_action, + /*protocol*/AP_PROTO_PIO_IN, + /*ata_flags*/AP_FLAG_TDIR_FROM_DEV | + AP_FLAG_BYT_BLOK_BYTES | + AP_FLAG_TLEN_SECT_CNT, + /*features*/0, + /*sector_count*/dxfer_len, + /*lba*/0, + /*command*/ATA_ATA_IDENTIFY, + /*device*/ 0, + /*icc*/ 0, + /*auxiliary*/ 0, + /*control*/0, + data_ptr, + dxfer_len, + /*cdb_storage*/ NULL, + /*cdb_storage_len*/ 0, + /*minimum_cmd_size*/ 0, + sense_len, + timeout); } void @@ -6196,6 +8213,248 @@ scsi_ata_trim(struct ccb_scsiio *csio, u_int32_t retries, timeout); } +int +scsi_ata_read_log(struct ccb_scsiio *csio, uint32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, uint32_t log_address, + uint32_t page_number, uint16_t block_count, + uint8_t protocol, uint8_t *data_ptr, uint32_t dxfer_len, + uint8_t sense_len, uint32_t timeout) +{ + uint8_t command, protocol_out; + uint16_t count_out; + uint64_t lba; + int retval; + + retval = 0; + + switch (protocol) { + case AP_PROTO_DMA: + count_out = block_count; + command = ATA_READ_LOG_DMA_EXT; + protocol_out = AP_PROTO_DMA; + break; + case AP_PROTO_PIO_IN: + default: + count_out = block_count; + command = ATA_READ_LOG_EXT; + protocol_out = AP_PROTO_PIO_IN; + break; + } + + lba = (((uint64_t)page_number & 0xff00) << 32) | + ((page_number & 0x00ff) << 8) | + (log_address & 0xff); + + protocol_out |= AP_EXTEND; + + retval = scsi_ata_pass(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_IN, + tag_action, + /*protocol*/ protocol_out, + /*ata_flags*/AP_FLAG_TLEN_SECT_CNT | + AP_FLAG_BYT_BLOK_BLOCKS | + AP_FLAG_TDIR_FROM_DEV, + /*feature*/ 0, + /*sector_count*/ count_out, + /*lba*/ lba, + /*command*/ command, + /*device*/ 0, + /*icc*/ 0, + /*auxiliary*/ 0, + /*control*/0, + data_ptr, + dxfer_len, + /*cdb_storage*/ NULL, + /*cdb_storage_len*/ 0, + /*minimum_cmd_size*/ 0, + sense_len, + timeout); + + return (retval); +} + +/* + * Note! This is an unusual CDB building function because it can return + * an error in the event that the command in question requires a variable + * length CDB, but the caller has not given storage space for one or has not + * given enough storage space. If there is enough space available in the + * standard SCSI CCB CDB bytes, we'll prefer that over passed in storage. + */ +int +scsi_ata_pass(struct ccb_scsiio *csio, uint32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint32_t flags, uint8_t tag_action, + uint8_t protocol, uint8_t ata_flags, uint16_t features, + uint16_t sector_count, uint64_t lba, uint8_t command, + uint8_t device, uint8_t icc, uint32_t auxiliary, + uint8_t control, u_int8_t *data_ptr, uint32_t dxfer_len, + uint8_t *cdb_storage, size_t cdb_storage_len, + int minimum_cmd_size, u_int8_t sense_len, u_int32_t timeout) +{ + uint32_t cam_flags; + uint8_t *cdb_ptr; + int cmd_size; + int retval; + uint8_t cdb_len; + + retval = 0; + cam_flags = flags; + + /* + * Round the user's request to the nearest command size that is at + * least as big as what he requested. + */ + if (minimum_cmd_size <= 12) + cmd_size = 12; + else if (minimum_cmd_size > 16) + cmd_size = 32; + else + cmd_size = 16; + + /* + * If we have parameters that require a 48-bit ATA command, we have to + * use the 16 byte ATA PASS-THROUGH command at least. + */ + if (((lba > ATA_MAX_28BIT_LBA) + || (sector_count > 255) + || (features > 255) + || (protocol & AP_EXTEND)) + && ((cmd_size < 16) + || ((protocol & AP_EXTEND) == 0))) { + if (cmd_size < 16) + cmd_size = 16; + protocol |= AP_EXTEND; + } + + /* + * The icc and auxiliary ATA registers are only supported in the + * 32-byte version of the ATA PASS-THROUGH command. + */ + if ((icc != 0) + || (auxiliary != 0)) { + cmd_size = 32; + protocol |= AP_EXTEND; + } + + + if ((cmd_size > sizeof(csio->cdb_io.cdb_bytes)) + && ((cdb_storage == NULL) + || (cdb_storage_len < cmd_size))) { + retval = 1; + goto bailout; + } + + /* + * At this point we know we have enough space to store the command + * in one place or another. We prefer the built-in array, but used + * the passed in storage if necessary. + */ + if (cmd_size <= sizeof(csio->cdb_io.cdb_bytes)) + cdb_ptr = csio->cdb_io.cdb_bytes; + else { + cdb_ptr = cdb_storage; + cam_flags |= CAM_CDB_POINTER; + } + + if (cmd_size <= 12) { + struct ata_pass_12 *cdb; + + cdb = (struct ata_pass_12 *)cdb_ptr; + cdb_len = sizeof(*cdb); + bzero(cdb, cdb_len); + + cdb->opcode = ATA_PASS_12; + cdb->protocol = protocol; + cdb->flags = ata_flags; + cdb->features = features; + cdb->sector_count = sector_count; + cdb->lba_low = lba & 0xff; + cdb->lba_mid = (lba >> 8) & 0xff; + cdb->lba_high = (lba >> 16) & 0xff; + cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA; + cdb->command = command; + cdb->control = control; + } else if (cmd_size <= 16) { + struct ata_pass_16 *cdb; + + cdb = (struct ata_pass_16 *)cdb_ptr; + cdb_len = sizeof(*cdb); + bzero(cdb, cdb_len); + + cdb->opcode = ATA_PASS_16; + cdb->protocol = protocol; + cdb->flags = ata_flags; + cdb->features = features & 0xff; + cdb->sector_count = sector_count & 0xff; + cdb->lba_low = lba & 0xff; + cdb->lba_mid = (lba >> 8) & 0xff; + cdb->lba_high = (lba >> 16) & 0xff; + /* + * If AP_EXTEND is set, we're sending a 48-bit command. + * Otherwise it's a 28-bit command. + */ + if (protocol & AP_EXTEND) { + cdb->lba_low_ext = (lba >> 24) & 0xff; + cdb->lba_mid_ext = (lba >> 32) & 0xff; + cdb->lba_high_ext = (lba >> 40) & 0xff; + cdb->features_ext = (features >> 8) & 0xff; + cdb->sector_count_ext = (sector_count >> 8) & 0xff; + cdb->device = device | ATA_DEV_LBA; + } else { + cdb->lba_low_ext = (lba >> 24) & 0xf; + cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA; + } + cdb->command = command; + cdb->control = control; + } else { + struct ata_pass_32 *cdb; + uint8_t tmp_lba[8]; + + cdb = (struct ata_pass_32 *)cdb_ptr; + cdb_len = sizeof(*cdb); + bzero(cdb, cdb_len); + cdb->opcode = VARIABLE_LEN_CDB; + cdb->control = control; + cdb->length = sizeof(*cdb) - __offsetof(struct ata_pass_32, + service_action); + scsi_ulto2b(ATA_PASS_32_SA, cdb->service_action); + cdb->protocol = protocol; + cdb->flags = ata_flags; + + if ((protocol & AP_EXTEND) == 0) { + lba &= 0x0fffffff; + cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA; + features &= 0xff; + sector_count &= 0xff; + } else { + cdb->device = device | ATA_DEV_LBA; + } + scsi_u64to8b(lba, tmp_lba); + bcopy(&tmp_lba[2], cdb->lba, sizeof(cdb->lba)); + scsi_ulto2b(features, cdb->features); + scsi_ulto2b(sector_count, cdb->count); + cdb->command = command; + cdb->icc = icc; + scsi_ulto4b(auxiliary, cdb->auxiliary); + } + + cam_fill_csio(csio, + retries, + cbfcnp, + cam_flags, + tag_action, + data_ptr, + dxfer_len, + sense_len, + cmd_size, + timeout); +bailout: + return (retval); +} + void scsi_ata_pass_16(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), @@ -6434,6 +8693,229 @@ scsi_start_stop(struct ccb_scsiio *csio, u_int32_t retries, timeout); } +void +scsi_read_attribute(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + u_int8_t tag_action, u_int8_t service_action, + uint32_t element, u_int8_t elem_type, int logical_volume, + int partition, u_int32_t first_attribute, int cache, + u_int8_t *data_ptr, u_int32_t length, int sense_len, + u_int32_t timeout) +{ + struct scsi_read_attribute *scsi_cmd; + + scsi_cmd = (struct scsi_read_attribute *)&csio->cdb_io.cdb_bytes; + bzero(scsi_cmd, sizeof(*scsi_cmd)); + + scsi_cmd->opcode = READ_ATTRIBUTE; + scsi_cmd->service_action = service_action; + scsi_ulto2b(element, scsi_cmd->element); + scsi_cmd->elem_type = elem_type; + scsi_cmd->logical_volume = logical_volume; + scsi_cmd->partition = partition; + scsi_ulto2b(first_attribute, scsi_cmd->first_attribute); + scsi_ulto4b(length, scsi_cmd->length); + if (cache != 0) + scsi_cmd->cache |= SRA_CACHE; + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_IN, + tag_action, + /*data_ptr*/data_ptr, + /*dxfer_len*/length, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + +void +scsi_write_attribute(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + u_int8_t tag_action, uint32_t element, int logical_volume, + int partition, int wtc, u_int8_t *data_ptr, + u_int32_t length, int sense_len, u_int32_t timeout) +{ + struct scsi_write_attribute *scsi_cmd; + + scsi_cmd = (struct scsi_write_attribute *)&csio->cdb_io.cdb_bytes; + bzero(scsi_cmd, sizeof(*scsi_cmd)); + + scsi_cmd->opcode = WRITE_ATTRIBUTE; + if (wtc != 0) + scsi_cmd->byte2 = SWA_WTC; + scsi_ulto3b(element, scsi_cmd->element); + scsi_cmd->logical_volume = logical_volume; + scsi_cmd->partition = partition; + scsi_ulto4b(length, scsi_cmd->length); + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_OUT, + tag_action, + /*data_ptr*/data_ptr, + /*dxfer_len*/length, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + +void +scsi_persistent_reserve_in(struct ccb_scsiio *csio, uint32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, int service_action, + uint8_t *data_ptr, uint32_t dxfer_len, int sense_len, + int timeout) +{ + struct scsi_per_res_in *scsi_cmd; + + scsi_cmd = (struct scsi_per_res_in *)&csio->cdb_io.cdb_bytes; + bzero(scsi_cmd, sizeof(*scsi_cmd)); + + scsi_cmd->opcode = PERSISTENT_RES_IN; + scsi_cmd->action = service_action; + scsi_ulto2b(dxfer_len, scsi_cmd->length); + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_IN, + tag_action, + data_ptr, + dxfer_len, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + +void +scsi_persistent_reserve_out(struct ccb_scsiio *csio, uint32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, int service_action, + int scope, int res_type, uint8_t *data_ptr, + uint32_t dxfer_len, int sense_len, int timeout) +{ + struct scsi_per_res_out *scsi_cmd; + + scsi_cmd = (struct scsi_per_res_out *)&csio->cdb_io.cdb_bytes; + bzero(scsi_cmd, sizeof(*scsi_cmd)); + + scsi_cmd->opcode = PERSISTENT_RES_OUT; + scsi_cmd->action = service_action; + scsi_cmd->scope_type = scope | res_type; + scsi_ulto4b(dxfer_len, scsi_cmd->length); + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_OUT, + tag_action, + /*data_ptr*/data_ptr, + /*dxfer_len*/dxfer_len, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + +void +scsi_security_protocol_in(struct ccb_scsiio *csio, uint32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, uint32_t security_protocol, + uint32_t security_protocol_specific, int byte4, + uint8_t *data_ptr, uint32_t dxfer_len, int sense_len, + int timeout) +{ + struct scsi_security_protocol_in *scsi_cmd; + + scsi_cmd = (struct scsi_security_protocol_in *)&csio->cdb_io.cdb_bytes; + bzero(scsi_cmd, sizeof(*scsi_cmd)); + + scsi_cmd->opcode = SECURITY_PROTOCOL_IN; + + scsi_cmd->security_protocol = security_protocol; + scsi_ulto2b(security_protocol_specific, + scsi_cmd->security_protocol_specific); + scsi_cmd->byte4 = byte4; + scsi_ulto4b(dxfer_len, scsi_cmd->length); + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_IN, + tag_action, + data_ptr, + dxfer_len, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + +void +scsi_security_protocol_out(struct ccb_scsiio *csio, uint32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, uint32_t security_protocol, + uint32_t security_protocol_specific, int byte4, + uint8_t *data_ptr, uint32_t dxfer_len, int sense_len, + int timeout) +{ + struct scsi_security_protocol_out *scsi_cmd; + + scsi_cmd = (struct scsi_security_protocol_out *)&csio->cdb_io.cdb_bytes; + bzero(scsi_cmd, sizeof(*scsi_cmd)); + + scsi_cmd->opcode = SECURITY_PROTOCOL_OUT; + + scsi_cmd->security_protocol = security_protocol; + scsi_ulto2b(security_protocol_specific, + scsi_cmd->security_protocol_specific); + scsi_cmd->byte4 = byte4; + scsi_ulto4b(dxfer_len, scsi_cmd->length); + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_OUT, + tag_action, + data_ptr, + dxfer_len, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + +void +scsi_report_supported_opcodes(struct ccb_scsiio *csio, uint32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, int options, int req_opcode, + int req_service_action, uint8_t *data_ptr, + uint32_t dxfer_len, int sense_len, int timeout) +{ + struct scsi_report_supported_opcodes *scsi_cmd; + + scsi_cmd = (struct scsi_report_supported_opcodes *) + &csio->cdb_io.cdb_bytes; + bzero(scsi_cmd, sizeof(*scsi_cmd)); + + scsi_cmd->opcode = MAINTENANCE_IN; + scsi_cmd->service_action = REPORT_SUPPORTED_OPERATION_CODES; + scsi_cmd->options = options; + scsi_cmd->requested_opcode = req_opcode; + scsi_ulto2b(req_service_action, scsi_cmd->requested_service_action); + scsi_ulto4b(dxfer_len, scsi_cmd->length); + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/CAM_DIR_IN, + tag_action, + data_ptr, + dxfer_len, + sense_len, + sizeof(*scsi_cmd), + timeout); +} /* * Try make as good a match as possible with @@ -6500,7 +8982,7 @@ scsi_static_inquiry_match(caddr_t inqbuffer, caddr_t table_entry) * \return 0 on a match, -1 otherwise. * * Treat rhs and lhs as arrays of vpd device id descriptors. Walk lhs matching - * agains each element in rhs until all data are exhausted or we have found + * against each element in rhs until all data are exhausted or we have found * a match. */ int |