xref: /spdk/test/nvme/doorbell_aers/doorbell_aers.c (revision 57fd99b91e71a4baa5543e19ff83958dc99d4dac)
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 Information 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 *)&regs->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 	opts.opts_size = sizeof(opts);
283 	spdk_env_opts_init(&opts);
284 	opts.name = "doorbell_aers";
285 	opts.shm_id = 0;
286 
287 	rc = parse_args(argc, argv, &opts);
288 	if (rc != 0) {
289 		exit(1);
290 	}
291 
292 	if (spdk_env_init(&opts) < 0) {
293 		fprintf(stderr, "Unable to initialize SPDK env\n");
294 		exit(1);
295 	}
296 
297 	setup_sig_handlers();
298 
299 	create_ctrlr();
300 	create_io_qpair();
301 
302 	set_doorbell_vars();
303 
304 	spdk_nvme_ctrlr_register_aer_callback(g_ctrlr, aer_cb, NULL);
305 
306 	test_write_invalid_db();
307 	test_invalid_db_write_overflow_sq();
308 	test_invalid_db_write_overflow_cq();
309 
310 	spdk_nvme_detach_async(g_ctrlr, &detach_ctx);
311 
312 	if (detach_ctx) {
313 		spdk_nvme_detach_poll(detach_ctx);
314 	}
315 
316 	spdk_env_fini();
317 
318 	return 0;
319 }
320