diff --git a/smartmontools/ChangeLog b/smartmontools/ChangeLog
index 975defb7d8997720bcf0227083c344b856cc8e8f..de3ddfe5d78487f63951ffcaba30a92f4c7bf2f7 100644
--- a/smartmontools/ChangeLog
+++ b/smartmontools/ChangeLog
@@ -1,5 +1,10 @@
 $Id$
 
+2022-05-28  Douglas Gilbert  <dgilbert@interlog.com>
+
+	[SCSI]: more work for calling REPORT SUPPORTED OPERATION
+	CODES [RSOC] command.
+
 2022-05-27  Douglas Gilbert  <dgilbert@interlog.com>
 
 	[SCSI]: prepare for calling REPORT SUPPORTED OPERATION
diff --git a/smartmontools/dev_interface.h b/smartmontools/dev_interface.h
index 41129918e2103690af802f3b057c999afadaadde..6657f0f175617e14501da92e39a750a1fac8a85d 100644
--- a/smartmontools/dev_interface.h
+++ b/smartmontools/dev_interface.h
@@ -575,6 +575,12 @@ protected:
 
 struct scsi_cmnd_io;
 
+struct scsi_rsoc_elem {
+    uint8_t cdb0;
+    uint8_t sa_valid;
+    uint16_t sa;
+};
+
 /// SCSI device access
 class scsi_device
 : virtual public /*extends*/ smart_device
@@ -596,6 +602,10 @@ public:
   bool use_rcap16() const
     { return rcap16_first; }
 
+  void set_spc4_or_higher() { spc4_or_above = true; }
+
+  bool is_spc4_or_higher() const { return spc4_or_above; }
+
 protected:
   /// Hide/unhide SCSI interface.
   void hide_scsi(bool hide = true)
@@ -604,11 +614,15 @@ protected:
   /// Default constructor, registers device as SCSI.
   scsi_device()
     : smart_device(never_called),
-      rcap16_first(false)
+      rcap16_first(false),
+      spc4_or_above(false)
     { hide_scsi(false); }
 
 private:
   bool rcap16_first;
+  bool spc4_or_above;
+  /* rsoc: report supported operation codes (command) */
+  std::vector<scsi_rsoc_elem> rsoc_list;
 };
 
 
diff --git a/smartmontools/os_freebsd.cpp b/smartmontools/os_freebsd.cpp
index 5d0c8b68a7edf9d738530e6208580c90850203d2..9e07ba4717ae52102ce579e379f6a2b7c5e49bab 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], false, 0);
+        np = scsi_get_opcode_name(ucp);
         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], false, 0);
+    np = scsi_get_opcode_name(ucp);
     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 b750521fb58fc012a7497d43fda29e8bb3e4650d..b0b95e51d09983c66f889cd1dfa72354cf13e1fc 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], false, 0);
+        np = scsi_get_opcode_name(ucp);
         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], false, 0);
+    np = scsi_get_opcode_name(ucp);
     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], false, 0);
+        np = scsi_get_opcode_name(ucp);
         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 66c3a4795bbeed8f7e6c475242af9571f7ff842f..612a384be657d96125526ffb53490ec55d789981 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], false, 0);
+    np = scsi_get_opcode_name(ucp);
     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 d2ffe9c33a4dfaa9edb8453a14b6cff2c053e510..df010cc5a3c8640e6a493c8d31fe91c613f7d8f9 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], false, 0);
+    np = scsi_get_opcode_name(ucp);
     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 00c9645575f51a28eb7a41d61597bd545ecc4079..f8b4b022c0b110d1fc5e22ff151d720a27c20e8d 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], false, 0);
+    np = scsi_get_opcode_name(ucp);
     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 0af305b1cfe5ee7971942c0149ee9df87e2efd6e..58905280ba31a0f80cbd52b2e3988f513562d8f2 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], false, 0);
+    np = scsi_get_opcode_name(ucp);
     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], false, 0);
+    np = scsi_get_opcode_name(ucp);
     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], false, 0);
+    np = scsi_get_opcode_name(ucp);
     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 c094a90aa2d865ae6b51131a357978b18fcae8fb..58a3e86527befc617f16b72375428b786eb010fe 100644
--- a/smartmontools/scsicmds.cpp
+++ b/smartmontools/scsicmds.cpp
@@ -197,6 +197,36 @@ is_scsi_cdb(const uint8_t * cdbp, int clen)
     return false;
 }
 
