diff --git a/include/spdk/nvme.h b/include/spdk/nvme.h index 30d95b0a7..547f34d0b 100644 --- a/include/spdk/nvme.h +++ b/include/spdk/nvme.h @@ -381,6 +381,28 @@ void spdk_nvme_ctrlr_register_aer_callback(struct spdk_nvme_ctrlr *ctrlr, */ struct spdk_nvme_qpair; +/** + * Signature for the callback function invoked when a timeout is + * detected on a request. + */ +typedef void (*spdk_nvme_timeout_cb)(struct spdk_nvme_ctrlr *ctrlr, + struct spdk_nvme_qpair *qpair, + void *cb_arg); + +/** + * \brief Register for timeout callback on a controller. + * + * The application can choose to register for timeout callback or not register + * for timeout callback. + * + * \param ctrlr NVMe controller on which to monitor for timeout. + * \param timeout_sec Timeout value in seconds. + * \param cb_fn A function pointer that points to the callback function + * \param cb_arg Argument to the callback function. + */ +void spdk_nvme_ctrlr_register_timeout_callback(struct spdk_nvme_ctrlr *ctrlr, + uint32_t timeout_sec, spdk_nvme_timeout_cb cb_fn, void *cb_arg); + /** * \brief Allocate an I/O queue pair (submission and completion queue). * diff --git a/lib/nvme/nvme_ctrlr.c b/lib/nvme/nvme_ctrlr.c index cfe0594cb..453fe283d 100644 --- a/lib/nvme/nvme_ctrlr.c +++ b/lib/nvme/nvme_ctrlr.c @@ -1299,6 +1299,9 @@ nvme_ctrlr_construct(struct spdk_nvme_ctrlr *ctrlr) } TAILQ_INIT(&ctrlr->active_procs); + ctrlr->timeout_cb_fn = NULL; + ctrlr->timeout_cb_arg = NULL; + ctrlr->timeout_ticks = 0; return rc; } @@ -1430,6 +1433,15 @@ spdk_nvme_ctrlr_register_aer_callback(struct spdk_nvme_ctrlr *ctrlr, ctrlr->aer_cb_arg = aer_cb_arg; } +void +spdk_nvme_ctrlr_register_timeout_callback(struct spdk_nvme_ctrlr *ctrlr, + uint32_t nvme_timeout, spdk_nvme_timeout_cb cb_fn, void *cb_arg) +{ + ctrlr->timeout_ticks = nvme_timeout * spdk_get_ticks_hz(); + ctrlr->timeout_cb_fn = cb_fn; + ctrlr->timeout_cb_arg = cb_arg; +} + bool spdk_nvme_ctrlr_is_log_page_supported(struct spdk_nvme_ctrlr *ctrlr, uint8_t log_page) { diff --git a/lib/nvme/nvme_internal.h b/lib/nvme/nvme_internal.h index 47d55b914..f8d402a5f 100644 --- a/lib/nvme/nvme_internal.h +++ b/lib/nvme/nvme_internal.h @@ -411,6 +411,13 @@ struct spdk_nvme_ctrlr { /** Track all the processes manage this controller */ TAILQ_HEAD(, spdk_nvme_ctrlr_process) active_procs; + + /** + * A function pointer to timeout callback function + */ + spdk_nvme_timeout_cb timeout_cb_fn; + void *timeout_cb_arg; + uint64_t timeout_ticks; }; struct nvme_driver { diff --git a/lib/nvme/nvme_pcie.c b/lib/nvme/nvme_pcie.c index adfd53265..e5427e303 100644 --- a/lib/nvme/nvme_pcie.c +++ b/lib/nvme/nvme_pcie.c @@ -121,14 +121,13 @@ struct nvme_tracker { uint32_t rsvd2; + uint64_t timeout_tick; uint64_t prp_sgl_bus_addr; union { uint64_t prp[NVME_MAX_PRP_LIST_ENTRIES]; struct spdk_nvme_sgl_descriptor sgl[NVME_MAX_SGL_DESCRIPTORS]; } u; - - uint64_t rsvd3; }; /* * struct nvme_tracker must be exactly 4K so that the prp[] array does not cross a page boundary @@ -933,6 +932,8 @@ nvme_pcie_qpair_submit_tracker(struct spdk_nvme_qpair *qpair, struct nvme_tracke struct nvme_pcie_qpair *pqpair = nvme_pcie_qpair(qpair); struct nvme_pcie_ctrlr *pctrlr = nvme_pcie_ctrlr(qpair->ctrlr); + tr->timeout_tick = spdk_get_ticks() + qpair->ctrlr->timeout_ticks; + req = tr->req; pqpair->tr[tr->cid].active = true; @@ -1725,6 +1726,44 @@ exit: return rc; } +static void +nvme_pcie_qpair_check_timeout(struct spdk_nvme_qpair *qpair) +{ + uint64_t t02; + struct nvme_tracker *tr; + struct nvme_pcie_qpair *pqpair = nvme_pcie_qpair(qpair); + struct spdk_nvme_ctrlr *ctrlr = qpair->ctrlr; + + if (TAILQ_EMPTY(&pqpair->outstanding_tr)) { + return; + } + + /* + * qpair could be either for normal i/o or for admin command. If qpair is admin + * and request is SPDK_NVME_OPC_ASYNC_EVENT_REQUEST, skip to next previous. + */ + tr = TAILQ_LAST(&pqpair->outstanding_tr, nvme_outstanding_tr_head); + while (tr->req->cmd.opc == SPDK_NVME_OPC_ASYNC_EVENT_REQUEST) { + /* qpair is for admin request */ + tr = TAILQ_PREV(tr, nvme_outstanding_tr_head, tq_list); + if (!tr) { + /* + * All request were AER + */ + return; + } + } + + t02 = spdk_get_ticks(); + if (tr->timeout_tick <= t02) { + /* + * Request has timed out. This could be i/o or admin request. + * Call the registered timeout function for user to take action. + */ + ctrlr->timeout_cb_fn(ctrlr, qpair, ctrlr->timeout_cb_arg); + } +} + int32_t nvme_pcie_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions) { @@ -1791,6 +1830,15 @@ nvme_pcie_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_ g_thread_mmio_ctrlr = NULL; } + if (qpair->ctrlr->state == NVME_CTRLR_STATE_READY) { + if (qpair->ctrlr->timeout_cb_fn) { + /* + * User registered for timeout callback + */ + nvme_pcie_qpair_check_timeout(qpair); + } + } + /* Before returning, complete any pending admin request. */ if (nvme_qpair_is_admin_queue(qpair)) { nvme_pcie_qpair_complete_pending_admin_request(qpair);