diff mbox series

[v2] hw/block/nvme: add device self test command support

Message ID 20210331083306.12461-1-anaidu.gollu@samsung.com
State New
Headers show
Series [v2] hw/block/nvme: add device self test command support | expand

Commit Message

Gollu Appalanaidu March 31, 2021, 8:33 a.m. UTC
This is to add support for Device Self Test Command (DST) and
DST Log Page. Refer NVM Express specification 1.4b section 5.8
("Device Self-test command")

Signed-off-by: Gollu Appalanaidu <anaidu.gollu@samsung.com>
---
changes:

-v2: addressed style fixes in hw/block/nvme.h

 hw/block/nvme.c                               | 118 +++++-
 hw/block/nvme.h                               |  13 +
 hw/block/trace-events                         |   1 +
 include/block/nvme.h                          |  49 +++
 ...add-device-self-test-command-support.patch | 335 ++++++++++++++++++
 5 files changed, 515 insertions(+), 1 deletion(-)
 create mode 100644 outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch

Comments

no-reply@patchew.org March 31, 2021, 8:48 a.m. UTC | #1
Patchew URL: https://patchew.org/QEMU/20210331083306.12461-1-anaidu.gollu@samsung.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Type: series
Message-id: 20210331083306.12461-1-anaidu.gollu@samsung.com
Subject: [PATCH v2] hw/block/nvme: add device self test command support

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
 * [new tag]         patchew/20210331083306.12461-1-anaidu.gollu@samsung.com -> patchew/20210331083306.12461-1-anaidu.gollu@samsung.com
Switched to a new branch 'test'
018f869 hw/block/nvme: add device self test command support

=== OUTPUT BEGIN ===
Use of uninitialized value $acpi_testexpected in string eq at ./scripts/checkpatch.pl line 1529.
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#337: 
new file mode 100644

ERROR: trailing whitespace
#369: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:28:
+ $

ERROR: trailing whitespace
#374: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:33:
+ $

ERROR: trailing whitespace
#418: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:77:
+ $

ERROR: trailing whitespace
#512: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:171:
+ $

ERROR: trailing whitespace
#515: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:174:
+ $

ERROR: trailing whitespace
#522: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:181:
+ $

ERROR: trailing whitespace
#535: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:194:
+ $

ERROR: trailing whitespace
#544: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:203:
+ $

ERROR: trailing whitespace
#566: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:225:
+ $

ERROR: trailing whitespace
#602: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:261:
+ $

ERROR: trailing whitespace
#657: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:316:
+ $

ERROR: trailing whitespace
#665: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:324:
+ $

ERROR: trailing whitespace
#674: FILE: outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch:333:
+-- $

total: 13 errors, 1 warnings, 617 lines checked

Commit 018f869ec2d4 (hw/block/nvme: add device self test command support) has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20210331083306.12461-1-anaidu.gollu@samsung.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com
diff mbox series

Patch

diff --git a/hw/block/nvme.c b/hw/block/nvme.c
index 6842b01ab5..3c2186b170 100644
--- a/hw/block/nvme.c
+++ b/hw/block/nvme.c
@@ -214,6 +214,7 @@  static const uint32_t nvme_cse_acs[256] = {
     [NVME_ADM_CMD_ASYNC_EV_REQ]     = NVME_CMD_EFF_CSUPP,
     [NVME_ADM_CMD_NS_ATTACHMENT]    = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_NIC,
     [NVME_ADM_CMD_FORMAT_NVM]       = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
+    [NVME_ADM_CMD_DST]              = NVME_CMD_EFF_CSUPP,
 };
 
 static const uint32_t nvme_cse_iocs_none[256];
@@ -3980,6 +3981,34 @@  static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len,
     return nvme_c2h(n, ((uint8_t *)&log) + off, trans_len, req);
 }
 
