diff --git a/smartmontools/ChangeLog b/smartmontools/ChangeLog
index f7b4dc37d8d57457857d7d3401797bbe23d94b17..975defb7d8997720bcf0227083c344b856cc8e8f 100644
--- a/smartmontools/ChangeLog
+++ b/smartmontools/ChangeLog
@@ -1,5 +1,14 @@
 $Id$
 
+2022-05-27  Douglas Gilbert  <dgilbert@interlog.com>
+
+	[SCSI]: prepare for calling REPORT SUPPORTED OPERATION
+	CODES [RSOC] command (and several others that use an
+	additional "service action" code to identify them). For
+	SCSI devices >= SPC-4 plan to call the RSOC command and
+	cache its result. Use that cache to determine if
+	commands like GET PHYSICAL ELEMENT STATUS are supported.
+
 2022-05-26  Christian Franke  <franke@computer.org>
 
 	INSTALL: Update ./configure description and Windows info.
diff --git a/smartmontools/os_freebsd.cpp b/smartmontools/os_freebsd.cpp
index bc7ecf8a33295a974daa352bc397940d0fcaab43..5d0c8b68a7edf9d738530e6208580c90850203d2 100644
--- a/smartmontools/os_freebsd.cpp
+++ b/smartmontools/os_freebsd.cpp
@@ -889,7 +889,7 @@ bool freebsd_megaraid_device::scsi_pass_through(scsi_cmnd_io *iop)
         char buff[256];
         const int sz = (int)sizeof(buff);
 
-        np = scsi_get_opcode_name(ucp[0]);
+        np = scsi_get_opcode_name(ucp[0], false, 0);
         j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
         for (k = 0; k < (int)iop->cmnd_len; ++k)
             j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
@@ -1253,7 +1253,7 @@ bool freebsd_scsi_device::scsi_pass_through(scsi_cmnd_io * iop)
     const unsigned char * ucp = iop->cmnd;
     const char * np;
 
-    np = scsi_get_opcode_name(ucp[0]);
+    np = scsi_get_opcode_name(ucp[0], false, 0);
     pout(" [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < iop->cmnd_len; ++k)
       pout("%02x ", ucp[k]);
diff --git a/smartmontools/os_linux.cpp b/smartmontools/os_linux.cpp
index e8b9438cca35624cc5a545a7ca8a7afb04312fcc..b750521fb58fc012a7497d43fda29e8bb3e4650d 100644
--- a/smartmontools/os_linux.cpp
+++ b/smartmontools/os_linux.cpp
@@ -542,7 +542,7 @@ static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
         const int sz = (int)sizeof(buff);
 
         pout(">>>> do_scsi_cmnd_io: sg_io_ifc=%d\n", (int)sg_io_ifc);
-        np = scsi_get_opcode_name(ucp[0]);
+        np = scsi_get_opcode_name(ucp[0], false, 0);
         j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
         for (k = 0; k < (int)iop->cmnd_len; ++k)
             j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
@@ -921,7 +921,7 @@ bool linux_aacraid_device::scsi_pass_through(scsi_cmnd_io *iop)
     char buff[256];
     const int sz = (int)sizeof(buff);
 
-    np = scsi_get_opcode_name(ucp[0]);
+    np = scsi_get_opcode_name(ucp[0], false, 0);
     j  = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < (int)iop->cmnd_len; ++k)
       j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
@@ -1226,7 +1226,7 @@ bool linux_megaraid_device::scsi_pass_through(scsi_cmnd_io *iop)
         char buff[256];
         const int sz = (int)sizeof(buff);
 
-        np = scsi_get_opcode_name(ucp[0]);
+        np = scsi_get_opcode_name(ucp[0], false, 0);
         j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
         for (k = 0; k < (int)iop->cmnd_len; ++k)
             j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
diff --git a/smartmontools/os_netbsd.cpp b/smartmontools/os_netbsd.cpp
index abf2f3242a7eb1f4f8c6bb630d74e55ab6bdede5..66c3a4795bbeed8f7e6c475242af9571f7ff842f 100644
--- a/smartmontools/os_netbsd.cpp
+++ b/smartmontools/os_netbsd.cpp
@@ -357,7 +357,7 @@ bool netbsd_scsi_device::scsi_pass_through(scsi_cmnd_io * iop)
     const unsigned char * ucp = iop->cmnd;
     const char * np;
 
