1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (C) 2023 Intel Corporation. 3 * All rights reserved. 4 */ 5 6 #include "spdk/stdinc.h" 7 8 #include "spdk/env.h" 9 #include "spdk/nvme.h" 10 #include "spdk/mmio.h" 11 12 #define IO_QUEUE_SIZE 32 13 14 static struct spdk_nvme_transport_id g_trid; 15 static struct spdk_nvme_ctrlr *g_ctrlr; 16 struct spdk_nvme_qpair *g_io_qpair; 17 18 uint32_t g_qpair_id; 19 uint32_t *g_doorbell_base; 20 uint32_t g_doorbell_stride_u32; 21 22 static union spdk_nvme_async_event_completion g_expected_event; 23 static bool g_test_done; 24 25 static struct spdk_nvme_error_information_entry g_error_entries[256]; 26 27 static bool g_exit; 28 29 static void 30 usage(char *program_name) 31 { 32 printf("%s options", program_name); 33 printf("\n"); 34 printf("\t[-r <fmt> Transport ID for PCIe NVMe device]\n"); 35 printf("\t Format: 'key:value [key:value] ...'\n"); 36 printf("\t Keys:\n"); 37 printf("\t trtype Transport type (PCIe)\n"); 38 printf("\t traddr Transport address (e.g. 0000:db:00.0)\n"); 39 printf("\t Example: -r 'trtype:PCIe traddr:0000:db:00.0'\n"); 40 printf("\t"); 41 } 42 43 static int 44 parse_args(int argc, char **argv, struct spdk_env_opts *env_opts) 45 { 46 int op; 47 48 while ((op = getopt(argc, argv, "r:")) != -1) { 49 switch (op) { 50 case 'r': 51 if (spdk_nvme_transport_id_parse(&g_trid, optarg) != 0) { 52 fprintf(stderr, "Invalid transport ID format '%s'\n", optarg); 53 exit(1); 54 } 55 56 if (g_trid.trtype != SPDK_NVME_TRANSPORT_PCIE) { 57 fprintf(stderr, "Invalid transport type, expected PCIe"); 58 exit(1); 59 } 60 61 break; 62 default: 63 usage(argv[0]); 64 return 1; 65 } 66 } 67 68 return 0; 69 } 70 71 static void 72 sig_handler(int signo) 73 { 74 g_exit = true; 75 } 76 77 static void 78 setup_sig_handlers(void) 79 { 80 struct sigaction sigact = {}; 81 int rc; 82 83 sigemptyset(&sigact.sa_mask); 84 sigact.sa_handler = sig_handler; 85 rc = sigaction(SIGINT, &sigact, NULL); 86 if (rc < 0) { 87 fprintf(stderr, "sigaction(SIGINT) failed, errno %d (%s)\n", errno, strerror(errno)); 88 exit(1); 89 } 90 91 rc = sigaction(SIGTERM, &sigact, NULL); 92 if (rc < 0) { 93 fprintf(stderr, "sigaction(SIGTERM) failed, errno %d (%s)\n", errno, strerror(errno)); 94 exit(1); 95 } 96 } 97 98 static void 99 get_error_log_page_completion(void *arg, const struct spdk_nvme_cpl *cpl) 100 { 101 if (spdk_nvme_cpl_is_error(cpl)) { 102 fprintf(stderr, "get error log page failed\n"); 103 exit(1); 104 } 105 106 /* TODO: do handling (print?) of error log page */ 107 printf("Error Informaton Log Page received.\n"); 108 g_test_done = true; 109 } 110 111 static void 112 get_error_log_page(void) 113 { 114 const struct spdk_nvme_ctrlr_data *cdata; 115 116 cdata = spdk_nvme_ctrlr_get_data(g_ctrlr); 117 118 if (spdk_nvme_ctrlr_cmd_get_log_page(g_ctrlr, SPDK_NVME_LOG_ERROR, 119 SPDK_NVME_GLOBAL_NS_TAG, g_error_entries, 120 sizeof(*g_error_entries) * (cdata->elpe + 1), 121 0, 122 get_error_log_page_completion, NULL)) { 123 fprintf(stderr, "spdk_nvme_ctrlr_cmd_get_log_page() failed\n"); 124 exit(1); 125 } 126 } 127 128 static void 129 aer_cb(void *arg, const struct spdk_nvme_cpl *cpl) 130 { 131 union spdk_nvme_async_event_completion event; 132 133 event.raw = cpl->cdw0; 134 135 printf("Asynchronous Event received.\n"); 136 137 if (spdk_nvme_cpl_is_error(cpl)) { 138 fprintf(stderr, "aer failed\n"); 139 exit(1); 140 } 141 142 if (event.bits.async_event_type != g_expected_event.bits.async_event_type) { 143 fprintf(stderr, "unexpected async event type 0x%x\n", event.bits.async_event_type); 144 exit(1); 145 } 146 147 if (event.bits.async_event_info != g_expected_event.bits.async_event_info) { 148 fprintf(stderr, "unexpected async event info 0x%x\n", event.bits.async_event_info); 149 exit(1); 150 } 151 152 if (event.bits.log_page_identifier != g_expected_event.bits.log_page_identifier) { 153 fprintf(stderr, "unexpected async event log page 0x%x\n", event.bits.log_page_identifier); 154 exit(1); 155 } 156 157 get_error_log_page(); 158 } 159 160 static void 161 wait_for_aer_and_log_page_cpl(void) 162 { 163 while (!g_exit && !g_test_done) { 164 spdk_nvme_ctrlr_process_admin_completions(g_ctrlr); 165 } 166 } 167 168 static void 169 create_ctrlr(void) 170 { 171 g_ctrlr = spdk_nvme_connect(&g_trid, NULL, 0); 172 if (g_ctrlr == NULL) { 173 fprintf(stderr, "spdk_nvme_connect() failed for transport address '%s'\n", g_trid.traddr); 174 exit(1); 175 } 176 } 177 178 static void 179 create_io_qpair(void) 180 { 181 struct spdk_nvme_io_qpair_opts opts; 182 183 /* Override io_queue_size here, instead of doing it at connect time with 184 * the ctrlr_opts. This is because stub app could be running, meaning 185 * that ctrlr opts were already set. 186 */ 187 spdk_nvme_ctrlr_get_default_io_qpair_opts(g_ctrlr, &opts, sizeof(opts)); 188 opts.io_queue_size = IO_QUEUE_SIZE; 189 opts.io_queue_requests = IO_QUEUE_SIZE; 190 191 g_io_qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_ctrlr, &opts, sizeof(opts)); 192 if (!g_io_qpair) { 193 fprintf(stderr, "failed to spdk_nvme_ctrlr_alloc_io_qpair()"); 194 exit(1); 195 } 196 197 g_qpair_id = spdk_nvme_qpair_get_id(g_io_qpair); 198 } 199 200 static void 201 set_doorbell_vars(void) 202 { 203 volatile struct spdk_nvme_registers *regs = spdk_nvme_ctrlr_get_registers(g_ctrlr); 204 205 g_doorbell_stride_u32 = 1 << regs->cap.bits.dstrd; 206 g_doorbell_base = (uint32_t *)®s->doorbell[0].sq_tdbl; 207 } 208 209 210 static void 211 pre_test(const char *test_name, enum spdk_nvme_async_event_info_error aec_info) 212 { 213 printf("Executing: %s\n", test_name); 214 215 g_test_done = false; 216 217 g_expected_event.bits.async_event_type = SPDK_NVME_ASYNC_EVENT_TYPE_ERROR; 218 g_expected_event.bits.log_page_identifier = SPDK_NVME_LOG_ERROR; 219 g_expected_event.bits.async_event_info = aec_info; 220 } 221 222 static void 223 post_test(const char *test_name) 224 { 225 printf("Waiting for AER completion...\n"); 226 wait_for_aer_and_log_page_cpl(); 227 printf("%s: %s\n\n", g_test_done ? "Success" : "Failure", test_name); 228 } 229 230 static void 231 test_write_invalid_db(void) 232 { 233 volatile uint32_t *wrong_db; 234 235 pre_test(__func__, SPDK_NVME_ASYNC_EVENT_WRITE_INVALID_DB); 236 237 spdk_wmb(); 238 /* Write to invalid register (note g_qpair_id + 1). */ 239 wrong_db = g_doorbell_base + (2 * (g_qpair_id + 1) + 0) * g_doorbell_stride_u32; 240 spdk_mmio_write_4(wrong_db, 0); 241 242 post_test(__func__); 243 } 244 245 static void 246 test_invalid_db_write_overflow_sq(void) 247 { 248 volatile uint32_t *good_db; 249 250 pre_test(__func__, SPDK_NVME_ASYNC_EVENT_INVALID_DB_WRITE); 251 252 spdk_wmb(); 253 good_db = g_doorbell_base + (2 * g_qpair_id + 0) * g_doorbell_stride_u32; 254 /* Overflow SQ doorbell over queue size. */ 255 spdk_mmio_write_4(good_db, IO_QUEUE_SIZE + 1); 256 257 post_test(__func__); 258 } 259 260 static void 261 test_invalid_db_write_overflow_cq(void) 262 { 263 volatile uint32_t *good_db; 264 265 pre_test(__func__, SPDK_NVME_ASYNC_EVENT_INVALID_DB_WRITE); 266 267 good_db = g_doorbell_base + (2 * g_qpair_id + 1) * g_doorbell_stride_u32; 268 spdk_wmb(); 269 /* Overflow CQ doorbell over queue size. */ 270 spdk_mmio_write_4(good_db, IO_QUEUE_SIZE + 1); 271 272 post_test(__func__); 273 } 274 275 int 276 main(int argc, char **argv) 277 { 278 int rc; 279 struct spdk_env_opts opts; 280 struct spdk_nvme_detach_ctx *detach_ctx = NULL; 281 282 spdk_env_opts_init(&opts); 283 opts.name = "doorbell_aers"; 284 opts.shm_id = 0; 285 286 rc = parse_args(argc, argv, &opts); 287 if (rc != 0) { 288 exit(1); 289 } 290 291 if (spdk_env_init(&opts) < 0) { 292 fprintf(stderr, "Unable to initialize SPDK env\n"); 293 exit(1); 294 } 295 296 setup_sig_handlers(); 297 298 create_ctrlr(); 299 create_io_qpair(); 300 301 set_doorbell_vars(); 302 303 spdk_nvme_ctrlr_register_aer_callback(g_ctrlr, aer_cb, NULL); 304 305 test_write_invalid_db(); 306 test_invalid_db_write_overflow_sq(); 307 test_invalid_db_write_overflow_cq(); 308 309 spdk_nvme_detach_async(g_ctrlr, &detach_ctx); 310 311 if (detach_ctx) { 312 spdk_nvme_detach_poll(detach_ctx); 313 } 314 315 spdk_env_fini(); 316 317 return 0; 318 } 319