+static uint16_t nvme_dst_info(NvmeCtrl *n,  uint32_t buf_len, uint64_t off,
+                              NvmeRequest *req)
+{
+    NvmeDstLogPage dst_log = {};
+    NvmeDst *dst;
+    NvmeDstEntry *traverser;
+    uint32_t trans_len;
+    uint8_t entry_index = 0;
+    dst = &n->dst;
+
+    if (off >= sizeof(dst_log)) {
+        return NVME_INVALID_FIELD | NVME_DNR;
+    }
+
+    dst_log.current_dsto = dst->current_dsto;
+    dst_log.current_dstc = dst->current_dstc;
+
+    QTAILQ_FOREACH(traverser, &dst->dst_list, entry) {
+        memcpy(&dst_log.dst_result[entry_index],
+            &traverser->dst_entry, sizeof(NvmeSelfTestResult));
+        entry_index++;
+    }
+
+    trans_len = MIN(sizeof(dst_log) - off, buf_len);
+
+    return nvme_c2h(n, ((uint8_t *)&dst_log) + off, trans_len, req);
+}
+
 static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req)
 {
     NvmeCmd *cmd = &req->cmd;
@@ -4027,6 +4056,8 @@  static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req)
         return nvme_changed_nslist(n, rae, len, off, req);
     case NVME_LOG_CMD_EFFECTS:
         return nvme_cmd_effects(n, csi, len, off, req);
+    case NVME_LOG_DEV_SELF_TEST:
+        return nvme_dst_info(n, len, off, req);
     default:
         trace_pci_nvme_err_invalid_log_page(nvme_cid(req), lid);
         return NVME_INVALID_FIELD | NVME_DNR;
@@ -5069,6 +5100,73 @@  static uint16_t nvme_format(NvmeCtrl *n, NvmeRequest *req)
     return req->status;
 }
 
+static void nvme_dst_create_entry(NvmeCtrl *n, uint32_t nsid,
+                                uint8_t stc)
+{
+    NvmeDstEntry *cur_entry;
+    time_t current_ms;
+
+    cur_entry = QTAILQ_LAST(&n->dst.dst_list);
+    QTAILQ_REMOVE(&n->dst.dst_list, cur_entry, entry);
+    memset(cur_entry, 0x0, sizeof(NvmeDstEntry));
+
+    cur_entry->dst_entry.dst_status = stc << 4;
+
+    if ((n->temperature >= n->features.temp_thresh_hi) ||
+        (n->temperature <= n->features.temp_thresh_low)) {
+        cur_entry->dst_entry.dst_status |= NVME_DST_WITH_FAILED_SEG;
+        cur_entry->dst_entry.segment_number = NVME_SMART_CHECK;
+    }
+
+    current_ms = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+    cur_entry->dst_entry.poh = cpu_to_le64((((current_ms -
+        n->starttime_ms) / 1000) / 60) / 60);
+    cur_entry->dst_entry.nsid = nsid;
+
+    QTAILQ_INSERT_HEAD(&n->dst.dst_list, cur_entry, entry);
+}
+
+static uint16_t nvme_dst_processing(NvmeCtrl *n, uint32_t nsid,
+                                    uint8_t stc)
+{
+    /*
+     * n->dst.current_dsto will be always 0x0 or NO DST OPERATION,
+     * since no background device self test operation takes place.
+     */
+    assert(n->dst.current_dsto == NVME_DST_NO_OPERATION);
+
+    if (stc == NVME_ABORT_DSTO) {
+        goto out;
+    }
+    if (stc == NVME_SHORT_DSTO || stc == NVME_EXTENDED_DSTO) {
+        nvme_dst_create_entry(n, nsid, stc);
+    }
+
+out:
+    n->dst.current_dstc = NVME_DST_OPERATION_COMPLETED;
+    return NVME_SUCCESS;
+}
+
+static uint16_t nvme_dst(NvmeCtrl *n, NvmeRequest *req)
+{
+    uint32_t dw10 = le32_to_cpu(req->cmd.cdw10);
+    uint32_t nsid = le32_to_cpu(req->cmd.nsid);
+    uint8_t stc = dw10 & 0xf;
+
+    trace_pci_nvme_dst(nvme_cid(req), nsid, stc);
+
+    if (!nvme_nsid_valid(n, nsid) && nsid != 0) {
+        return NVME_INVALID_NSID | NVME_DNR;
+    }
+
+    if (nsid != NVME_NSID_BROADCAST && nsid != 0 &&
+        !nvme_ns(n, nsid)) {
+        return NVME_INVALID_FIELD | NVME_DNR;
+    }
+
+    return nvme_dst_processing(n, nsid, stc);
+}
+
 static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req)
 {
     trace_pci_nvme_admin_cmd(nvme_cid(req), nvme_sqid(req), req->cmd.opcode,
@@ -5109,6 +5207,8 @@  static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req)
         return nvme_ns_attachment(n, req);
     case NVME_ADM_CMD_FORMAT_NVM:
         return nvme_format(n, req);
+    case NVME_ADM_CMD_DST:
+        return nvme_dst(n, req);
     default:
         assert(false);
     }