-    np = scsi_get_opcode_name(ucp[0]);
+    np = scsi_get_opcode_name(ucp[0], false, 0);
     pout(" [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < iop->cmnd_len; ++k)
       pout("%02x ", ucp[k]);
diff --git a/smartmontools/os_openbsd.cpp b/smartmontools/os_openbsd.cpp
index 25f66adc69dbb8c7aec6a3497f763dda4b5d5204..d2ffe9c33a4dfaa9edb8453a14b6cff2c053e510 100644
--- a/smartmontools/os_openbsd.cpp
+++ b/smartmontools/os_openbsd.cpp
@@ -244,7 +244,7 @@ bool openbsd_scsi_device::scsi_pass_through(scsi_cmnd_io * iop)
     const unsigned char * ucp = iop->cmnd;
     const char * np;
 
-    np = scsi_get_opcode_name(ucp[0]);
+    np = scsi_get_opcode_name(ucp[0], false, 0);
     pout(" [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < iop->cmnd_len; ++k)
       pout("%02x ", ucp[k]);
diff --git a/smartmontools/os_solaris.cpp b/smartmontools/os_solaris.cpp
index 8aaeda7f98d672e667a36f2dddbced82839cb604..00c9645575f51a28eb7a41d61597bd545ecc4079 100644
--- a/smartmontools/os_solaris.cpp
+++ b/smartmontools/os_solaris.cpp
@@ -240,7 +240,7 @@ int do_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report)
     const unsigned char * ucp = iop->cmnd;
     const char * np;
 
-    np = scsi_get_opcode_name(ucp[0]);
+    np = scsi_get_opcode_name(ucp[0], false, 0);
     pout(" [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < (int)iop->cmnd_len; ++k)
       pout("%02x ", ucp[k]);
diff --git a/smartmontools/os_win32.cpp b/smartmontools/os_win32.cpp
index 8506347c1459947eda3703459c691ea531e784b1..0af305b1cfe5ee7971942c0149ee9df87e2efd6e 100644
--- a/smartmontools/os_win32.cpp
+++ b/smartmontools/os_win32.cpp
@@ -2830,7 +2830,7 @@ bool win_scsi_device::scsi_pass_through(struct scsi_cmnd_io * iop)
     char buff[256];
     const int sz = (int)sizeof(buff);
 
-    np = scsi_get_opcode_name(ucp[0]);
+    np = scsi_get_opcode_name(ucp[0], false, 0);
     j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < (int)iop->cmnd_len; ++k)
       j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
@@ -2959,7 +2959,7 @@ static long scsi_pass_through_direct(HANDLE fd, UCHAR targetid, struct scsi_cmnd
     char buff[256];
     const int sz = (int)sizeof(buff);
 
-    np = scsi_get_opcode_name(ucp[0]);
+    np = scsi_get_opcode_name(ucp[0], false, 0);
     j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < (int)iop->cmnd_len; ++k)
       j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
@@ -3396,7 +3396,7 @@ bool win_aacraid_device::scsi_pass_through(struct scsi_cmnd_io *iop)
     const char * np;
     char buff[256];
     const int sz = (int)sizeof(buff);
-    np = scsi_get_opcode_name(ucp[0]);
+    np = scsi_get_opcode_name(ucp[0], false, 0);
     j  = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < (int)iop->cmnd_len; ++k)
       j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
diff --git a/smartmontools/scsicmds.cpp b/smartmontools/scsicmds.cpp
index b90b913461a780c2598c9e4d39dd22b64197895c..c094a90aa2d865ae6b51131a357978b18fcae8fb 100644
--- a/smartmontools/scsicmds.cpp
+++ b/smartmontools/scsicmds.cpp
@@ -199,30 +199,38 @@ is_scsi_cdb(const uint8_t * cdbp, int clen)
 
 struct scsi_opcode_name {
     uint8_t opcode;
+    bool sa_valid;
+    uint16_t sa;
     const char * name;
 };
 
