Index: sys/dev/nvme/nvme.h =================================================================== --- sys/dev/nvme/nvme.h +++ sys/dev/nvme/nvme.h @@ -1394,23 +1394,25 @@ _Static_assert(sizeof(struct nvme_command_effects_page) == 4096, "bad size for nvme_command_effects_page"); +struct nvme_device_self_test_result { + uint8_t status; + uint8_t segment_num; + uint8_t valid_diag_info; + uint8_t rsvd3; + uint64_t poh; + uint32_t nsid; + /* Define as an array to simplify alignment issues */ + uint8_t failing_lba[8]; + uint8_t status_code_type; + uint8_t status_code; + uint8_t vendor_specific[2]; +} __packed; + struct nvme_device_self_test_page { - uint8_t curr_operation; - uint8_t curr_compl; - uint8_t rsvd2[2]; - struct { - uint8_t status; - uint8_t segment_num; - uint8_t valid_diag_info; - uint8_t rsvd3; - uint64_t poh; - uint32_t nsid; - /* Define as an array to simplify alignment issues */ - uint8_t failing_lba[8]; - uint8_t status_code_type; - uint8_t status_code; - uint8_t vendor_specific[2]; - } __packed result[20]; + uint8_t curr_operation; + uint8_t curr_compl; + uint8_t rsvd2[2]; + struct nvme_device_self_test_result result[20]; } __packed __aligned(4); _Static_assert(sizeof(struct nvme_device_self_test_page) == 564, Index: usr.sbin/bhyve/pci_nvme.c =================================================================== --- usr.sbin/bhyve/pci_nvme.c +++ usr.sbin/bhyve/pci_nvme.c @@ -62,6 +62,7 @@ #include #include #include +#include #include #include @@ -184,6 +185,33 @@ NVME_STOR_RAM = 1, }; +#define SELF_TEST_SHORT_DURATION (60) +#define SELF_TEST_EXTENDED_DURATION (300) + +#define SELF_TEST_IN_PROGRESS_SHORT(ops, start, now) \ + (ops == 0x1 && (now.tv_sec - start.tv_sec) < SELF_TEST_SHORT_DURATION) +#define SELF_TEST_IN_PROGRESS_EXTENDED(ops, start, now) \ + (ops == 0x2 && (now.tv_sec - start.tv_sec) < SELF_TEST_EXTENDED_DURATION) +#define SELF_TEST_IN_PROGRESS(ops, start, now) \ + (SELF_TEST_IN_PROGRESS_SHORT(ops, start, now) || \ + SELF_TEST_IN_PROGRESS_EXTENDED(ops, start, now)) + +#define SELF_TEST_FINISH_SHORT(ops, start, now) \ + (ops == 0x1 && (now.tv_sec - start.tv_sec) >= SELF_TEST_SHORT_DURATION) +#define SELF_TEST_FINISH_EXTENDED(ops, start, now) \ + (ops == 0x2 && (now.tv_sec - start.tv_sec) >= SELF_TEST_EXTENDED_DURATION) +#define SELF_TEST_FINISH(ops, start, now) \ + (SELF_TEST_FINISH_SHORT(ops, start, now) || \ + SELF_TEST_FINISH_EXTENDED(ops, start, now)) + +#define SELF_TEST_CACULATE_PROGRESS_SHORT(start, now) \ + (((now.tv_sec - start.tv_sec) * 100 / SELF_TEST_SHORT_DURATION) & 0x7F) +#define SELF_TEST_CACULATE_PROGRESS_EXTENDED(start, now) \ + (((now.tv_sec - start.tv_sec) * 100 / SELF_TEST_EXTENDED_DURATION) & 0x7F) + +#define TIME_DIFF_IN_HOURS(start, now) \ + ((now.tv_sec - start.tv_sec) / 3600) + struct pci_nvme_blockstore { enum nvme_storage_type type; void *ctx; @@ -267,6 +295,7 @@ struct nvme_error_information_entry err_log; struct nvme_health_information_page health_log; struct nvme_firmware_page fw_log; + struct nvme_device_self_test_page self_log; struct pci_nvme_blockstore nvstore; @@ -301,6 +330,10 @@ uint32_t read_dunits_remainder; uint32_t write_dunits_remainder; + struct timeval power_on_time; + struct timeval self_test_start_time; + struct nvme_device_self_test_result self_test_result; + STAILQ_HEAD(, pci_nvme_aer) aer_list; uint32_t aer_count; }; @@ -309,6 +342,7 @@ static struct pci_nvme_ioreq *pci_nvme_get_ioreq(struct pci_nvme_softc *); static void pci_nvme_release_ioreq(struct pci_nvme_softc *, struct pci_nvme_ioreq *); static void pci_nvme_io_done(struct blockif_req *, int); +static void nvme_self_test_make_progress(struct pci_nvme_softc *); /* Controller Configuration utils */ #define NVME_CC_GET_EN(cc) \ @@ -467,7 +501,8 @@ cd->ver = 0x00010300; - cd->oacs = 1 << NVME_CTRLR_DATA_OACS_FORMAT_SHIFT; + cd->oacs = (1 << NVME_CTRLR_DATA_OACS_FORMAT_SHIFT) | + (1 << NVME_CTRLR_DATA_OACS_SELFTEST_SHIFT); cd->acl = 2; cd->aerl = 4; @@ -503,6 +538,11 @@ cd->fna = 0x03; cd->power_state[0].mp = 10; + + /* minutes to complete extended device self-test */ + cd->edstt = SELF_TEST_EXTENDED_DURATION / 60; + + gettimeofday(&sc->power_on_time, NULL); } /* @@ -600,6 +640,7 @@ memset(&sc->err_log, 0, sizeof(sc->err_log)); memset(&sc->health_log, 0, sizeof(sc->health_log)); memset(&sc->fw_log, 0, sizeof(sc->fw_log)); + memset(&sc->self_log, 0, sizeof(sc->self_log)); /* Set read/write remainder to round up according to spec */ sc->read_dunits_remainder = 999; @@ -1113,6 +1154,13 @@ MIN(logsize, sizeof(sc->fw_log)), NVME_COPY_TO_PRP); break; + case NVME_LOG_DEVICE_SELF_TEST: + nvme_self_test_make_progress(sc); + nvme_prp_memcpy(sc->nsc_pi->pi_vmctx, command->prp1, + command->prp2, (uint8_t *)&sc->self_log, + MIN(logsize, sizeof(sc->self_log)), + NVME_COPY_TO_PRP); + break; default: DPRINTF("%s get log page %x command not supported", __func__, logpage); @@ -1442,6 +1490,94 @@ return (0); } +static void +nvme_insert_self_test_log(struct nvme_device_self_test_result* result, + struct nvme_device_self_test_page* self_log) +{ + uint8_t i; + + DPRINTF("%s", __func__); + /* + * Newest log entry always inserts to position 0. + * move old ones to next position. + * the last log entry will be abandoned + */ + for (i = 19; i > 0; i--) { + memcpy((uint8_t*)&self_log->result[i], (uint8_t*)&self_log->result[i - 1], + sizeof(struct nvme_device_self_test_result)); + } + memcpy(&self_log->result[0], result, sizeof(struct nvme_device_self_test_result)); + memset(result, 0, sizeof(struct nvme_device_self_test_result)); +} + +static void +nvme_self_test_make_progress(struct pci_nvme_softc* sc) +{ + struct timeval now; + + gettimeofday(&now, NULL); + DPRINTF("%s, self test duration %ld", __func__, now.tv_sec - sc->self_test_start_time.tv_sec); + + if (SELF_TEST_IN_PROGRESS_SHORT(sc->self_log.curr_operation, sc->self_test_start_time, now)) { + sc->self_log.curr_compl = SELF_TEST_CACULATE_PROGRESS_SHORT(sc->self_test_start_time, now); + } else if (SELF_TEST_IN_PROGRESS_EXTENDED(sc->self_log.curr_operation, sc->self_test_start_time, now)) { + sc->self_log.curr_compl = SELF_TEST_CACULATE_PROGRESS_EXTENDED(sc->self_test_start_time, now); + } else if (SELF_TEST_FINISH(sc->self_log.curr_operation, sc->self_test_start_time, now)) { + sc->self_log.curr_operation = 0; + sc->self_log.curr_compl = 0; + nvme_insert_self_test_log(&sc->self_test_result, &sc->self_log); /* insert log entry */ + } +} + +static int +nvme_opc_device_self_test(struct pci_nvme_softc* sc, struct nvme_command* command, + struct nvme_completion* compl) +{ + uint32_t nsid = command->nsid; + uint8_t stc = command->cdw10 & 0xF; + struct timeval now; + + DPRINTF("%s stc %d", __func__, stc); + if (nsid != 0 && nsid != 1 && nsid != 0xFFFFFFFF) { + pci_nvme_status_genc(&compl->status, + NVME_SC_INVALID_NAMESPACE_OR_FORMAT); + return (1); + } + + gettimeofday(&now, NULL); + nvme_self_test_make_progress(sc); + + switch (stc) { + case 0x1: /* Start a short device self-test operation */ + /* FALLTHROUGH */ + case 0x2: /* Start an extended device self-test operation */ + if (SELF_TEST_IN_PROGRESS(sc->self_log.curr_operation, sc->self_test_start_time, now)) { + pci_nvme_status_tc(&compl->status, NVME_SCT_COMMAND_SPECIFIC, + NVME_SC_SELF_TEST_IN_PROGRESS); + return (1); + } + memcpy(&sc->self_test_start_time, &now, sizeof(struct timeval)); + sc->self_log.curr_operation = stc; + + sc->self_test_result.status = stc << 4; + sc->self_test_result.poh = TIME_DIFF_IN_HOURS(sc->power_on_time, now); + break; + case 0xF: /* Abort device self-test operation */ + if (SELF_TEST_IN_PROGRESS(sc->self_log.curr_operation, sc->self_test_start_time, now)) { + sc->self_log.curr_operation = 0; /* clear current operation */ + sc->self_test_result.status |= 0x1; /* aborted */ + nvme_insert_self_test_log(&sc->self_test_result, &sc->self_log); + } + break; + default: + pci_nvme_status_genc(&compl->status, NVME_SC_INVALID_FIELD); + return (1); + } + pci_nvme_status_genc(&compl->status, NVME_SC_SUCCESS); + + return (0); +} + static int nvme_opc_format_nvm(struct pci_nvme_softc* sc, struct nvme_command* command, struct nvme_completion* compl) @@ -1617,6 +1753,10 @@ NVME_SCT_COMMAND_SPECIFIC, NVME_SC_INVALID_FIRMWARE_SLOT); break; + case NVME_OPC_DEVICE_SELF_TEST: + DPRINTF("%s command DEVICE_SELF_TEST", __func__); + nvme_opc_device_self_test(sc, cmd, &compl); + break; case NVME_OPC_ASYNC_EVENT_REQUEST: DPRINTF("%s command ASYNC_EVENT_REQ", __func__); nvme_opc_async_event_req(sc, cmd, &compl);