+enum scsi_sa_t {
+    scsi_sa_none = 0,
+    scsi_sa_b1b4n5,     /* for cdb byte 1, bit 4, number 5 bits */
+    scsi_sa_b8b7n16,
+};
+
+struct scsi_sa_var_map {
+    uint8_t cdb0;
+    enum scsi_sa_t sa_var;
+};
+
+static struct scsi_sa_var_map sa_var_a[] = {
+    {0x3b, scsi_sa_b1b4n5},     /* Write buffer modes_s */
+    {0x3c, scsi_sa_b1b4n5},     /* Read buffer(10) modes_s */
+    {0x48, scsi_sa_b1b4n5},     /* Sanitize sa_s */
+    {0x5e, scsi_sa_b1b4n5},     /* Persistent reserve in sa_s */
+    {0x5f, scsi_sa_b1b4n5},     /* Persistent reserve out sa_s */
+    {0x7f, scsi_sa_b8b7n16},    /* Variable length commands */
+    {0x83, scsi_sa_b1b4n5},     /* Extended copy out/cmd sa_s */
+    {0x84, scsi_sa_b1b4n5},     /* Extended copy in sa_s */
+    {0x8c, scsi_sa_b1b4n5},     /* Read attribute sa_s */
+    {0x9b, scsi_sa_b1b4n5},     /* Read buffer(16) modes_s */
+    {0x9e, scsi_sa_b1b4n5},     /* Service action in (16) */
+    {0x9f, scsi_sa_b1b4n5},     /* Service action out (16) */
+    {0xa3, scsi_sa_b1b4n5},     /* Maintenance in */
+    {0xa4, scsi_sa_b1b4n5},     /* Maintenance out */
+    {0xa9, scsi_sa_b1b4n5},     /* Service action out (12) */
+    {0xab, scsi_sa_b1b4n5},     /* Service action in (12) */
+};
+
 struct scsi_opcode_name {
     uint8_t opcode;
     bool sa_valid;
@@ -238,14 +268,43 @@ 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, bool sa_valid, uint16_t sa)
+scsi_get_opcode_name(const uint8_t * cdbp)
 {
+    uint8_t opcode = cdbp[0];
+    uint8_t cdb0;
+    enum scsi_sa_t sa_var = scsi_sa_none;
+    bool sa_valid = false;
+    uint16_t sa = 0;
+    int k;
+    static const int sa_var_len = sizeof(sa_var_a) /
+                                  sizeof(sa_var_a[0]);
     static const int len = sizeof(opcode_name_arr) /
                            sizeof(opcode_name_arr[0]);
 
     if (opcode >= 0xc0)
         return vendor_specific;
-    for (int k = 0; k < len; ++k) {
+    for (k = 0; k < sa_var_len; ++k) {
+        cdb0 = sa_var_a[k].cdb0;
+        if (opcode == cdb0) {
+            sa_var = sa_var_a[k].sa_var;
+            break;
+        }
+        if (opcode < cdb0)
+            break;
+    }
+    switch (sa_var) {
+    case scsi_sa_none:
+        break;
+    case scsi_sa_b1b4n5:
+        sa_valid = true;
+        sa = cdbp[1] & 0x1f;
+        break;
+    case scsi_sa_b8b7n16:
+        sa_valid = true;
+        sa = sg_get_unaligned_be16(cdbp + 8);
+        break;
+    }
+    for (k = 0; k < len; ++k) {
         struct scsi_opcode_name * onp = &opcode_name_arr[k];
 
         if (opcode == onp->opcode) {
@@ -255,7 +314,7 @@ scsi_get_opcode_name(uint8_t opcode, bool sa_valid, uint16_t sa)
                 if (sa == onp->sa)
                     return onp->name;
             }
-            /* should see sa_valid and ! onp->sa_valid (or vice versa) */
+            /* should not see sa_valid and ! onp->sa_valid (or vice versa) */
         } else if (opcode < onp->opcode)
             return NULL;
     }
@@ -865,11 +924,14 @@ scsiStdInquiry(scsi_device * device, uint8_t *pBuf, int bufLen)
 {
     struct scsi_sense_disect sinfo;
     struct scsi_cmnd_io io_hdr = {};
+    int res;
     uint8_t cdb[6] = {};
     uint8_t sense[32];
 
     if ((bufLen < 0) || (bufLen > 1023))
         return -EINVAL;
+    if (bufLen >= 36)   /* normal case */
+        memset(pBuf, 0, 36);
     io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
     io_hdr.dxfer_len = bufLen;
     io_hdr.dxferp = pBuf;
@@ -882,8 +944,22 @@ scsiStdInquiry(scsi_device * device, uint8_t *pBuf, int bufLen)
     io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
 
     if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
-      return -device->get_errno();
-    return scsiSimpleSenseFilter(&sinfo);
+        return -device->get_errno();
+    res = scsiSimpleSenseFilter(&sinfo);
+    if ((SIMPLE_NO_ERROR == res) && (! device->is_spc4_or_higher())) {
+        if (((bufLen -  io_hdr.resid) >= 36) &&
+            (pBuf[2] >= 6) &&           /* VERSION field >= SPC-4 */
+            ((pBuf[3] & 0xf) == 2)) {   /* RESPONSE DATA field == 2 */
+            uint8_t pdt = pBuf[0] & 0x1f;
+
+            if ((SCSI_PT_DIRECT_ACCESS == pdt) ||
+               (SCSI_PT_HOST_MANAGED == pdt) ||
+               (SCSI_PT_SEQUENTIAL_ACCESS == pdt) ||
+               (SCSI_PT_MEDIUM_CHANGER == pdt))
+                device->set_spc4_or_higher();
+        }
+    }
+    return res;
 }
 
 /* INQUIRY to fetch Vital Page Data.  Returns 0 if ok, 1 if NOT READY
@@ -1323,6 +1399,39 @@ scsiReadCapacity16(scsi_device * device, uint8_t *pBuf, int bufLen)
     return scsiSimpleSenseFilter(&sinfo);
 }
 
+/* REPORT SUPPORTED OPERATION CODES [RSOC] command. If SIMPLE_NO_ERROR is
+ * returned then the response length is written to rspLen. */
+int
+scsiRSOCcmd(scsi_device * device, uint8_t *pBuf, int bufLen, int & rspLen)
+{
+    struct scsi_cmnd_io io_hdr = {};
+    struct scsi_sense_disect sinfo;
+    int res;
+    uint8_t cdb[12] = {};
+    uint8_t sense[32];
+
+    io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+    io_hdr.dxfer_len = bufLen;
+    io_hdr.dxferp = pBuf;
+    cdb[0] = MAINTENANCE_IN_12;
+    cdb[1] = MI_REP_SUP_OPCODES;
+    /* RCTD=0 (no timeout descriptors); REPORTING_OPTION=0 (all commands) */
+    /* those settings imply response should contain 8 bytes per command */
+    sg_put_unaligned_be32(bufLen, cdb + 6);
+    io_hdr.cmnd = cdb;
+    io_hdr.cmnd_len = sizeof(cdb);
+    io_hdr.sensep = sense;
+    io_hdr.max_sense_len = sizeof(sense);
+    io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+    if (!scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+      return -device->get_errno();
+    res = scsiSimpleSenseFilter(&sinfo);
+    if (SIMPLE_NO_ERROR == res)
+        rspLen = bufLen - io_hdr.resid;
+    return res;
+}
+
 /* Return number of bytes of storage in 'device' or 0 if error. If
  * successful and lb_sizep is not NULL then the logical block size in bytes
  * is written to the location pointed to by lb_sizep. If the 'Logical Blocks
diff --git a/smartmontools/scsicmds.h b/smartmontools/scsicmds.h
index bcb03087326f1d47d4b2276f2eeeecd52249d357..95bad0e0bb3d6b3ac5f0eb45d245fc1ba814ac7c 100644
--- a/smartmontools/scsicmds.h
+++ b/smartmontools/scsicmds.h
@@ -106,6 +106,9 @@
 #define DXFER_FROM_DEVICE 1
 #define DXFER_TO_DEVICE   2
 
+
+/* scsi_rsoc_elem and scsi_device is defined in dev_interface.h */
+
 struct scsi_cmnd_io
 {
     uint8_t * cmnd;     /* [in]: ptr to SCSI command block (cdb) */
@@ -369,8 +372,6 @@ Documentation, see http://www.storage.ibm.com/techsup/hddtech/prodspecs.htm */
 #define SCSI_TIMEOUT_SELF_TEST  (5 * 60 * 60)   /* allow max 5 hours for */
                                             /* extended foreground self test */
 
-
-
 #define LOGPAGEHDRSIZE  4
 
 class scsi_device;
@@ -479,6 +480,8 @@ int scsiReadCapacity10(scsi_device * device, unsigned int * last_lbp,
 
 int scsiReadCapacity16(scsi_device * device, uint8_t *pBuf, int bufLen);
 
+int scsiRSOCcmd(scsi_device * device, uint8_t *pBuf, int bufLen, int & rspLen);
+
 /* SMART specific commands */
 int scsiCheckIE(scsi_device * device, int hasIELogPage, int hasTempLogPage,
                 uint8_t *asc, uint8_t *ascq, uint8_t *currenttemp,
@@ -523,7 +526,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, bool sa_valid, uint16_t sa);
+const char * scsi_get_opcode_name(const uint8_t * cdbp);
 void scsi_format_id_string(char * out, const uint8_t * in, int n);
 
 void dStrHex(const uint8_t * up, int len, int no_ascii);