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 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 \ 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 diff --git a/nvme.c b/nvme.c index 956dd07470..7d229951fb 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,146 @@ 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 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; +} + +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."; @@ -5629,9 +5769,12 @@ 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"; + 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; + __cleanup_libnvme_free struct nvme_id_ctrl *ctrl = NULL; struct libnvme_passthru_cmd cmd; nvme_print_flags_t flags; int err; @@ -5645,6 +5788,8 @@ static int sanitize_cmd(int argc, char **argv, struct command *acmd, struct plug __u8 sanact; __u32 ovrpat; bool emvs; + bool wait; + __u32 repeat; }; struct config cfg = { @@ -5656,6 +5801,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) = { @@ -5675,7 +5821,9 @@ 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), + OPT_UINT("repeat", 'r', &cfg.repeat, repeat)); err = parse_and_open(&ctx, &hdl, argc, argv, desc, opts); if (err) @@ -5687,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: @@ -5699,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"); @@ -5729,11 +5902,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; - } + + 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; }