@@ -5870,6 +5970,15 @@  static void nvme_init_state(NvmeCtrl *n)
     n->features.temp_thresh_hi = NVME_TEMPERATURE_WARNING;
     n->starttime_ms = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
     n->aer_reqs = g_new0(NvmeRequest *, n->params.aerl + 1);
+
+    QTAILQ_INIT(&n->dst.dst_list);
+
+    while (n->dst.num_entries < NVME_DST_MAX_ENTRIES) {
+        NvmeDstEntry *next_entry = g_malloc0(sizeof(NvmeDstEntry));
+        next_entry->dst_entry.dst_status = NVME_DST_ENTRY_NOT_USED;
+        QTAILQ_INSERT_HEAD(&n->dst.dst_list, next_entry, entry);
+        n->dst.num_entries++;
+    }
 }
 
 static int nvme_attach_namespace(NvmeCtrl *n, NvmeNamespace *ns, Error **errp)
@@ -6085,7 +6194,8 @@  static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev)
 
     id->mdts = n->params.mdts;
     id->ver = cpu_to_le32(NVME_SPEC_VER);
-    id->oacs = cpu_to_le16(NVME_OACS_NS_MGMT | NVME_OACS_FORMAT);
+    id->oacs = cpu_to_le16(NVME_OACS_NS_MGMT | NVME_OACS_FORMAT |
+        NVME_OACS_DST);
     id->cntrltype = 0x1;
 
     /*
@@ -6240,6 +6350,12 @@  static void nvme_exit(PCIDevice *pci_dev)
         host_memory_backend_set_mapped(n->pmr.dev, false);
     }
     msix_uninit_exclusive_bar(pci_dev);
+
+    while (!QTAILQ_EMPTY(&n->dst.dst_list)) {
+        NvmeDstEntry *entry = QTAILQ_FIRST(&n->dst.dst_list);
+        QTAILQ_REMOVE(&n->dst.dst_list, entry, entry);
+        g_free(entry);
+    }
 }
 
 static Property nvme_props[] = {
diff --git a/hw/block/nvme.h b/hw/block/nvme.h
index 5b0031b11d..5abd2fa7ed 100644
--- a/hw/block/nvme.h
+++ b/hw/block/nvme.h
@@ -158,6 +158,18 @@  typedef struct NvmeFeatureVal {
     uint32_t    async_config;
 } NvmeFeatureVal;
 
+typedef struct NvmeDst {
+    uint8_t      current_dsto;
+    uint8_t      current_dstc;
+    uint8_t      num_entries;
+    QTAILQ_HEAD(, NvmeDstEntry)  dst_list;
+} NvmeDst;
+
+typedef struct NvmeDstEntry {
+    NvmeSelfTestResult           dst_entry;
+    QTAILQ_ENTRY(NvmeDstEntry)   entry;
+} NvmeDstEntry;
+
 typedef struct NvmeCtrl {
     PCIDevice    parent_obj;
     MemoryRegion bar0;
@@ -223,6 +235,7 @@  typedef struct NvmeCtrl {
     NvmeCQueue      admin_cq;
     NvmeIdCtrl      id_ctrl;
     NvmeFeatureVal  features;
+    NvmeDst         dst;
 } NvmeCtrl;
 
 static inline NvmeNamespace *nvme_ns(NvmeCtrl *n, uint32_t nsid)
diff --git a/hw/block/trace-events b/hw/block/trace-events
index 22da06986d..f9a596e3a5 100644
--- a/hw/block/trace-events
+++ b/hw/block/trace-events
@@ -133,6 +133,7 @@  pci_nvme_enqueue_event(uint8_t typ, uint8_t info, uint8_t log_page) "type 0x%"PR
 pci_nvme_enqueue_event_noqueue(int queued) "queued %d"
 pci_nvme_enqueue_event_masked(uint8_t typ) "type 0x%"PRIx8""
 pci_nvme_no_outstanding_aers(void) "ignoring event; no outstanding AERs"
+pci_nvme_dst(uint16_t cid, uint32_t nsid, uint8_t stc) "cid %"PRIu16" nsid 0x%"PRIx32" fid 0x%"PRIx8""
 pci_nvme_enqueue_req_completion(uint16_t cid, uint16_t cqid, uint16_t status) "cid %"PRIu16" cqid %"PRIu16" status 0x%"PRIx16""
 pci_nvme_mmio_read(uint64_t addr, unsigned size) "addr 0x%"PRIx64" size %d"
 pci_nvme_mmio_write(uint64_t addr, uint64_t data, unsigned size) "addr 0x%"PRIx64" data 0x%"PRIx64" size %d"
diff --git a/include/block/nvme.h b/include/block/nvme.h
index b0a4e42916..f835b62577 100644
--- a/include/block/nvme.h
+++ b/include/block/nvme.h
@@ -567,6 +567,7 @@  enum NvmeAdminCommands {
     NVME_ADM_CMD_ACTIVATE_FW    = 0x10,
     NVME_ADM_CMD_DOWNLOAD_FW    = 0x11,
     NVME_ADM_CMD_NS_ATTACHMENT  = 0x15,
+    NVME_ADM_CMD_DST            = 0x14,
     NVME_ADM_CMD_FORMAT_NVM     = 0x80,
     NVME_ADM_CMD_SECURITY_SEND  = 0x81,
     NVME_ADM_CMD_SECURITY_RECV  = 0x82,
@@ -849,6 +850,7 @@  enum NvmeStatusCodes {
     NVME_NS_ALREADY_ATTACHED    = 0x0118,
     NVME_NS_NOT_ATTACHED        = 0x011A,
     NVME_NS_CTRL_LIST_INVALID   = 0x011C,
+    NVME_DST_IN_PROGRESS        = 0x011D,
     NVME_CONFLICTING_ATTRS      = 0x0180,
     NVME_INVALID_PROT_INFO      = 0x0181,
     NVME_WRITE_TO_RO            = 0x0182,
@@ -920,6 +922,50 @@  typedef struct QEMU_PACKED NvmeSmartLog {
 } NvmeSmartLog;
 
 #define NVME_SMART_WARN_MAX     6
+
+enum NvmeDstOpStatus {
+    NVME_DST_NO_OPERATION           = 0,
+    NVME_DST_OPERATION_COMPLETED    = 100,
+    NVME_DST_MAX_ENTRIES            = 20,
+};
+
+typedef struct QEMU_PACKED NvmeSelfTestResult {
+    uint8_t     dst_status;
+    uint8_t     segment_number;
+    uint8_t     valid_dinfo;
+    uint8_t     rsvd;
+    uint64_t    poh;
+    uint32_t    nsid;
+    uint64_t    flba;
+    uint8_t     sct;
+    uint8_t     sc;
+    uint8_t     vs[2];
+} NvmeSelfTestResult;
+
+typedef struct QEMU_PACKED NvmeDstLogPage {
+    uint8_t             current_dsto;
+    uint8_t             current_dstc;
+    uint8_t             rsvd[2];
+    NvmeSelfTestResult  dst_result[NVME_DST_MAX_ENTRIES];
+} NvmeDstLogPage;
+
+enum NvmeDstStc {
+    NVME_SHORT_DSTO     = 0x01,
+    NVME_EXTENDED_DSTO  = 0x02,
+    NVME_ABORT_DSTO     = 0x0f,
+};
+
+enum NvmeDstStatusResult {
+    NVME_DST_WITHOUT_ERROR      = 0x0,
+    NVME_DST_ABORTED_BY_DST_CMD = 0x1,
+    NVME_DST_WITH_FAILED_SEG    = 0x7,
+    NVME_DST_ENTRY_NOT_USED     = 0xf,
+};
+
+enum NvmeDstSegmentNumber {
+    NVME_SMART_CHECK    = 0x2,
+};
+
 enum NvmeSmartWarn {
     NVME_SMART_SPARE                  = 1 << 0,
     NVME_SMART_TEMPERATURE            = 1 << 1,
@@ -951,6 +997,7 @@  enum NvmeLogIdentifier {
     NVME_LOG_FW_SLOT_INFO   = 0x03,
     NVME_LOG_CHANGED_NSLIST = 0x04,
     NVME_LOG_CMD_EFFECTS    = 0x05,
+    NVME_LOG_DEV_SELF_TEST  = 0x06,
 };
 
 typedef struct QEMU_PACKED NvmePSD {
@@ -1076,6 +1123,7 @@  enum NvmeIdCtrlOacs {
     NVME_OACS_FORMAT    = 1 << 1,
     NVME_OACS_FW        = 1 << 2,
     NVME_OACS_NS_MGMT   = 1 << 3,
+    NVME_OACS_DST       = 1 << 4,
 };
 
 enum NvmeIdCtrlOncs {
@@ -1445,5 +1493,6 @@  static inline void _nvme_check_size(void)
     QEMU_BUILD_BUG_ON(sizeof(NvmeIdNsDescr) != 4);
     QEMU_BUILD_BUG_ON(sizeof(NvmeZoneDescr) != 64);
     QEMU_BUILD_BUG_ON(sizeof(NvmeDifTuple) != 8);
+    QEMU_BUILD_BUG_ON(sizeof(NvmeDstLogPage) != 564);
 }
 #endif
diff --git a/outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch b/outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch
new file mode 100644
index 0000000000..389b59412e
--- /dev/null
+++ b/outgoing/0001-hw-block-nvme-add-device-self-test-command-support.patch
@@ -0,0 +1,335 @@ 
+From df711c0ff8ead6e8a5afb8821eba476d57e782f5 Mon Sep 17 00:00:00 2001
+From: Gollu Appalanaidu <anaidu.gollu@samsung.com>
+Date: Wed, 9 Dec 2020 01:40:05 +0530
+Subject: [PATCH] hw/block/nvme: add device self test command support
+
+This is to add support for Device Self Test Command (DST) and
+DST Log Page. Refer NVM Express specification 1.4b section 5.8
+("Device Self-test command")
+
+Signed-off-by: Gollu Appalanaidu <anaidu.gollu@samsung.com>
+---
+ hw/block/nvme.c       | 118 +++++++++++++++++++++++++++++++++++++++++-
+ hw/block/nvme.h       |  13 +++++
+ hw/block/trace-events |   1 +
+ include/block/nvme.h  |  49 ++++++++++++++++++
+ 4 files changed, 180 insertions(+), 1 deletion(-)
+
+diff --git a/hw/block/nvme.c b/hw/block/nvme.c
+index 6842b01ab5..3c2186b170 100644
+--- a/hw/block/nvme.c
++++ b/hw/block/nvme.c
+@@ -214,6 +214,7 @@ static const uint32_t nvme_cse_acs[256] = {
+     [NVME_ADM_CMD_ASYNC_EV_REQ]     = NVME_CMD_EFF_CSUPP,
+     [NVME_ADM_CMD_NS_ATTACHMENT]    = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_NIC,
+     [NVME_ADM_CMD_FORMAT_NVM]       = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
++    [NVME_ADM_CMD_DST]              = NVME_CMD_EFF_CSUPP,
+ };
+ 
+ static const uint32_t nvme_cse_iocs_none[256];
+@@ -3980,6 +3981,34 @@ static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len,
+     return nvme_c2h(n, ((uint8_t *)&log) + off, trans_len, req);
+ }
+ 
++static uint16_t nvme_dst_info(NvmeCtrl *n,  uint32_t buf_len, uint64_t off,
++                              NvmeRequest *req)
++{
++    NvmeDstLogPage dst_log = {};
++    NvmeDst *dst;
++    NvmeDstEntry *traverser;
++    uint32_t trans_len;
++    uint8_t entry_index = 0;
++    dst = &n->dst;
++
++    if (off >= sizeof(dst_log)) {
++        return NVME_INVALID_FIELD | NVME_DNR;
++    }
++
++    dst_log.current_dsto = dst->current_dsto;
++    dst_log.current_dstc = dst->current_dstc;
++
++    QTAILQ_FOREACH(traverser, &dst->dst_list, entry) {
++        memcpy(&dst_log.dst_result[entry_index],
++            &traverser->dst_entry, sizeof(NvmeSelfTestResult));
++        entry_index++;
++    }
++
++    trans_len = MIN(sizeof(dst_log) - off, buf_len);
++
++    return nvme_c2h(n, ((uint8_t *)&dst_log) + off, trans_len, req);
++}
++
+ static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req)
+ {
+     NvmeCmd *cmd = &req->cmd;
+@@ -4027,6 +4056,8 @@ static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req)
+         return nvme_changed_nslist(n, rae, len, off, req);
+     case NVME_LOG_CMD_EFFECTS:
+         return nvme_cmd_effects(n, csi, len, off, req);
++    case NVME_LOG_DEV_SELF_TEST:
++        return nvme_dst_info(n, len, off, req);
+     default:
+         trace_pci_nvme_err_invalid_log_page(nvme_cid(req), lid);
+         return NVME_INVALID_FIELD | NVME_DNR;
+@@ -5069,6 +5100,73 @@ static uint16_t nvme_format(NvmeCtrl *n, NvmeRequest *req)
+     return req->status;
+ }
+ 
++static void nvme_dst_create_entry(NvmeCtrl *n, uint32_t nsid,
++                                uint8_t stc)
++{
++    NvmeDstEntry *cur_entry;
++    time_t current_ms;
++
++    cur_entry = QTAILQ_LAST(&n->dst.dst_list);
++    QTAILQ_REMOVE(&n->dst.dst_list, cur_entry, entry);
++    memset(cur_entry, 0x0, sizeof(NvmeDstEntry));
++
++    cur_entry->dst_entry.dst_status = stc << 4;
++
++    if ((n->temperature >= n->features.temp_thresh_hi) ||
++        (n->temperature <= n->features.temp_thresh_low)) {
++        cur_entry->dst_entry.dst_status |= NVME_DST_WITH_FAILED_SEG;
++        cur_entry->dst_entry.segment_number = NVME_SMART_CHECK;
++    }
++
++    current_ms = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
++    cur_entry->dst_entry.poh = cpu_to_le64((((current_ms -
++        n->starttime_ms) / 1000) / 60) / 60);
++    cur_entry->dst_entry.nsid = nsid;
++
++    QTAILQ_INSERT_HEAD(&n->dst.dst_list, cur_entry, entry);
++}
++
++static uint16_t nvme_dst_processing(NvmeCtrl *n, uint32_t nsid,
++                                    uint8_t stc)
++{
++    /*
++     * n->dst.current_dsto will be always 0x0 or NO DST OPERATION,
++     * since no background device self test operation takes place.
++     */
++    assert(n->dst.current_dsto == NVME_DST_NO_OPERATION);
++
++    if (stc == NVME_ABORT_DSTO) {
++        goto out;
++    }
++    if (stc == NVME_SHORT_DSTO || stc == NVME_EXTENDED_DSTO) {
++        nvme_dst_create_entry(n, nsid, stc);
++    }
++
++out:
++    n->dst.current_dstc = NVME_DST_OPERATION_COMPLETED;
++    return NVME_SUCCESS;
++}
++
++static uint16_t nvme_dst(NvmeCtrl *n, NvmeRequest *req)
++{
++    uint32_t dw10 = le32_to_cpu(req->cmd.cdw10);
++    uint32_t nsid = le32_to_cpu(req->cmd.nsid);
++    uint8_t stc = dw10 & 0xf;
++
++    trace_pci_nvme_dst(nvme_cid(req), nsid, stc);
++
++    if (!nvme_nsid_valid(n, nsid) && nsid != 0) {
++        return NVME_INVALID_NSID | NVME_DNR;
++    }
++
++    if (nsid != NVME_NSID_BROADCAST && nsid != 0 &&
++        !nvme_ns(n, nsid)) {
++        return NVME_INVALID_FIELD | NVME_DNR;
++    }
++
++    return nvme_dst_processing(n, nsid, stc);
++}
++
+ static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req)
+ {
+     trace_pci_nvme_admin_cmd(nvme_cid(req), nvme_sqid(req), req->cmd.opcode,
+@@ -5109,6 +5207,8 @@ static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req)
+         return nvme_ns_attachment(n, req);
+     case NVME_ADM_CMD_FORMAT_NVM:
+         return nvme_format(n, req);
++    case NVME_ADM_CMD_DST:
++        return nvme_dst(n, req);
+     default:
+         assert(false);
+     }
+@@ -5870,6 +5970,15 @@ static void nvme_init_state(NvmeCtrl *n)
+     n->features.temp_thresh_hi = NVME_TEMPERATURE_WARNING;
+     n->starttime_ms = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+     n->aer_reqs = g_new0(NvmeRequest *, n->params.aerl + 1);
++
++    QTAILQ_INIT(&n->dst.dst_list);
++
++    while (n->dst.num_entries < NVME_DST_MAX_ENTRIES) {
++        NvmeDstEntry *next_entry = g_malloc0(sizeof(NvmeDstEntry));
++        next_entry->dst_entry.dst_status = NVME_DST_ENTRY_NOT_USED;
++        QTAILQ_INSERT_HEAD(&n->dst.dst_list, next_entry, entry);
++        n->dst.num_entries++;
++    }
+ }
+ 
+ static int nvme_attach_namespace(NvmeCtrl *n, NvmeNamespace *ns, Error **errp)
+@@ -6085,7 +6194,8 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev)
+ 
+     id->mdts = n->params.mdts;
+     id->ver = cpu_to_le32(NVME_SPEC_VER);
+-    id->oacs = cpu_to_le16(NVME_OACS_NS_MGMT | NVME_OACS_FORMAT);
++    id->oacs = cpu_to_le16(NVME_OACS_NS_MGMT | NVME_OACS_FORMAT |
++        NVME_OACS_DST);
+     id->cntrltype = 0x1;
+ 
+     /*
+@@ -6240,6 +6350,12 @@ static void nvme_exit(PCIDevice *pci_dev)
+         host_memory_backend_set_mapped(n->pmr.dev, false);
+     }
+     msix_uninit_exclusive_bar(pci_dev);
++
++    while (!QTAILQ_EMPTY(&n->dst.dst_list)) {
++        NvmeDstEntry *entry = QTAILQ_FIRST(&n->dst.dst_list);
++        QTAILQ_REMOVE(&n->dst.dst_list, entry, entry);
++        g_free(entry);
++    }
+ }
+ 
+ static Property nvme_props[] = {
+diff --git a/hw/block/nvme.h b/hw/block/nvme.h
+index 5b0031b11d..20e020d467 100644
+--- a/hw/block/nvme.h
++++ b/hw/block/nvme.h
+@@ -158,6 +158,18 @@ typedef struct NvmeFeatureVal {
+     uint32_t    async_config;
+ } NvmeFeatureVal;
+ 
++typedef struct NvmeDst {
++    uint8_t      current_dsto;
++    uint8_t      current_dstc;
++    uint8_t      num_entries;
++    QTAILQ_HEAD(dst_list, NvmeDstEntry)  dst_list;
++} NvmeDst;
++
++typedef struct NvmeDstEntry {
++    NvmeSelfTestResult           dst_entry;
++    QTAILQ_ENTRY(NvmeDstEntry)   entry;
++} NvmeDstEntry;
++
+ typedef struct NvmeCtrl {
+     PCIDevice    parent_obj;
+     MemoryRegion bar0;
+@@ -223,6 +235,7 @@ typedef struct NvmeCtrl {
+     NvmeCQueue      admin_cq;
+     NvmeIdCtrl      id_ctrl;
+     NvmeFeatureVal  features;
++    NvmeDst         dst;
+ } NvmeCtrl;
+ 
+ static inline NvmeNamespace *nvme_ns(NvmeCtrl *n, uint32_t nsid)
+diff --git a/hw/block/trace-events b/hw/block/trace-events
+index 22da06986d..f9a596e3a5 100644
+--- a/hw/block/trace-events
++++ b/hw/block/trace-events
+@@ -133,6 +133,7 @@ pci_nvme_enqueue_event(uint8_t typ, uint8_t info, uint8_t log_page) "type 0x%"PR
+ pci_nvme_enqueue_event_noqueue(int queued) "queued %d"
+ pci_nvme_enqueue_event_masked(uint8_t typ) "type 0x%"PRIx8""
+ pci_nvme_no_outstanding_aers(void) "ignoring event; no outstanding AERs"
++pci_nvme_dst(uint16_t cid, uint32_t nsid, uint8_t stc) "cid %"PRIu16" nsid 0x%"PRIx32" fid 0x%"PRIx8""
+ pci_nvme_enqueue_req_completion(uint16_t cid, uint16_t cqid, uint16_t status) "cid %"PRIu16" cqid %"PRIu16" status 0x%"PRIx16""
+ pci_nvme_mmio_read(uint64_t addr, unsigned size) "addr 0x%"PRIx64" size %d"
+ pci_nvme_mmio_write(uint64_t addr, uint64_t data, unsigned size) "addr 0x%"PRIx64" data 0x%"PRIx64" size %d"
+diff --git a/include/block/nvme.h b/include/block/nvme.h
+index b0a4e42916..f835b62577 100644
+--- a/include/block/nvme.h
++++ b/include/block/nvme.h
+@@ -567,6 +567,7 @@ enum NvmeAdminCommands {
+     NVME_ADM_CMD_ACTIVATE_FW    = 0x10,
+     NVME_ADM_CMD_DOWNLOAD_FW    = 0x11,
+     NVME_ADM_CMD_NS_ATTACHMENT  = 0x15,
++    NVME_ADM_CMD_DST            = 0x14,
+     NVME_ADM_CMD_FORMAT_NVM     = 0x80,
+     NVME_ADM_CMD_SECURITY_SEND  = 0x81,
+     NVME_ADM_CMD_SECURITY_RECV  = 0x82,
+@@ -849,6 +850,7 @@ enum NvmeStatusCodes {
+     NVME_NS_ALREADY_ATTACHED    = 0x0118,
+     NVME_NS_NOT_ATTACHED        = 0x011A,
+     NVME_NS_CTRL_LIST_INVALID   = 0x011C,
++    NVME_DST_IN_PROGRESS        = 0x011D,
+     NVME_CONFLICTING_ATTRS      = 0x0180,
+     NVME_INVALID_PROT_INFO      = 0x0181,
+     NVME_WRITE_TO_RO            = 0x0182,
+@@ -920,6 +922,50 @@ typedef struct QEMU_PACKED NvmeSmartLog {
+ } NvmeSmartLog;
+ 
+ #define NVME_SMART_WARN_MAX     6
++
++enum NvmeDstOpStatus {
++    NVME_DST_NO_OPERATION           = 0,
++    NVME_DST_OPERATION_COMPLETED    = 100,
++    NVME_DST_MAX_ENTRIES            = 20,
++};
++
++typedef struct QEMU_PACKED NvmeSelfTestResult {
++    uint8_t     dst_status;
++    uint8_t     segment_number;
++    uint8_t     valid_dinfo;
++    uint8_t     rsvd;
++    uint64_t    poh;
++    uint32_t    nsid;
++    uint64_t    flba;
++    uint8_t     sct;
++    uint8_t     sc;
++    uint8_t     vs[2];
++} NvmeSelfTestResult;
++
++typedef struct QEMU_PACKED NvmeDstLogPage {
++    uint8_t             current_dsto;
++    uint8_t             current_dstc;
++    uint8_t             rsvd[2];
++    NvmeSelfTestResult  dst_result[NVME_DST_MAX_ENTRIES];
++} NvmeDstLogPage;
++
++enum NvmeDstStc {
++    NVME_SHORT_DSTO     = 0x01,
++    NVME_EXTENDED_DSTO  = 0x02,
++    NVME_ABORT_DSTO     = 0x0f,
++};
++
++enum NvmeDstStatusResult {
++    NVME_DST_WITHOUT_ERROR      = 0x0,
++    NVME_DST_ABORTED_BY_DST_CMD = 0x1,
++    NVME_DST_WITH_FAILED_SEG    = 0x7,
++    NVME_DST_ENTRY_NOT_USED     = 0xf,
++};
++
++enum NvmeDstSegmentNumber {
++    NVME_SMART_CHECK    = 0x2,
++};
++
+ enum NvmeSmartWarn {
+     NVME_SMART_SPARE                  = 1 << 0,
+     NVME_SMART_TEMPERATURE            = 1 << 1,
+@@ -951,6 +997,7 @@ enum NvmeLogIdentifier {
+     NVME_LOG_FW_SLOT_INFO   = 0x03,
+     NVME_LOG_CHANGED_NSLIST = 0x04,
+     NVME_LOG_CMD_EFFECTS    = 0x05,
++    NVME_LOG_DEV_SELF_TEST  = 0x06,
+ };
+ 
+ typedef struct QEMU_PACKED NvmePSD {
+@@ -1076,6 +1123,7 @@ enum NvmeIdCtrlOacs {
+     NVME_OACS_FORMAT    = 1 << 1,
+     NVME_OACS_FW        = 1 << 2,
+     NVME_OACS_NS_MGMT   = 1 << 3,
++    NVME_OACS_DST       = 1 << 4,
+ };
+ 
+ enum NvmeIdCtrlOncs {
+@@ -1445,5 +1493,6 @@ static inline void _nvme_check_size(void)
+     QEMU_BUILD_BUG_ON(sizeof(NvmeIdNsDescr) != 4);
+     QEMU_BUILD_BUG_ON(sizeof(NvmeZoneDescr) != 64);
+     QEMU_BUILD_BUG_ON(sizeof(NvmeDifTuple) != 8);
++    QEMU_BUILD_BUG_ON(sizeof(NvmeDstLogPage) != 564);
+ }
+ #endif
+-- 
+2.17.1
+