From fb058fe576158366505d02444fc64155ee3e1c31 Mon Sep 17 00:00:00 2001 From: "Szulik, Maciej" Date: Wed, 29 Mar 2023 12:46:45 +0200 Subject: [PATCH] test/nvme: add doorbell_aers test app This application can be used to test AER flows related to doorbell errors. Signed-off-by: Szulik, Maciej Change-Id: Idc04d326f08f8e04455c77ab8265cc601485afbe Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/17401 Tested-by: SPDK CI Jenkins Reviewed-by: Jim Harris Reviewed-by: Aleksey Marchuk --- test/nvme/Makefile | 2 +- test/nvme/doorbell_aers/.gitignore | 1 + test/nvme/doorbell_aers/Makefile | 10 + test/nvme/doorbell_aers/doorbell_aers.c | 318 ++++++++++++++++++++++++ test/nvme/nvme.sh | 7 + 5 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 test/nvme/doorbell_aers/.gitignore create mode 100644 test/nvme/doorbell_aers/Makefile create mode 100644 test/nvme/doorbell_aers/doorbell_aers.c diff --git a/test/nvme/Makefile b/test/nvme/Makefile index bd8093686..64997b9d0 100644 --- a/test/nvme/Makefile +++ b/test/nvme/Makefile @@ -8,7 +8,7 @@ include $(SPDK_ROOT_DIR)/mk/spdk.common.mk DIRS-y = aer reset sgl e2edp overhead err_injection \ startup reserve simple_copy connect_stress boot_partition \ - compliance fused_ordering + compliance fused_ordering doorbell_aers DIRS-$(CONFIG_NVME_CUSE) += cuse .PHONY: all clean $(DIRS-y) diff --git a/test/nvme/doorbell_aers/.gitignore b/test/nvme/doorbell_aers/.gitignore new file mode 100644 index 000000000..d78476a77 --- /dev/null +++ b/test/nvme/doorbell_aers/.gitignore @@ -0,0 +1 @@ +doorbell_aers diff --git a/test/nvme/doorbell_aers/Makefile b/test/nvme/doorbell_aers/Makefile new file mode 100644 index 000000000..fb005b47d --- /dev/null +++ b/test/nvme/doorbell_aers/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2023 Intel Corporation. +# All rights reserved. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) + +APP = doorbell_aers + +include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk diff --git a/test/nvme/doorbell_aers/doorbell_aers.c b/test/nvme/doorbell_aers/doorbell_aers.c new file mode 100644 index 000000000..cb84bdab8 --- /dev/null +++ b/test/nvme/doorbell_aers/doorbell_aers.c @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (C) 2023 Intel Corporation. + * All rights reserved. + */ + +#include "spdk/stdinc.h" + +#include "spdk/env.h" +#include "spdk/nvme.h" +#include "spdk/mmio.h" + +#define IO_QUEUE_SIZE 32 + +static struct spdk_nvme_transport_id g_trid; +static struct spdk_nvme_ctrlr *g_ctrlr; +struct spdk_nvme_qpair *g_io_qpair; + +uint32_t g_qpair_id; +uint32_t *g_doorbell_base; +uint32_t g_doorbell_stride_u32; + +static union spdk_nvme_async_event_completion g_expected_event; +static bool g_test_done; + +static struct spdk_nvme_error_information_entry g_error_entries[256]; + +static bool g_exit; + +static void +usage(char *program_name) +{ + printf("%s options", program_name); + printf("\n"); + printf("\t[-r Transport ID for PCIe NVMe device]\n"); + printf("\t Format: 'key:value [key:value] ...'\n"); + printf("\t Keys:\n"); + printf("\t trtype Transport type (PCIe)\n"); + printf("\t traddr Transport address (e.g. 0000:db:00.0)\n"); + printf("\t Example: -r 'trtype:PCIe traddr:0000:db:00.0'\n"); + printf("\t"); +} + +static int +parse_args(int argc, char **argv, struct spdk_env_opts *env_opts) +{ + int op; + + while ((op = getopt(argc, argv, "r:")) != -1) { + switch (op) { + case 'r': + if (spdk_nvme_transport_id_parse(&g_trid, optarg) != 0) { + fprintf(stderr, "Invalid transport ID format '%s'\n", optarg); + exit(1); + } + + if (g_trid.trtype != SPDK_NVME_TRANSPORT_PCIE) { + fprintf(stderr, "Invalid transport type, expected PCIe"); + exit(1); + } + + break; + default: + usage(argv[0]); + return 1; + } + } + + return 0; +} + +static void +sig_handler(int signo) +{ + g_exit = true; +} + +static void +setup_sig_handlers(void) +{ + struct sigaction sigact = {}; + int rc; + + sigemptyset(&sigact.sa_mask); + sigact.sa_handler = sig_handler; + rc = sigaction(SIGINT, &sigact, NULL); + if (rc < 0) { + fprintf(stderr, "sigaction(SIGINT) failed, errno %d (%s)\n", errno, strerror(errno)); + exit(1); + } + + rc = sigaction(SIGTERM, &sigact, NULL); + if (rc < 0) { + fprintf(stderr, "sigaction(SIGTERM) failed, errno %d (%s)\n", errno, strerror(errno)); + exit(1); + } +} + +static void +get_error_log_page_completion(void *arg, const struct spdk_nvme_cpl *cpl) +{ + if (spdk_nvme_cpl_is_error(cpl)) { + fprintf(stderr, "get error log page failed\n"); + exit(1); + } + + /* TODO: do handling (print?) of error log page */ + printf("Error Informaton Log Page received.\n"); + g_test_done = true; +} + +static void +get_error_log_page(void) +{ + const struct spdk_nvme_ctrlr_data *cdata; + + cdata = spdk_nvme_ctrlr_get_data(g_ctrlr); + + if (spdk_nvme_ctrlr_cmd_get_log_page(g_ctrlr, SPDK_NVME_LOG_ERROR, + SPDK_NVME_GLOBAL_NS_TAG, g_error_entries, + sizeof(*g_error_entries) * (cdata->elpe + 1), + 0, + get_error_log_page_completion, NULL)) { + fprintf(stderr, "spdk_nvme_ctrlr_cmd_get_log_page() failed\n"); + exit(1); + } +} + +static void +aer_cb(void *arg, const struct spdk_nvme_cpl *cpl) +{ + union spdk_nvme_async_event_completion event; + + event.raw = cpl->cdw0; + + printf("Asynchronous Event received.\n"); + + if (spdk_nvme_cpl_is_error(cpl)) { + fprintf(stderr, "aer failed\n"); + exit(1); + } + + if (event.bits.async_event_type != g_expected_event.bits.async_event_type) { + fprintf(stderr, "unexpected async event type 0x%x\n", event.bits.async_event_type); + exit(1); + } + + if (event.bits.async_event_info != g_expected_event.bits.async_event_info) { + fprintf(stderr, "unexpected async event info 0x%x\n", event.bits.async_event_info); + exit(1); + } + + if (event.bits.log_page_identifier != g_expected_event.bits.log_page_identifier) { + fprintf(stderr, "unexpected async event log page 0x%x\n", event.bits.log_page_identifier); + exit(1); + } + + get_error_log_page(); +} + +static void +wait_for_aer_and_log_page_cpl(void) +{ + while (!g_exit && !g_test_done) { + spdk_nvme_ctrlr_process_admin_completions(g_ctrlr); + } +} + +static void +create_ctrlr(void) +{ + g_ctrlr = spdk_nvme_connect(&g_trid, NULL, 0); + if (g_ctrlr == NULL) { + fprintf(stderr, "spdk_nvme_connect() failed for transport address '%s'\n", g_trid.traddr); + exit(1); + } +} + +static void +create_io_qpair(void) +{ + struct spdk_nvme_io_qpair_opts opts; + + /* Override io_queue_size here, instead of doing it at connect time with + * the ctrlr_opts. This is because stub app could be running, meaning + * that ctrlr opts were already set. + */ + spdk_nvme_ctrlr_get_default_io_qpair_opts(g_ctrlr, &opts, sizeof(opts)); + opts.io_queue_size = IO_QUEUE_SIZE; + opts.io_queue_requests = IO_QUEUE_SIZE; + + g_io_qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_ctrlr, &opts, sizeof(opts)); + if (!g_io_qpair) { + fprintf(stderr, "failed to spdk_nvme_ctrlr_alloc_io_qpair()"); + exit(1); + } + + g_qpair_id = spdk_nvme_qpair_get_id(g_io_qpair); +} + +static void +set_doorbell_vars(void) +{ + volatile struct spdk_nvme_registers *regs = spdk_nvme_ctrlr_get_registers(g_ctrlr); + + g_doorbell_stride_u32 = 1 << regs->cap.bits.dstrd; + g_doorbell_base = (uint32_t *)®s->doorbell[0].sq_tdbl; +} + + +static void +pre_test(const char *test_name, enum spdk_nvme_async_event_info_error aec_info) +{ + printf("Executing: %s\n", test_name); + + g_test_done = false; + + g_expected_event.bits.async_event_type = SPDK_NVME_ASYNC_EVENT_TYPE_ERROR; + g_expected_event.bits.log_page_identifier = SPDK_NVME_LOG_ERROR; + g_expected_event.bits.async_event_info = aec_info; +} + +static void +post_test(const char *test_name) +{ + printf("Waiting for AER completion...\n"); + wait_for_aer_and_log_page_cpl(); + printf("%s: %s\n\n", g_test_done ? "Success" : "Failure", test_name); +} + +static void +test_write_invalid_db(void) +{ + volatile uint32_t *wrong_db; + + pre_test(__func__, SPDK_NVME_ASYNC_EVENT_WRITE_INVALID_DB); + + spdk_wmb(); + /* Write to invalid register (note g_qpair_id + 1). */ + wrong_db = g_doorbell_base + (2 * (g_qpair_id + 1) + 0) * g_doorbell_stride_u32; + spdk_mmio_write_4(wrong_db, 0); + + post_test(__func__); +} + +static void +test_invalid_db_write_overflow_sq(void) +{ + volatile uint32_t *good_db; + + pre_test(__func__, SPDK_NVME_ASYNC_EVENT_INVALID_DB_WRITE); + + spdk_wmb(); + good_db = g_doorbell_base + (2 * g_qpair_id + 0) * g_doorbell_stride_u32; + /* Overflow SQ doorbell over queue size. */ + spdk_mmio_write_4(good_db, IO_QUEUE_SIZE + 1); + + post_test(__func__); +} + +static void +test_invalid_db_write_overflow_cq(void) +{ + volatile uint32_t *good_db; + + pre_test(__func__, SPDK_NVME_ASYNC_EVENT_INVALID_DB_WRITE); + + good_db = g_doorbell_base + (2 * g_qpair_id + 1) * g_doorbell_stride_u32; + spdk_wmb(); + /* Overflow CQ doorbell over queue size. */ + spdk_mmio_write_4(good_db, IO_QUEUE_SIZE + 1); + + post_test(__func__); +} + +int +main(int argc, char **argv) +{ + int rc; + struct spdk_env_opts opts; + struct spdk_nvme_detach_ctx *detach_ctx = NULL; + + spdk_env_opts_init(&opts); + opts.name = "doorbell_aers"; + opts.shm_id = 0; + + rc = parse_args(argc, argv, &opts); + if (rc != 0) { + exit(1); + } + + if (spdk_env_init(&opts) < 0) { + fprintf(stderr, "Unable to initialize SPDK env\n"); + exit(1); + } + + setup_sig_handlers(); + + create_ctrlr(); + create_io_qpair(); + + set_doorbell_vars(); + + spdk_nvme_ctrlr_register_aer_callback(g_ctrlr, aer_cb, NULL); + + test_write_invalid_db(); + test_invalid_db_write_overflow_sq(); + test_invalid_db_write_overflow_cq(); + + spdk_nvme_detach_async(g_ctrlr, &detach_ctx); + + if (detach_ctx) { + spdk_nvme_detach_poll(detach_ctx); + } + + spdk_env_fini(); + + return 0; +} diff --git a/test/nvme/nvme.sh b/test/nvme/nvme.sh index f98415092..4dbd21e00 100755 --- a/test/nvme/nvme.sh +++ b/test/nvme/nvme.sh @@ -64,6 +64,12 @@ function nvme_multi_secondary() { wait $pid1 } +function nvme_doorbell_aers() { + for bdf in $(get_nvme_bdfs); do + timeout --preserve-status 10 $testdir/doorbell_aers/doorbell_aers -r "trtype:PCIe traddr:${bdf}" + done +} + if [ $(uname) = Linux ]; then # check that our setup.sh script does not bind NVMe devices to uio/vfio if they # have an active mountpoint @@ -137,6 +143,7 @@ run_test "nvme_err_injection" $testdir/err_injection/err_injection run_test "nvme_overhead" $testdir/overhead/overhead -o 4096 -t 1 -H -i 0 run_test "nvme_arbitration" $SPDK_EXAMPLE_DIR/arbitration -t 3 -i 0 run_test "nvme_single_aen" $testdir/aer/aer -T -i 0 -L log +run_test "nvme_doorbell_aers" nvme_doorbell_aers if [ $(uname) != "FreeBSD" ]; then run_test "nvme_multi_aen" $testdir/aer/aer -m -T -i 0 -L log