From: Daniel Gomez <[email protected]>

NVMe spec defines Multiple Atomicity Mode (MAM) as a superset of Single
Atomicity Mode (SAM) where the controller works in either mode. In this
mode, a write command that crosses the Namespace Atomic Boundaries is
divided at those boundaries into atomic LBA subranges, each written
atomically, although the full command is not atomic as a whole.

Add an "atomic.mam" nvme-ns boolean that sets the existing
NVME_ID_NS_NSFEAT_MAM bit once the configured geometry satisfies the
mode's requirements: NSABP, with a nonzero NABSN/NABSPF and ==
NAWUN/NAWUPF.

This lets a guest use the atomic-write/MAM path with the following
emulated NVMe option atomic.mam=on, e.g.:

  -device nvme,serial=foo,id=nvme0\
  -drive file=nvme0.qcow2,if=none,format=qcow2,id=nvme0-ns0\
  -device nvme-ns,drive=nvme0-ns0,bus=nvme0,atomic.nawun=7,\
  atomic.nawupf=7,atomic.nabsn=7,atomic.nabspf=7,atomic.mam=on

Note that for MAM emulation, the atomic boundaries limits are skipped and
the whole command is serialized as one atomic write.

Also, fix a typo in the error message reported when nabspf and nawupf
are checked when setting the Namespace Atomic Boundaries.

Signed-off-by: Daniel Gomez <[email protected]>
---
 hw/nvme/ctrl.c |  7 +++++--
 hw/nvme/ns.c   | 27 ++++++++++++++++++++++++++-
 hw/nvme/nvme.h |  1 +
 3 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c
index 3baf57af634..c912a4df8da 100644
--- a/hw/nvme/ctrl.c
+++ b/hw/nvme/ctrl.c
@@ -56,6 +56,7 @@
  *              zoned.max_active=<N[optional]>, \
  *              zoned.max_open=<N[optional]>, \
  *              zoned.cross_read=<true|false[optional]>, \
+ *              atomic.mam=<on|off[optional]>, \
  *              atomic.nabo=<N[optional]>, \
  *              atomic.nabsn=<N[optional]>, \
  *              atomic.nabspf=<N[optional]>, \
@@ -7830,6 +7831,8 @@ static int nvme_atomic_write_check(NvmeCtrl *n, NvmeCmd 
*cmd,
     NvmeAtomic *atomic)
 {
     NvmeRwCmd *rw = (NvmeRwCmd *)cmd;
+    NvmeNamespace *ns = nvme_ns(n, le32_to_cpu(cmd->nsid));
+    bool mam = ns && (ns->id_ns.nsfeat & NVME_ID_NS_NSFEAT_MAM);
     uint64_t slba = le64_to_cpu(rw->slba);
     uint32_t nlb = (uint32_t)le16_to_cpu(rw->nlb);
     uint64_t elba = slba + nlb;
@@ -7837,14 +7840,14 @@ static int nvme_atomic_write_check(NvmeCtrl *n, NvmeCmd 
*cmd,
     int i;
 
     if ((cmd->opcode == NVME_CMD_READ) || ((cmd->opcode == NVME_CMD_WRITE) &&
-        ((nlb + 1) > atomic->atomic_max_write_size))) {
+        !mam && ((nlb + 1) > atomic->atomic_max_write_size))) {
         cmd_atomic_wr = false;
     }
 
     /*
      * Check if a write crosses an atomic boundary.
      */
-    if (cmd->opcode == NVME_CMD_WRITE) {
+    if (cmd->opcode == NVME_CMD_WRITE && !mam) {
         if (!nvme_atomic_boundary_check(n, cmd, atomic)) {
             cmd_atomic_wr = false;
         }
diff --git a/hw/nvme/ns.c b/hw/nvme/ns.c
index 7f0f9ac7662..344c16d1aa0 100644
--- a/hw/nvme/ns.c
+++ b/hw/nvme/ns.c
@@ -769,7 +769,7 @@ static bool nvme_ns_set_nab(NvmeCtrl *n, NvmeNamespace *ns, 
Error **errp)
         }
 
         if (nabspf && nabspf < le16_to_cpu(id_ns->nawupf)) {
-            error_setg(errp, "nabspf must be great than or equal to nawupf");
+            error_setg(errp, "nabspf must be greater than or equal to nawupf");
             return false;
         }
     }
@@ -787,6 +787,30 @@ static bool nvme_ns_set_nab(NvmeCtrl *n, NvmeNamespace 
*ns, Error **errp)
 
     nvme_ns_atomic_configure_boundary(n->dn, nabsn, nabspf, &ns->atomic);
 
+    if (ns->params.atomic.mam) {
+        uint16_t nawupf = le16_to_cpu(id_ns->nawupf);
+        uint16_t nawun = le16_to_cpu(id_ns->nawun);
+
+        if (!(id_ns->nsfeat & NVME_ID_NS_NSFEAT_NSABP)) {
+            error_setg(errp, "atomic.mam requires per-namespace atomic 
writes");
+            return false;
+        }
+
+        if (!nabsn || !nabspf) {
+            error_setg(errp, "atomic.mam requires a nonzero atomic.nabsn and "
+                             "atomic.nabspf");
+            return false;
+        }
+
+        if (nabsn != nawun || nabspf != nawupf || nawun != nawupf) {
+            error_setg(errp, "atomic.mam requires atomic.nabsn == atomic.nawun 
"
+                             "== atomic.nabspf == atomic.nawupf");
+            return false;
+        }
+
+        id_ns->nsfeat |= NVME_ID_NS_NSFEAT_MAM;
+    }
+
     return true;
 }
 
@@ -1096,6 +1120,7 @@ static const Property nvme_ns_props[] = {
     DEFINE_PROP_UINT16("atomic.nabsn", NvmeNamespace, params.atomic.nabsn, 0),
     DEFINE_PROP_UINT16("atomic.nabspf", NvmeNamespace, params.atomic.nabspf, 
0),
     DEFINE_PROP_UINT16("atomic.nabo", NvmeNamespace, params.atomic.nabo, 0),
+    DEFINE_PROP_BOOL("atomic.mam", NvmeNamespace, params.atomic.mam, 0),
 };
 
 static void nvme_ns_class_init(ObjectClass *oc, const void *data)
diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h
index 43e3c916f73..edc84f347b3 100644
--- a/hw/nvme/nvme.h
+++ b/hw/nvme/nvme.h
@@ -232,6 +232,7 @@ typedef struct NvmeNamespaceParams {
         uint16_t nabsn;
         uint16_t nabspf;
         uint16_t nabo;
+        bool mam;
     } atomic;
 } NvmeNamespaceParams;
 

-- 
2.55.0


Reply via email to