+/* Array assumed to be sorted by opcode then service action (sa) */
 static struct scsi_opcode_name opcode_name_arr[] = {
     /* in ascending opcode order */
-    {TEST_UNIT_READY, "test unit ready"},       /* 0x00 */
-    {REQUEST_SENSE, "request sense"},           /* 0x03 */
-    {INQUIRY, "inquiry"},                       /* 0x12 */
-    {MODE_SELECT, "mode select(6)"},            /* 0x15 */
-    {MODE_SENSE, "mode sense(6)"},              /* 0x1a */
-    {START_STOP_UNIT, "start stop unit"},       /* 0x1b */
-    {RECEIVE_DIAGNOSTIC, "receive diagnostic"}, /* 0x1c */
-    {SEND_DIAGNOSTIC, "send diagnostic"},       /* 0x1d */
-    {READ_CAPACITY_10, "read capacity(10)"},    /* 0x25 */
-    {READ_DEFECT_10, "read defect list(10)"},   /* 0x37 */
-    {LOG_SELECT, "log select"},                 /* 0x4c */
-    {LOG_SENSE, "log sense"},                   /* 0x4d */
-    {MODE_SELECT_10, "mode select(10)"},        /* 0x55 */
-    {MODE_SENSE_10, "mode sense(10)"},          /* 0x5a */
-    {SAT_ATA_PASSTHROUGH_16, "ata pass-through(16)"}, /* 0x85 */
-    {READ_CAPACITY_16, "read capacity(16)"},    /* 0x9e,0x10 */
-    {REPORT_LUNS, "report luns"},               /* 0xa0 */
-    {SAT_ATA_PASSTHROUGH_12, "ata pass-through(12)"}, /* 0xa1 */
-    {READ_DEFECT_12, "read defect list(12)"},   /* 0xb7 */
+    {TEST_UNIT_READY, false, 0, "test unit ready"},       /* 0x00 */
+    {REQUEST_SENSE, false, 0, "request sense"},           /* 0x03 */
+    {INQUIRY, false, 0, "inquiry"},                       /* 0x12 */
+    {MODE_SELECT_6, false, 0, "mode select(6)"},          /* 0x15 */
+    {MODE_SENSE_6, false, 0, "mode sense(6)"},            /* 0x1a */
+    {START_STOP_UNIT, false, 0, "start stop unit"},       /* 0x1b */
+    {RECEIVE_DIAGNOSTIC, false, 0, "receive diagnostic"}, /* 0x1c */
+    {SEND_DIAGNOSTIC, false, 0, "send diagnostic"},       /* 0x1d */
+    {READ_CAPACITY_10, false, 0, "read capacity(10)"},    /* 0x25 */
+    {READ_DEFECT_10, false, 0, "read defect list(10)"},   /* 0x37 */
+    {LOG_SELECT, false, 0, "log select"},                 /* 0x4c */
+    {LOG_SENSE, false, 0, "log sense"},                   /* 0x4d */
+    {MODE_SELECT_10, false, 0, "mode select(10)"},        /* 0x55 */
+    {MODE_SENSE_10, false, 0, "mode sense(10)"},          /* 0x5a */
+    {SAT_ATA_PASSTHROUGH_16, false, 0, "ata pass-through(16)"}, /* 0x85 */
+    {SERVICE_ACTION_IN_16, true, SAI_READ_CAPACITY_16, "read capacity(16)"},
+                                                          /* 0x9e,0x10 */
+    {SERVICE_ACTION_IN_16, true, SAI_GET_PHY_ELEM_STATUS,
+        "get physical element status"},                   /* 0x9e,0x17 */
+    {REPORT_LUNS, false, 0, "report luns"},               /* 0xa0 */
+    {SAT_ATA_PASSTHROUGH_12, false, 0, "ata pass-through(12)"}, /* 0xa1 */
+    {MAINTENANCE_IN_12, true, MI_REP_SUP_OPCODES,
+        "report suported operation codes"},               /* 0xa3,0xc */
+    {READ_DEFECT_12, false, 0, "read defect list(12)"},   /* 0xb7 */
 };
 
 static const char * vendor_specific = "<vendor specific>";
@@ -230,7 +238,7 @@ static const char * vendor_specific = "<vendor specific>";
 /* Need to expand to take service action into account. For commands
  * of interest the service action is in the 2nd command byte */
 const char *
