]> www.infradead.org Git - users/sagi/libnvme.git/commitdiff
mi: Add inter-command-delay quirk
authorJeremy Kerr <jk@codeconstruct.com.au>
Wed, 21 Sep 2022 12:39:34 +0000 (20:39 +0800)
committerJeremy Kerr <jk@codeconstruct.com.au>
Thu, 3 Nov 2022 05:53:50 +0000 (13:53 +0800)
Some NVMe-MI controllers will not respond to a command that is sent too
soon after the previous response. This change our first quirk, which
inserts a suitable delay if necessary (ie, the quirk is set, and a
command is sent before the minimum delay time).

Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
src/nvme/mi.c
src/nvme/private.h

index c483874485b2101da6236f4ef22bb188142a6d68..b635fe54919f0a1c9ab855ba9536c04cfba3d9db 100644 (file)
@@ -10,6 +10,7 @@
 #include <stdlib.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <time.h>
 
 #include <ccan/array_size/array_size.h>
 #include <ccan/endian/endian.h>
@@ -79,6 +80,62 @@ void nvme_mi_ep_probe(struct nvme_mi_ep *ep)
        ep->quirks = 0;
 }
 
+static const int nsec_per_sec = 1000 * 1000 * 1000;
+/* timercmp and timersub, but for struct timespec */
+#define timespec_cmp(a, b, CMP)                                                \
+       (((a)->tv_sec == (b)->tv_sec)                                   \
+               ? ((a)->tv_nsec CMP (b)->tv_nsec)                       \
+               : ((a)->tv_sec CMP (b)->tv_sec))
+
+#define timespec_sub(a, b, result)                                     \
+       do {                                                            \
+               (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;           \
+               (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec;        \
+               if ((result)->tv_nsec < 0) {                            \
+                       --(result)->tv_sec;                             \
+                       (result)->tv_nsec += nsec_per_sec;              \
+               }                                                       \
+       } while (0)
+
+static void nvme_mi_insert_delay(struct nvme_mi_ep *ep)
+{
+       struct timespec now, next, delay;
+       int rc;
+
+       if (!ep->last_resp_time_valid)
+               return;
+
+       /* calculate earliest next command time */
+       next.tv_nsec = ep->last_resp_time.tv_nsec + ep->inter_command_us * 1000;
+       next.tv_sec = ep->last_resp_time.tv_sec;
+       if (next.tv_nsec > nsec_per_sec) {
+               next.tv_nsec -= nsec_per_sec;
+               next.tv_sec += 1;
+       }
+
+       rc = clock_gettime(CLOCK_MONOTONIC, &now);
+       if (rc) {
+               /* not much we can do; continue immediately */
+               return;
+       }
+
+       if (timespec_cmp(&now, &next, >=))
+               return;
+
+       timespec_sub(&next, &now, &delay);
+
+       nanosleep(&delay, NULL);
+}
+
+static void nvme_mi_record_resp_time(struct nvme_mi_ep *ep)
+{
+       int rc;
+
+       rc = clock_gettime(CLOCK_MONOTONIC, &ep->last_resp_time);
+       ep->last_resp_time_valid = !rc;
+}
+
+
 struct nvme_mi_ep *nvme_mi_init_ep(nvme_root_t root)
 {
        struct nvme_mi_ep *ep;
@@ -257,7 +314,14 @@ int nvme_mi_submit(nvme_mi_ep_t ep, struct nvme_mi_req *req,
        if (ep->transport->mic_enabled)
                nvme_mi_calc_req_mic(req);
 
+       if (nvme_mi_ep_has_quirk(ep, NVME_QUIRK_MIN_INTER_COMMAND_TIME))
+               nvme_mi_insert_delay(ep);
+
        rc = ep->transport->submit(ep, req, resp);
+
+       if (nvme_mi_ep_has_quirk(ep, NVME_QUIRK_MIN_INTER_COMMAND_TIME))
+               nvme_mi_record_resp_time(ep);
+
        if (rc) {
                nvme_msg(ep->root, LOG_INFO, "transport failure\n");
                return rc;
index bb695bddd10af3a151e502d855d9891e57ec0a13..fbaaaa4af03b884e8bd2a838cad701bf15b56d5d 100644 (file)
@@ -187,6 +187,14 @@ struct nvme_mi_transport {
        int (*check_timeout)(struct nvme_mi_ep *ep, unsigned int timeout);
 };
 
+/* quirks */
+
+/* Set a minimum time between receiving a response from one command and
+ * sending the next request. Some devices may ignore new commands sent too soon
+ * after the previous request, so manually insert a delay
+ */
+#define NVME_QUIRK_MIN_INTER_COMMAND_TIME      (1 << 0)
+
 struct nvme_mi_ep {
        struct nvme_root *root;
        const struct nvme_mi_transport *transport;
@@ -197,6 +205,11 @@ struct nvme_mi_ep {
        unsigned int timeout;
        unsigned int mprt_max;
        unsigned long quirks;
+
+       /* inter-command delay, for NVME_QUIRK_MIN_INTER_COMMAND_TIME */
+       unsigned int inter_command_us;
+       struct timespec last_resp_time;
+       bool last_resp_time_valid;
 };
 
 struct nvme_mi_ctrl {