From 843c387a1f8e7bb735e2c4a0464240f16deebf2d Mon Sep 17 00:00:00 2001 From: Jim Harris Date: Sat, 4 Dec 2021 02:56:47 +0000 Subject: [PATCH] nvme: add spdk_nvme_ctrlr_get_discovery_log_page API This API is a helper for getting the full discovery log page from a discovery controller. It will read the log page header to get the total number of entries, allocate a buffer for all of the entries, and then issue a series of get_log_page commands to read each 4KiB worth of entries. Signed-off-by: Jim Harris Change-Id: I02666ef5adcb9fc8825a221655811ace708f97b8 Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/10564 Community-CI: Broadcom CI Reviewed-by: Changpeng Liu Reviewed-by: Aleksey Marchuk Tested-by: SPDK CI Jenkins --- examples/nvme/identify/identify.c | 92 ++------------ include/spdk/nvme.h | 36 ++++++ lib/nvme/Makefile | 7 +- lib/nvme/nvme_discovery.c | 200 ++++++++++++++++++++++++++++++ lib/nvme/spdk_nvme.map | 1 + 5 files changed, 254 insertions(+), 82 deletions(-) create mode 100644 lib/nvme/nvme_discovery.c diff --git a/examples/nvme/identify/identify.c b/examples/nvme/identify/identify.c index 43f64f1a4..038bef314 100644 --- a/examples/nvme/identify/identify.c +++ b/examples/nvme/identify/identify.c @@ -378,94 +378,26 @@ get_intel_md_log_page(struct spdk_nvme_ctrlr *ctrlr) } static void -get_discovery_log_page_header_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl) +get_discovery_log_page_cb(void *ctx, int rc, const struct spdk_nvme_cpl *cpl, + struct spdk_nvmf_discovery_log_page *log_page) { - struct spdk_nvmf_discovery_log_page *new_discovery_page; - struct spdk_nvme_ctrlr *ctrlr = cb_arg; - uint16_t recfmt; - uint64_t remaining; - uint64_t offset; + if (rc || spdk_nvme_cpl_is_error(cpl)) { + printf("get discovery log page failed\n"); + exit(1); + } + g_discovery_page = log_page; + g_discovery_page_numrec = from_le64(&log_page->numrec); + g_discovery_page_size = sizeof(struct spdk_nvmf_discovery_log_page); + g_discovery_page_size += g_discovery_page_numrec * + sizeof(struct spdk_nvmf_discovery_log_page_entry); outstanding_commands--; - if (spdk_nvme_cpl_is_error(cpl)) { - /* Return without printing anything - this may not be a discovery controller */ - free(g_discovery_page); - g_discovery_page = NULL; - return; - } - - /* Got the first 4K of the discovery log page */ - recfmt = from_le16(&g_discovery_page->recfmt); - if (recfmt != 0) { - printf("Unrecognized discovery log record format %" PRIu16 "\n", recfmt); - return; - } - - g_discovery_page_numrec = from_le64(&g_discovery_page->numrec); - - /* Pick an arbitrary limit to avoid ridiculously large buffer size. */ - if (g_discovery_page_numrec > MAX_DISCOVERY_LOG_ENTRIES) { - printf("Discovery log has %" PRIu64 " entries - limiting to %" PRIu64 ".\n", - g_discovery_page_numrec, MAX_DISCOVERY_LOG_ENTRIES); - g_discovery_page_numrec = MAX_DISCOVERY_LOG_ENTRIES; - } - - /* - * Now that we now how many entries should be in the log page, we can allocate - * the full log page buffer. - */ - g_discovery_page_size += g_discovery_page_numrec * sizeof(struct - spdk_nvmf_discovery_log_page_entry); - new_discovery_page = realloc(g_discovery_page, g_discovery_page_size); - if (new_discovery_page == NULL) { - free(g_discovery_page); - printf("Discovery page allocation failed!\n"); - return; - } - - g_discovery_page = new_discovery_page; - - /* Retrieve the rest of the discovery log page */ - offset = offsetof(struct spdk_nvmf_discovery_log_page, entries); - remaining = g_discovery_page_size - offset; - while (remaining) { - uint32_t size; - - /* Retrieve up to 4 KB at a time */ - size = spdk_min(remaining, 4096); - - if (spdk_nvme_ctrlr_cmd_get_log_page(ctrlr, SPDK_NVME_LOG_DISCOVERY, - 0, (char *)g_discovery_page + offset, size, offset, - get_log_page_completion, NULL)) { - printf("spdk_nvme_ctrlr_cmd_get_log_page() failed\n"); - exit(1); - } - - offset += size; - remaining -= size; - outstanding_commands++; - } } static int get_discovery_log_page(struct spdk_nvme_ctrlr *ctrlr) { - /* Allocate the initial discovery log page buffer - this will be resized later. */ - g_discovery_page_size = sizeof(*g_discovery_page); - g_discovery_page = calloc(1, g_discovery_page_size); - if (g_discovery_page == NULL) { - printf("Discovery log page allocation failed!\n"); - exit(1); - } - - if (spdk_nvme_ctrlr_cmd_get_log_page(ctrlr, SPDK_NVME_LOG_DISCOVERY, - 0, g_discovery_page, g_discovery_page_size, 0, - get_discovery_log_page_header_completion, ctrlr)) { - printf("spdk_nvme_ctrlr_cmd_get_log_page() failed\n"); - exit(1); - } - - return 0; + return spdk_nvme_ctrlr_get_discovery_log_page(ctrlr, get_discovery_log_page_cb, NULL); } static void diff --git a/include/spdk/nvme.h b/include/spdk/nvme.h index 09de8b933..ce0e68cfd 100644 --- a/include/spdk/nvme.h +++ b/include/spdk/nvme.h @@ -1465,6 +1465,42 @@ void spdk_nvme_ctrlr_register_timeout_callback(struct spdk_nvme_ctrlr *ctrlr, uint64_t timeout_io_us, uint64_t timeout_admin_us, spdk_nvme_timeout_cb cb_fn, void *cb_arg); +/** + * Signature for the callback function when a + * \ref spdk_nvme_ctrlr_get_discovery_log_page operation is completed. + * + * \param cb_arg Argument passed to callback function. + * \param rc Status of operation. 0 means success, and that the cpl argument is valid. + * Failure indicated by negative errno value. + * \param cpl NVMe completion status of the operation. NULL if rc != 0. If multiple + * completions with error status occurred during the operation, the cpl + * value for the first error will be used here. + * \param log_page Pointer to the full discovery log page. The application is + * responsible for freeing this buffer using free(). + */ +typedef void (*spdk_nvme_discovery_cb)(void *cb_arg, int rc, + const struct spdk_nvme_cpl *cpl, + struct spdk_nvmf_discovery_log_page *log_page); + +/** + * Get a full discovery log page from the specified controller. + * + * This function will first read the discovery log header to determine the + * total number of valid entries in the discovery log, then it will allocate + * a buffer to hold the entire log and issue multiple GET_LOG_PAGE commands to + * get all of the entries. + * + * The application is responsible for calling + * \ref spdk_nvme_ctrlr_process_admin_completions to trigger processing of + * completions submitted by this function. + * + * \param ctrlr Pointer to the discovery controller. + * \param cb_fn Function to call when the operation is complete. + * \param cb_arg Argument to pass to cb_fn. + */ +int spdk_nvme_ctrlr_get_discovery_log_page(struct spdk_nvme_ctrlr *ctrlr, + spdk_nvme_discovery_cb cb_fn, void *cb_arg); + /** * NVMe I/O queue pair initialization options. * diff --git a/lib/nvme/Makefile b/lib/nvme/Makefile index 81af5e544..06180417f 100644 --- a/lib/nvme/Makefile +++ b/lib/nvme/Makefile @@ -37,8 +37,11 @@ include $(SPDK_ROOT_DIR)/mk/spdk.common.mk SO_VER := 7 SO_MINOR := 1 -C_SRCS = nvme_ctrlr_cmd.c nvme_ctrlr.c nvme_fabric.c nvme_ns_cmd.c nvme_ns.c nvme_pcie_common.c nvme_pcie.c nvme_qpair.c nvme.c nvme_quirks.c nvme_transport.c \ - nvme_ctrlr_ocssd_cmd.c nvme_ns_ocssd_cmd.c nvme_tcp.c nvme_opal.c nvme_io_msg.c nvme_poll_group.c nvme_zns.c +C_SRCS = nvme_ctrlr_cmd.c nvme_ctrlr.c nvme_fabric.c nvme_ns_cmd.c \ + nvme_ns.c nvme_pcie_common.c nvme_pcie.c nvme_qpair.c nvme.c \ + nvme_quirks.c nvme_transport.c nvme_discovery.c \ + nvme_ctrlr_ocssd_cmd.c nvme_ns_ocssd_cmd.c nvme_tcp.c \ + nvme_opal.c nvme_io_msg.c nvme_poll_group.c nvme_zns.c C_SRCS-$(CONFIG_VFIO_USER) += nvme_vfio_user.c C_SRCS-$(CONFIG_RDMA) += nvme_rdma.c C_SRCS-$(CONFIG_NVME_CUSE) += nvme_cuse.c diff --git a/lib/nvme/nvme_discovery.c b/lib/nvme/nvme_discovery.c new file mode 100644 index 000000000..71917277f --- /dev/null +++ b/lib/nvme/nvme_discovery.c @@ -0,0 +1,200 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nvme_internal.h" + +#include "spdk/endian.h" + +struct nvme_discovery_ctx { + struct spdk_nvme_ctrlr *ctrlr; + struct spdk_nvmf_discovery_log_page *log_page; + uint64_t genctr; + spdk_nvme_discovery_cb cb_fn; + void *cb_arg; + struct spdk_nvme_cpl cpl; + uint32_t outstanding_commands; +}; + +static void +get_log_page_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct nvme_discovery_ctx *ctx = cb_arg; + + if (spdk_nvme_cpl_is_error(cpl)) { + /* Only save the cpl for the first error that we encounter. */ + if (!spdk_nvme_cpl_is_error(&ctx->cpl)) { + ctx->cpl = *cpl; + } + } + ctx->outstanding_commands--; + if (ctx->outstanding_commands == 0) { + struct spdk_nvmf_discovery_log_page *log_page; + + if (!spdk_nvme_cpl_is_error(&ctx->cpl)) { + log_page = ctx->log_page; + } else { + /* We had an error, so don't return the log page to the caller. */ + log_page = NULL; + free(ctx->log_page); + } + + ctx->cb_fn(ctx->cb_arg, 0, &ctx->cpl, log_page); + free(ctx); + } +} + +static void +discovery_log_header_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct spdk_nvmf_discovery_log_page *new_page; + struct nvme_discovery_ctx *ctx = cb_arg; + size_t page_size; + uint16_t recfmt; + uint64_t numrec; + uint64_t remaining; + uint64_t offset; + int rc; + + if (spdk_nvme_cpl_is_error(cpl)) { + /* Return without printing anything - this may not be a discovery controller */ + ctx->cb_fn(ctx->cb_arg, 0, cpl, NULL); + free(ctx->log_page); + free(ctx); + return; + } + + /* Got the first 4K of the discovery log page */ + recfmt = from_le16(&ctx->log_page->recfmt); + if (recfmt != 0) { + SPDK_ERRLOG("Unrecognized discovery log record format %" PRIu16 "\n", recfmt); + ctx->cb_fn(ctx->cb_arg, -EINVAL, NULL, NULL); + free(ctx->log_page); + free(ctx); + return; + } + + numrec = from_le64(&ctx->log_page->numrec); + + if (numrec == 0) { + /* No entries in the discovery log. So we can just return the header to the + * caller. Increment outstanding_commands and use the get_log_page_completion() + * function to avoid duplicating that code here. + */ + ctx->outstanding_commands++; + get_log_page_completion(ctx, cpl); + return; + } + + /* + * Now that we know how many entries should be in the log page, we can allocate + * the full log page buffer. + */ + page_size = sizeof(struct spdk_nvmf_discovery_log_page); + page_size += numrec * sizeof(struct spdk_nvmf_discovery_log_page_entry); + new_page = realloc(ctx->log_page, page_size); + if (new_page == NULL) { + SPDK_ERRLOG("Could not allocate buffer for log page (%" PRIu64 " entries)\n", + numrec); + ctx->cb_fn(ctx->cb_arg, -ENOMEM, NULL, NULL); + free(ctx->log_page); + free(ctx); + return; + } + + ctx->log_page = new_page; + + /* Retrieve the rest of the discovery log page */ + offset = offsetof(struct spdk_nvmf_discovery_log_page, entries); + remaining = page_size - offset; + while (remaining) { + uint32_t size; + + /* Retrieve up to 4 KB at a time */ + size = spdk_min(remaining, 4096); + + ctx->outstanding_commands++; + rc = spdk_nvme_ctrlr_cmd_get_log_page(ctx->ctrlr, SPDK_NVME_LOG_DISCOVERY, + 0, (char *)ctx->log_page + offset, size, offset, + get_log_page_completion, ctx); + if (rc != 0) { + /* We may have already successfully submitted some get_log_page commands, + * so we cannot just call the user's callback with error status and free + * the log page here. Simulate a completion instead, so that we keep + * all of the cleanup code in the get_log_page_completion() function. + */ + struct spdk_nvme_cpl cpl = { 0 }; + + SPDK_ERRLOG("spdk_nvme_ctrlr_cmd_get_log_page() failed\n"); + cpl.status.sct = SPDK_NVME_SCT_GENERIC; + cpl.status.sc = SPDK_NVME_SC_INTERNAL_DEVICE_ERROR; + cpl.status.dnr = 1; + get_log_page_completion(ctx, &cpl); + return; + } + + offset += size; + remaining -= size; + } +} + +int +spdk_nvme_ctrlr_get_discovery_log_page(struct spdk_nvme_ctrlr *ctrlr, + spdk_nvme_discovery_cb cb_fn, void *cb_arg) +{ + struct nvme_discovery_ctx *ctx; + int rc; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -ENOMEM; + } + + ctx->log_page = calloc(1, sizeof(*ctx->log_page)); + if (ctx->log_page == NULL) { + free(ctx); + return -ENOMEM; + } + + ctx->ctrlr = ctrlr; + ctx->cb_fn = cb_fn; + ctx->cb_arg = cb_arg; + + rc = spdk_nvme_ctrlr_cmd_get_log_page(ctrlr, SPDK_NVME_LOG_DISCOVERY, 0, + ctx->log_page, sizeof(*ctx->log_page), 0, + discovery_log_header_completion, ctx); + if (rc != 0) { + free(ctx->log_page); + free(ctx); + } + + return rc; +} diff --git a/lib/nvme/spdk_nvme.map b/lib/nvme/spdk_nvme.map index 2f9af62f0..f4522aa31 100644 --- a/lib/nvme/spdk_nvme.map +++ b/lib/nvme/spdk_nvme.map @@ -113,6 +113,7 @@ spdk_nvme_ctrlr_free_qid; spdk_nvme_ctrlr_set_remove_cb; spdk_nvme_ctrlr_get_memory_domains; + spdk_nvme_ctrlr_get_discovery_log_page; spdk_nvme_poll_group_create; spdk_nvme_poll_group_add;