-scsi_get_opcode_name(uint8_t opcode)
+scsi_get_opcode_name(uint8_t opcode, bool sa_valid, uint16_t sa)
 {
     static const int len = sizeof(opcode_name_arr) /
                            sizeof(opcode_name_arr[0]);
@@ -239,9 +247,16 @@ scsi_get_opcode_name(uint8_t opcode)
         return vendor_specific;
     for (int k = 0; k < len; ++k) {
         struct scsi_opcode_name * onp = &opcode_name_arr[k];
-        if (opcode == onp->opcode)
-            return onp->name;
-        else if (opcode < onp->opcode)
+
+        if (opcode == onp->opcode) {
+            if ((! sa_valid) && (! onp->sa_valid))
+                return onp->name;
+            if (sa_valid && onp->sa_valid) {
+                if (sa == onp->sa)
+                    return onp->name;
+            }
+            /* should see sa_valid and ! onp->sa_valid (or vice versa) */
+        } else if (opcode < onp->opcode)
             return NULL;
     }
     return NULL;
@@ -690,7 +705,7 @@ scsiModeSense(scsi_device * device, int pagenum, int subpagenum, int pc,
     io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
     io_hdr.dxfer_len = bufLen;
     io_hdr.dxferp = pBuf;
-    cdb[0] = MODE_SENSE;
+    cdb[0] = MODE_SENSE_6;
     cdb[2] = (pc << 6) | (pagenum & 0x3f);
     cdb[3] = subpagenum;
     cdb[4] = bufLen;
@@ -743,7 +758,7 @@ scsiModeSelect(scsi_device * device, int sp, uint8_t *pBuf, int bufLen)
     io_hdr.dxfer_dir = DXFER_TO_DEVICE;
     io_hdr.dxfer_len = hdr_plus_1_pg;
     io_hdr.dxferp = pBuf;
-    cdb[0] = MODE_SELECT;
+    cdb[0] = MODE_SELECT_6;
     cdb[1] = 0x10 | (sp & 1);      /* set PF (page format) bit always */
     cdb[4] = hdr_plus_1_pg; /* make sure only one page sent */
     io_hdr.cmnd = cdb;
@@ -1294,7 +1309,7 @@ scsiReadCapacity16(scsi_device * device, uint8_t *pBuf, int bufLen)
     io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
     io_hdr.dxfer_len = bufLen;
     io_hdr.dxferp = pBuf;
-    cdb[0] = READ_CAPACITY_16;
+    cdb[0] = SERVICE_ACTION_IN_16;
     cdb[1] = SAI_READ_CAPACITY_16;
     sg_put_unaligned_be32(bufLen, cdb + 10);
     io_hdr.cmnd = cdb;
diff --git a/smartmontools/scsicmds.h b/smartmontools/scsicmds.h
index a0b73932fccecbb22163d3ef719fca2e9d9f88ae..bcb03087326f1d47d4b2276f2eeeecd52249d357 100644
--- a/smartmontools/scsicmds.h
+++ b/smartmontools/scsicmds.h
@@ -39,14 +39,14 @@
 #ifndef LOG_SENSE
 #define LOG_SENSE 0x4d
 #endif
-#ifndef MODE_SENSE
-#define MODE_SENSE 0x1a
+#ifndef MODE_SENSE_6
+#define MODE_SENSE_6 0x1a
 #endif
 #ifndef MODE_SENSE_10
 #define MODE_SENSE_10 0x5a
 #endif
-#ifndef MODE_SELECT
-#define MODE_SELECT 0x15
+#ifndef MODE_SELECT_6
+#define MODE_SELECT_6 0x15
 #endif
 #ifndef MODE_SELECT_10
 #define MODE_SELECT_10 0x55
@@ -78,12 +78,21 @@
 #ifndef READ_CAPACITY_10
 #define READ_CAPACITY_10  0x25
 #endif
-#ifndef READ_CAPACITY_16
-#define READ_CAPACITY_16  0x9e
+#ifndef SERVICE_ACTION_IN_16
+#define SERVICE_ACTION_IN_16  0x9e
 #endif
-#ifndef SAI_READ_CAPACITY_16    /* service action for READ_CAPACITY_16 */
+#ifndef SAI_READ_CAPACITY_16    /* service action in for READ_CAPACITY_16 */
 #define SAI_READ_CAPACITY_16  0x10
 #endif
+#ifndef SAI_GET_PHY_ELEM_STATUS    /* Get physical element status */
+#define SAI_GET_PHY_ELEM_STATUS  0x17
+#endif
+#ifndef MAINTENANCE_IN_12
+#define MAINTENANCE_IN_12  0xa3
+#endif
+#ifndef MI_REP_SUP_OPCODES
+#define MI_REP_SUP_OPCODES  0xc    /* maintenance in (12) */
+#endif
 
 #ifndef SAT_ATA_PASSTHROUGH_12
 #define SAT_ATA_PASSTHROUGH_12 0xa1
@@ -514,7 +523,7 @@ int scsiSmartSelfTestAbort(scsi_device * device);
 const char * scsiTapeAlertsTapeDevice(unsigned short code);
 const char * scsiTapeAlertsChangerDevice(unsigned short code);
 
-const char * scsi_get_opcode_name(uint8_t opcode);
+const char * scsi_get_opcode_name(uint8_t opcode, bool sa_valid, uint16_t sa);
 void scsi_format_id_string(char * out, const uint8_t * in, int n);
 
 void dStrHex(const uint8_t * up, int len, int no_ascii);