From bfcc03d929aeb6adb7bc1a42cf275f89212fb4ec Mon Sep 17 00:00:00 2001 From: Tokunori Ikegami Date: Wed, 10 Jun 2026 02:53:05 +0900 Subject: [PATCH 1/6] nvme: add support for sanitize wait option The option is for waiting sanitize completion. Signed-off-by: Tokunori Ikegami --- nvme.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/nvme.c b/nvme.c index 956dd07470..312a5507fd 100644 --- a/nvme.c +++ b/nvme.c @@ -4632,7 +4632,7 @@ static int list_secondary_ctrl(int argc, char **argv, struct command *acmd, stru return err; } -static int sleep_self_test(unsigned int seconds) +static int nvme_sleep(unsigned int seconds) { nvme_sigint_received = false; @@ -4674,7 +4674,7 @@ static int wait_self_test(struct libnvme_transport_handle *hdl) while (true) { printf("\r[%.*s%c%.*s] %3d%%", p / 2, dash, spin[i % 4], 49 - p / 2, space, p); fflush(stdout); - err = sleep_self_test(1); + err = nvme_sleep(1); if (err) return err; @@ -4697,8 +4697,8 @@ static int wait_self_test(struct libnvme_transport_handle *hdl) if (log->completion < p) { printf("\n"); - nvme_show_error("progress broken"); - return -EIO; + nvme_show_error("progress broken"); + return -EIO; } else if (log->completion != p) { p = log->completion; cnt = 0; @@ -5618,6 +5618,96 @@ static int ns_rescan(int argc, char **argv, struct command *acmd, struct plugin return err; } +static int wait_sanitize(struct libnvme_transport_handle *hdl) +{ + __cleanup_libnvme_free struct nvme_sanitize_log_page *log = NULL; + static const char spin[] = {'-', '\\', '|', '/' }; + __u64 i = 0, cnt = 0, wthr = 0; + __u32 p = 0; + int err; + + log = libnvme_alloc(sizeof(*log)); + if (!log) + return -ENOMEM; + + err = nvme_get_log_sanitize(hdl, false, log); + if (err) { + nvme_show_err(err, "sanitize status log"); + return err; + } + + switch (NVME_GET(log->scdw10, SANITIZE_CDW10_SANACT)) { + case NVME_SANITIZE_SANACT_EXIT_FAILURE: + break; + case NVME_SANITIZE_SANACT_START_BLOCK_ERASE: + if (NVME_GET(log->scdw10, SANITIZE_CDW10_NDAS)) + wthr = le32_to_cpu(log->etbend); + else + wthr = le32_to_cpu(log->etbe); + break; + case NVME_SANITIZE_SANACT_START_OVERWRITE: + if (NVME_GET(log->scdw10, SANITIZE_CDW10_NDAS)) + wthr = le32_to_cpu(log->etond); + else + wthr = le32_to_cpu(log->eto); + break; + case NVME_SANITIZE_SANACT_START_CRYPTO_ERASE: + if (NVME_GET(log->scdw10, SANITIZE_CDW10_NDAS)) + wthr = le32_to_cpu(log->etcend); + else + wthr = le32_to_cpu(log->etce); + break; + case NVME_SANITIZE_SANACT_EXIT_MEDIA_VERIF: + default: + break; + } + if (wthr != 0xffffffff && NVME_GET(log->scdw10, SANITIZE_CDW10_EMVS)) + wthr += le32_to_cpu(log->etpvds); + + printf("Waiting for sanitize completion...\n"); + while (true) { + printf("\r[%.*s%c%.*s] %3d%%", p * 100 / 0xffff / 2, dash, + spin[i % 4], 49 - p * 100 / 0xffff / 2, space, + p * 100 / 0xffff); + fflush(stdout); + err = nvme_sleep(1); + if (err) + return err; + + err = nvme_get_log_sanitize(hdl, false, log); + if (err) { + printf("\n"); + nvme_show_err(err, "sanitize status log"); + return err; + } + + if (++cnt > wthr) { + nvme_show_error( + "no progress for %"PRIu64" seconds, stop waiting", + wthr); + return -EIO; + } + + if (le16_to_cpu(log->sprog) == 0xffff) { + printf("\r[%.*s] %3d%%\n", 50, dash, 100); + break; + } + + if (le16_to_cpu(log->sprog) < p) { + printf("\n"); + nvme_show_error("progress broken"); + return -EIO; + } else if (le16_to_cpu(log->sprog) != p) { + p = le16_to_cpu(log->sprog); + cnt = 0; + } + + i++; + } + + return 0; +} + static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plugin *plugin) { const char *desc = "Send a sanitize command."; @@ -5629,6 +5719,7 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug const char *sanact_desc = "Sanitize action: 1 = Exit failure mode, 2 = Start block erase," "3 = Start overwrite, 4 = Start crypto erase, 5 = Exit media verification"; const char *ovrpat_desc = "Overwrite pattern."; + const char *wait = "Wait for the sanitize to finish"; __cleanup_nvme_transport_handle struct libnvme_transport_handle *hdl = NULL; __cleanup_nvme_global_ctx struct libnvme_global_ctx *ctx = NULL; @@ -5645,6 +5736,7 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug __u8 sanact; __u32 ovrpat; bool emvs; + bool wait; }; struct config cfg = { @@ -5675,7 +5767,8 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug OPT_FLAG("ause", 'u', &cfg.ause, ause_desc), OPT_BYTE("sanact", 'a', &cfg.sanact, sanact_desc, sanact), OPT_UINT("ovrpat", 'p', &cfg.ovrpat, ovrpat_desc), - OPT_FLAG("emvs", 'e', &cfg.emvs, emvs_desc)); + OPT_FLAG("emvs", 'e', &cfg.emvs, emvs_desc), + OPT_FLAG("wait", 'w', &cfg.wait, wait)); err = parse_and_open(&ctx, &hdl, argc, argv, desc, opts); if (err) @@ -5735,6 +5828,9 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug return err; } + if (cfg.wait) + err = wait_sanitize(hdl); + return err; } From 112b0fc5976747f0bd3611241cf442bab44d6a07 Mon Sep 17 00:00:00 2001 From: Tokunori Ikegami Date: Thu, 11 Jun 2026 01:16:32 +0900 Subject: [PATCH 2/6] nvme: add support for sanitize repeat option Make verified multi-cycle sanitization accessible without a separate tool. Signed-off-by: Tokunori Ikegami --- nvme.c | 53 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/nvme.c b/nvme.c index 312a5507fd..cf4c6a290f 100644 --- a/nvme.c +++ b/nvme.c @@ -5708,6 +5708,36 @@ static int wait_sanitize(struct libnvme_transport_handle *hdl) return 0; } +static bool is_sanitized(struct libnvme_transport_handle *hdl) +{ + __cleanup_libnvme_free struct nvme_sanitize_log_page *log = NULL; + int err; + + log = libnvme_alloc(sizeof(*log)); + if (!log) + return -ENOMEM; + + err = nvme_get_log_sanitize(hdl, false, log); + if (err) { + nvme_show_err(err, "sanitize status log"); + return err; + } + + switch (NVME_GET(le16_to_cpu(log->sstat), SANITIZE_SSTAT_STATUS)) { + case NVME_SANITIZE_SSTAT_STATUS_NEVER_SANITIZED: + break; + case NVME_SANITIZE_SSTAT_STATUS_COMPLETE_SUCCESS: + return true; + case NVME_SANITIZE_SSTAT_STATUS_IN_PROGRESS: + case NVME_SANITIZE_SSTAT_STATUS_COMPLETED_FAILED: + case NVME_SANITIZE_SSTAT_STATUS_ND_COMPLETE_SUCCESS: + default: + break; + } + + return false; +} + static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plugin *plugin) { const char *desc = "Send a sanitize command."; @@ -5720,6 +5750,7 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug "3 = Start overwrite, 4 = Start crypto erase, 5 = Exit media verification"; const char *ovrpat_desc = "Overwrite pattern."; const char *wait = "Wait for the sanitize to finish"; + const char *repeat = "Repeat for the multi cycle sanitization"; __cleanup_nvme_transport_handle struct libnvme_transport_handle *hdl = NULL; __cleanup_nvme_global_ctx struct libnvme_global_ctx *ctx = NULL; @@ -5737,6 +5768,7 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug __u32 ovrpat; bool emvs; bool wait; + __u32 repeat; }; struct config cfg = { @@ -5748,6 +5780,7 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug .sanact = 0, .ovrpat = 0, .emvs = false, + .repeat = 1, }; OPT_VALS(sanact) = { @@ -5768,7 +5801,8 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug OPT_BYTE("sanact", 'a', &cfg.sanact, sanact_desc, sanact), OPT_UINT("ovrpat", 'p', &cfg.ovrpat, ovrpat_desc), OPT_FLAG("emvs", 'e', &cfg.emvs, emvs_desc), - OPT_FLAG("wait", 'w', &cfg.wait, wait)); + OPT_FLAG("wait", 'w', &cfg.wait, wait), + OPT_UINT("repeat", 'r', &cfg.repeat, repeat)); err = parse_and_open(&ctx, &hdl, argc, argv, desc, opts); if (err) @@ -5822,14 +5856,17 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug else printf("ISH is supported only for NVMe-MI\n"); } - err = libnvme_exec_admin_passthru(hdl, &cmd); - if (err) { - nvme_show_err(err, "sanitize"); - return err; - } - if (cfg.wait) - err = wait_sanitize(hdl); + do { + err = libnvme_exec_admin_passthru(hdl, &cmd); + if (err) { + nvme_show_err(err, "sanitize"); + return err; + } + + if (cfg.wait) + err = wait_sanitize(hdl); + } while (--cfg.repeat && !err && is_sanitized(hdl)); return err; } From 9c86533df80fc53fa38a74b0e5b47d3af026d98c Mon Sep 17 00:00:00 2001 From: Tokunori Ikegami Date: Fri, 12 Jun 2026 01:26:35 +0900 Subject: [PATCH 3/6] nvme-types-base: add sanicap vers and nvers definitions Also add sanicap shift and mask definitions to get. Signed-off-by: Tokunori Ikegami --- libnvme/src/nvme/nvme-types-base.h | 37 +++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/libnvme/src/nvme/nvme-types-base.h b/libnvme/src/nvme/nvme-types-base.h index 4e6b19e738..ac4e478c8e 100644 --- a/libnvme/src/nvme/nvme-types-base.h +++ b/libnvme/src/nvme/nvme-types-base.h @@ -2297,12 +2297,37 @@ enum nvme_id_ctrl_hctm { * mask to extract value. */ enum nvme_id_ctrl_sanicap { - NVME_CTRL_SANICAP_CES = 1 << 0, - NVME_CTRL_SANICAP_BES = 1 << 1, - NVME_CTRL_SANICAP_OWS = 1 << 2, - NVME_CTRL_SANICAP_NDI = 1 << 29, - NVME_CTRL_SANICAP_NODMMAS = 3 << 30, -}; + NVME_CTRL_SANICAP_CES_SHIFT = 0, + NVME_CTRL_SANICAP_BES_SHIFT = 1, + NVME_CTRL_SANICAP_OWS_SHIFT = 2, + NVME_CTRL_SANICAP_VERS_SHIFT = 4, + NVME_CTRL_SANICAP_NVERS_SHIFT = 5, + NVME_CTRL_SANICAP_NDI_SHIFT = 29, + NVME_CTRL_SANICAP_NODMMAS_SHIFT = 30, + NVME_CTRL_SANICAP_CES_MASK = 0x1, + NVME_CTRL_SANICAP_BES_MASK = 0x1, + NVME_CTRL_SANICAP_OWS_MASK = 0x1, + NVME_CTRL_SANICAP_VERS_MASK = 0x1, + NVME_CTRL_SANICAP_NVERS_MASK = 0x1, + NVME_CTRL_SANICAP_NDI_MASK = 0x1, + NVME_CTRL_SANICAP_NODMMAS_MASK = 0x3, + NVME_CTRL_SANICAP_CES = NVME_VAL(CTRL_SANICAP_CES), + NVME_CTRL_SANICAP_BES = NVME_VAL(CTRL_SANICAP_BES), + NVME_CTRL_SANICAP_OWS = NVME_VAL(CTRL_SANICAP_OWS), + NVME_CTRL_SANICAP_VERS = NVME_VAL(CTRL_SANICAP_VERS), + NVME_CTRL_SANICAP_NVERS = NVME_VAL(CTRL_SANICAP_NVERS), + NVME_CTRL_SANICAP_NDI = NVME_VAL(CTRL_SANICAP_NDI), + NVME_CTRL_SANICAP_NODMMAS = NVME_VAL(CTRL_SANICAP_NODMMAS), +}; + +#define NVME_CTRL_SANICAP_CES(sanicap) NVME_GET(sanicap, CTRL_SANICAP_CES) +#define NVME_CTRL_SANICAP_BES(sanicap) NVME_GET(sanicap, CTRL_SANICAP_BES) +#define NVME_CTRL_SANICAP_OWS(sanicap) NVME_GET(sanicap, CTRL_SANICAP_OWS) +#define NVME_CTRL_SANICAP_VERS(sanicap) NVME_GET(sanicap, CTRL_SANICAP_VERS) +#define NVME_CTRL_SANICAP_NVERS(sanicap) NVME_GET(sanicap, CTRL_SANICAP_NVERS) +#define NVME_CTRL_SANICAP_NDI(sanicap) NVME_GET(sanicap, CTRL_SANICAP_NDI) +#define NVME_CTRL_SANICAP_NODMMAS(sanicap) \ + NVME_GET(sanicap, CTRL_SANICAP_NODMMAS) /** * enum nvme_id_ctrl_anacap - This field indicates the capabilities associated From de8ae6edea7c24fb958c6c81e1fa73f4ce2c6f64 Mon Sep 17 00:00:00 2001 From: Tokunori Ikegami Date: Fri, 12 Jun 2026 02:21:30 +0900 Subject: [PATCH 4/6] nvme: add support for sanitize sanicap verification This is to prevent issuing unsupported actions. Signed-off-by: Tokunori Ikegami --- nvme.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/nvme.c b/nvme.c index cf4c6a290f..7d229951fb 100644 --- a/nvme.c +++ b/nvme.c @@ -5738,6 +5738,26 @@ static bool is_sanitized(struct libnvme_transport_handle *hdl) return false; } +struct nvme_id_ctrl *identify_ctrl(struct libnvme_transport_handle *hdl) +{ + struct nvme_id_ctrl *ctrl = libnvme_alloc(sizeof(*ctrl)); + int err = 0; + + if (!ctrl) { + errno = ENOMEM; + return NULL; + } + + err = nvme_identify_ctrl(hdl, ctrl); + if (err) { + nvme_show_error("identify-ctrl: %s", libnvme_strerror(err)); + libnvme_free(ctrl); + return NULL; + } + + return ctrl; +} + static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plugin *plugin) { const char *desc = "Send a sanitize command."; @@ -5754,6 +5774,7 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug __cleanup_nvme_transport_handle struct libnvme_transport_handle *hdl = NULL; __cleanup_nvme_global_ctx struct libnvme_global_ctx *ctx = NULL; + __cleanup_libnvme_free struct nvme_id_ctrl *ctrl = NULL; struct libnvme_passthru_cmd cmd; nvme_print_flags_t flags; int err; @@ -5814,11 +5835,31 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug return err; } + ctrl = identify_ctrl(hdl); + if (!ctrl) + return -errno; + switch (cfg.sanact) { case NVME_SANITIZE_SANACT_EXIT_FAILURE: + break; case NVME_SANITIZE_SANACT_START_BLOCK_ERASE: + if (!NVME_CTRL_SANICAP_BES(le32_to_cpu(ctrl->sanicap))) { + nvme_show_error("block erase action unsupported"); + return -EINVAL; + } + break; case NVME_SANITIZE_SANACT_START_OVERWRITE: + if (!NVME_CTRL_SANICAP_OWS(le32_to_cpu(ctrl->sanicap))) { + nvme_show_error("overwrite action unsupported"); + return -EINVAL; + } + break; case NVME_SANITIZE_SANACT_START_CRYPTO_ERASE: + if (!NVME_CTRL_SANICAP_CES(le32_to_cpu(ctrl->sanicap))) { + nvme_show_error("crypto erase action unsupported"); + return -EINVAL; + } + break; case NVME_SANITIZE_SANACT_EXIT_MEDIA_VERIF: break; default: @@ -5826,6 +5867,11 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug return -EINVAL; } + if (cfg.emvs && !NVME_CTRL_SANICAP_VERS(le32_to_cpu(ctrl->sanicap))) { + nvme_show_error("media verification unsupported"); + return -EINVAL; + } + if (cfg.ause || cfg.no_dealloc) { if (cfg.sanact == NVME_SANITIZE_SANACT_EXIT_FAILURE) { nvme_show_error("SANACT is Exit Failure Mode"); From f2122efcd2087335ef0f7122251ec0f654c011c7 Mon Sep 17 00:00:00 2001 From: Tokunori Ikegami Date: Sun, 14 Jun 2026 23:26:27 +0900 Subject: [PATCH 5/6] doc: add sanitize command wait and repeat options These options for the multi-cycle sanitization accessible. Signed-off-by: Tokunori Ikegami --- Documentation/nvme-sanitize.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Documentation/nvme-sanitize.txt b/Documentation/nvme-sanitize.txt index ec0f63a27c..bfb0af2f7c 100644 --- a/Documentation/nvme-sanitize.txt +++ b/Documentation/nvme-sanitize.txt @@ -14,7 +14,8 @@ SYNOPSIS [--ause | -u] [--sanact= | -a ] [--ovrpat= | -p ] [--emvs | -e] [--force] - [--human-readable | -H] + [--human-readable | -H] [--wait | -w] + [--repeat= | -r ] DESCRIPTION ----------- @@ -103,6 +104,14 @@ OPTIONS Display values in a human-readable format where possible. (deprecated, use --verbose) +-w:: +--wait:: + Wait for the sanitize to finish + +-r :: +--repeat=:: + Repeat for the multi cycle sanitization + include::global-options.txt[] EXAMPLES From 8b9a5895ea67d971563727ab343466a16e12b094 Mon Sep 17 00:00:00 2001 From: Tokunori Ikegami Date: Tue, 16 Jun 2026 00:11:58 +0900 Subject: [PATCH 6/6] completions: add sanitize command wait and repeat options These options for the multi-cycle sanitization accessible. Signed-off-by: Tokunori Ikegami --- completions/_nvme | 4 ++++ completions/bash-nvme-completion.sh | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/completions/_nvme b/completions/_nvme index 8e6273eb10..59c0525719 100644 --- a/completions/_nvme +++ b/completions/_nvme @@ -1644,6 +1644,10 @@ _nvme () { -p':alias of --ovrpat' --emvs=':Enter media verification state' -e':alias of --emvs' + --wait':Wait for the sanitize to finish' + -w':alias of --wait' + --repeat=':Repeat for the multi cycle sanitization' + -r':alias of --repeat' ) _arguments '*:: :->subcmds' _describe -t commands "nvme sanitize options" _sanitize diff --git a/completions/bash-nvme-completion.sh b/completions/bash-nvme-completion.sh index 9355dc86db..933c9918dc 100644 --- a/completions/bash-nvme-completion.sh +++ b/completions/bash-nvme-completion.sh @@ -366,7 +366,8 @@ nvme_list_opts () { ;; "sanitize") opts+=" --no-dealloc -d --oipbp -i --owpass= -n \ - --ause -u --sanact= -a --ovrpat= -p --emvs= -e" + --ause -u --sanact= -a --ovrpat= -p --emvs= -e \ + --wait -w --repeat= -r" case $opt in --sanact|-a) vals+=" exit-failure start-block-erase start-overwrite \