xref: /freebsd-src/sbin/nvmecontrol/fabrics.c (revision 8bba2c0f8958443790b1f3abc0675719da987e87)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2023-2024 Chelsio Communications, Inc.
5  * Written by: John Baldwin <jhb@FreeBSD.org>
6  */
7 
8 #include <sys/socket.h>
9 #include <netinet/in.h>
10 #include <arpa/inet.h>
11 #include <err.h>
12 #include <libnvmf.h>
13 #include <netdb.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sysexits.h>
18 #include <unistd.h>
19 
20 #include "fabrics.h"
21 
22 /*
23  * Subroutines shared by several Fabrics commands.
24  */
25 static char nqn[NVMF_NQN_MAX_LEN];
26 static uint8_t hostid[16];
27 static bool hostid_initted = false;
28 
29 static bool
30 init_hostid(void)
31 {
32 	int error;
33 
34 	if (hostid_initted)
35 		return (true);
36 
37 	error = nvmf_hostid_from_hostuuid(hostid);
38 	if (error != 0) {
39 		warnc(error, "Failed to generate hostid");
40 		return (false);
41 	}
42 	error = nvmf_nqn_from_hostuuid(nqn);
43 	if (error != 0) {
44 		warnc(error, "Failed to generate host NQN");
45 		return (false);
46 	}
47 
48 	hostid_initted = true;
49 	return (true);
50 }
51 
52 const char *
53 nvmf_default_hostnqn(void)
54 {
55 	if (!init_hostid())
56 		exit(EX_IOERR);
57 	return (nqn);
58 }
59 
60 void
61 nvmf_parse_address(const char *in_address, const char **address,
62     const char **port, char **tofree)
63 {
64 	char *cp;
65 
66 	/*
67 	 * Accepts the following address formats:
68 	 *
69 	 * [IPv6 address]:port
70 	 * IPv4 address:port
71 	 * hostname:port
72 	 * [IPv6 address]
73 	 * IPv6 address
74 	 * IPv4 address
75 	 * hostname
76 	 */
77 	if (in_address[0] == '[') {
78 		/* IPv6 address in square brackets. */
79 		cp = strchr(in_address + 1, ']');
80 		if (cp == NULL || cp == in_address + 1)
81 			errx(EX_USAGE, "Invalid address %s", in_address);
82 		*tofree = strndup(in_address + 1, cp - (in_address + 1));
83 		*address = *tofree;
84 
85 		/* Skip over ']' */
86 		cp++;
87 		switch (*cp) {
88 		case '\0':
89 			*port = NULL;
90 			return;
91 		case ':':
92 			if (cp[1] != '\0') {
93 				*port = cp + 1;
94 				return;
95 			}
96 			/* FALLTHROUGH */
97 		default:
98 			errx(EX_USAGE, "Invalid address %s", in_address);
99 		}
100 	}
101 
102 	/* Look for the first colon. */
103 	cp = strchr(in_address, ':');
104 	if (cp == NULL) {
105 		*address = in_address;
106 		*port = NULL;
107 		*tofree = NULL;
108 		return;
109 	}
110 
111 	/* If there is another colon, assume this is an IPv6 address. */
112 	if (strchr(cp + 1, ':') != NULL) {
113 		*address = in_address;
114 		*port = NULL;
115 		*tofree = NULL;
116 		return;
117 	}
118 
119 	/* Both strings on either side of the colon must be non-empty. */
120 	if (cp == in_address || cp[1] == '\0')
121 		errx(EX_USAGE, "Invalid address %s", in_address);
122 
123 	*tofree = strndup(in_address, cp - in_address);
124 	*address = *tofree;
125 
126 	/* Skip over ':' */
127 	*port = cp + 1;
128 }
129 
130 uint16_t
131 nvmf_parse_cntlid(const char *cntlid)
132 {
133 	u_long value;
134 
135 	if (strcasecmp(cntlid, "dynamic") == 0)
136 		return (NVMF_CNTLID_DYNAMIC);
137 	else if (strcasecmp(cntlid, "static") == 0)
138 		return (NVMF_CNTLID_STATIC_ANY);
139 	else {
140 		value = strtoul(cntlid, NULL, 0);
141 
142 		if (value > NVMF_CNTLID_STATIC_MAX)
143 			errx(EX_USAGE, "Invalid controller ID");
144 
145 		return (value);
146 	}
147 }
148 
149 static bool
150 tcp_qpair_params(struct nvmf_qpair_params *params, int adrfam,
151     const char *address, const char *port)
152 {
153 	struct addrinfo hints, *ai, *list;
154 	int error, s;
155 
156 	memset(&hints, 0, sizeof(hints));
157 	hints.ai_family = adrfam;
158 	hints.ai_protocol = IPPROTO_TCP;
159 	error = getaddrinfo(address, port, &hints, &list);
160 	if (error != 0) {
161 		warnx("%s", gai_strerror(error));
162 		return (false);
163 	}
164 
165 	for (ai = list; ai != NULL; ai = ai->ai_next) {
166 		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
167 		if (s == -1)
168 			continue;
169 
170 		if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) {
171 			close(s);
172 			continue;
173 		}
174 
175 		params->tcp.fd = s;
176 		freeaddrinfo(list);
177 		return (true);
178 	}
179 	warn("Failed to connect to controller at %s:%s", address, port);
180 	freeaddrinfo(list);
181 	return (false);
182 }
183 
184 static void
185 tcp_discovery_association_params(struct nvmf_association_params *params)
186 {
187 	params->tcp.pda = 0;
188 	params->tcp.header_digests = false;
189 	params->tcp.data_digests = false;
190 	params->tcp.maxr2t = 1;
191 }
192 
193 struct nvmf_qpair *
194 connect_discovery_adminq(enum nvmf_trtype trtype, const char *address,
195     const char *port, const char *hostnqn)
196 {
197 	struct nvmf_association_params aparams;
198 	struct nvmf_qpair_params qparams;
199 	struct nvmf_association *na;
200 	struct nvmf_qpair *qp;
201 	uint64_t cap, cc, csts;
202 	int error, timo;
203 
204 	memset(&aparams, 0, sizeof(aparams));
205 	aparams.sq_flow_control = false;
206 	switch (trtype) {
207 	case NVMF_TRTYPE_TCP:
208 		/* 7.4.9.3 Default port for discovery */
209 		if (port == NULL)
210 			port = "8009";
211 		tcp_discovery_association_params(&aparams);
212 		break;
213 	default:
214 		errx(EX_UNAVAILABLE, "Unsupported transport %s",
215 		    nvmf_transport_type(trtype));
216 	}
217 
218 	if (!init_hostid())
219 		exit(EX_IOERR);
220 	if (hostnqn != NULL) {
221 		if (!nvmf_nqn_valid(hostnqn))
222 			errx(EX_USAGE, "Invalid HostNQN %s", hostnqn);
223 	} else
224 		hostnqn = nqn;
225 
226 	na = nvmf_allocate_association(trtype, false, &aparams);
227 	if (na == NULL)
228 		err(EX_IOERR, "Failed to create discovery association");
229 	memset(&qparams, 0, sizeof(qparams));
230 	qparams.admin = true;
231 	if (!tcp_qpair_params(&qparams, AF_UNSPEC, address, port))
232 		exit(EX_NOHOST);
233 	qp = nvmf_connect(na, &qparams, 0, NVME_MIN_ADMIN_ENTRIES, hostid,
234 	    NVMF_CNTLID_DYNAMIC, NVMF_DISCOVERY_NQN, hostnqn, 0);
235 	if (qp == NULL)
236 		errx(EX_IOERR, "Failed to connect to discovery controller: %s",
237 		    nvmf_association_error(na));
238 	nvmf_free_association(na);
239 
240 	/* Fetch Controller Capabilities Property */
241 	error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
242 	if (error != 0)
243 		errc(EX_IOERR, error, "Failed to fetch CAP");
244 
245 	/* Set Controller Configuration Property (CC.EN=1) */
246 	error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
247 	if (error != 0)
248 		errc(EX_IOERR, error, "Failed to fetch CC");
249 
250 	/* Clear known fields preserving any reserved fields. */
251 	cc &= ~(NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
252 	    NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
253 
254 	/* Leave AMS, MPS, and CSS as 0. */
255 
256 	cc |= NVMEF(NVME_CC_REG_EN, 1);
257 
258 	error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
259 	if (error != 0)
260 		errc(EX_IOERR, error, "Failed to set CC");
261 
262 	/* Wait for CSTS.RDY in Controller Status */
263 	timo = NVME_CAP_LO_TO(cap);
264 	for (;;) {
265 		error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
266 		if (error != 0)
267 			errc(EX_IOERR, error, "Failed to fetch CSTS");
268 
269 		if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
270 			break;
271 
272 		if (timo == 0)
273 			errx(EX_IOERR, "Controller failed to become ready");
274 		timo--;
275 		usleep(500 * 1000);
276 	}
277 
278 	return (qp);
279 }
280 
281 /*
282  * XXX: Should this accept the admin queue size as a parameter rather
283  * than always using NVMF_MIN_ADMIN_MAX_SQ_SIZE?
284  */
285 static int
286 connect_nvm_adminq(struct nvmf_association *na,
287     const struct nvmf_qpair_params *params, struct nvmf_qpair **qpp,
288     uint16_t cntlid, const char *subnqn, const char *hostnqn, uint32_t kato,
289     uint16_t *mqes)
290 {
291 	struct nvmf_qpair *qp;
292 	uint64_t cap, cc, csts;
293 	u_int mps, mpsmin, mpsmax;
294 	int error, timo;
295 
296 	qp = nvmf_connect(na, params, 0, NVMF_MIN_ADMIN_MAX_SQ_SIZE, hostid,
297 	    cntlid, subnqn, hostnqn, kato);
298 	if (qp == NULL) {
299 		warnx("Failed to connect to NVM controller %s: %s", subnqn,
300 		    nvmf_association_error(na));
301 		return (EX_IOERR);
302 	}
303 
304 	/* Fetch Controller Capabilities Property */
305 	error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
306 	if (error != 0) {
307 		warnc(error, "Failed to fetch CAP");
308 		nvmf_free_qpair(qp);
309 		return (EX_IOERR);
310 	}
311 
312 	/* Require the NVM command set. */
313 	if (NVME_CAP_HI_CSS_NVM(cap >> 32) == 0) {
314 		warnx("Controller %s does not support the NVM command set",
315 		    subnqn);
316 		nvmf_free_qpair(qp);
317 		return (EX_UNAVAILABLE);
318 	}
319 
320 	*mqes = NVME_CAP_LO_MQES(cap);
321 
322 	/* Prefer native host page size if it fits. */
323 	mpsmin = NVMEV(NVME_CAP_HI_REG_MPSMIN, cap >> 32);
324 	mpsmax = NVMEV(NVME_CAP_HI_REG_MPSMAX, cap >> 32);
325 	mps = ffs(getpagesize()) - 1;
326 	if (mps < mpsmin + NVME_MPS_SHIFT)
327 		mps = mpsmin;
328 	else if (mps > mpsmax + NVME_MPS_SHIFT)
329 		mps = mpsmax;
330 	else
331 		mps -= NVME_MPS_SHIFT;
332 
333 	/* Configure controller. */
334 	error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
335 	if (error != 0) {
336 		warnc(error, "Failed to fetch CC");
337 		nvmf_free_qpair(qp);
338 		return (EX_IOERR);
339 	}
340 
341 	/* Clear known fields preserving any reserved fields. */
342 	cc &= ~(NVMEM(NVME_CC_REG_IOCQES) | NVMEM(NVME_CC_REG_IOSQES) |
343 	    NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
344 	    NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
345 
346 	cc |= NVMEF(NVME_CC_REG_IOCQES, 4);	/* CQE entry size == 16 */
347 	cc |= NVMEF(NVME_CC_REG_IOSQES, 6);	/* SEQ entry size == 64 */
348 	cc |= NVMEF(NVME_CC_REG_AMS, 0);	/* AMS 0 (Round-robin) */
349 	cc |= NVMEF(NVME_CC_REG_MPS, mps);
350 	cc |= NVMEF(NVME_CC_REG_CSS, 0);	/* NVM command set */
351 	cc |= NVMEF(NVME_CC_REG_EN, 1);		/* EN = 1 */
352 
353 	error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
354 	if (error != 0) {
355 		warnc(error, "Failed to set CC");
356 		nvmf_free_qpair(qp);
357 		return (EX_IOERR);
358 	}
359 
360 	/* Wait for CSTS.RDY in Controller Status */
361 	timo = NVME_CAP_LO_TO(cap);
362 	for (;;) {
363 		error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
364 		if (error != 0) {
365 			warnc(error, "Failed to fetch CSTS");
366 			nvmf_free_qpair(qp);
367 			return (EX_IOERR);
368 		}
369 
370 		if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
371 			break;
372 
373 		if (timo == 0) {
374 			warnx("Controller failed to become ready");
375 			nvmf_free_qpair(qp);
376 			return (EX_IOERR);
377 		}
378 		timo--;
379 		usleep(500 * 1000);
380 	}
381 
382 	*qpp = qp;
383 	return (0);
384 }
385 
386 static void
387 shutdown_controller(struct nvmf_qpair *qp)
388 {
389 	uint64_t cc;
390 	int error;
391 
392 	error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
393 	if (error != 0) {
394 		warnc(error, "Failed to fetch CC");
395 		goto out;
396 	}
397 
398 	cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_NORMAL);
399 
400 	error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
401 	if (error != 0) {
402 		warnc(error, "Failed to set CC to trigger shutdown");
403 		goto out;
404 	}
405 
406 out:
407 	nvmf_free_qpair(qp);
408 }
409 
410 /* Returns a value from <sysexits.h> */
411 int
412 connect_nvm_queues(const struct nvmf_association_params *aparams,
413     enum nvmf_trtype trtype, int adrfam, const char *address,
414     const char *port, uint16_t cntlid, const char *subnqn, const char *hostnqn,
415     uint32_t kato, struct nvmf_qpair **admin, struct nvmf_qpair **io,
416     u_int num_io_queues, u_int queue_size, struct nvme_controller_data *cdata)
417 {
418 	struct nvmf_qpair_params qparams;
419 	struct nvmf_association *na;
420 	u_int queues;
421 	int error;
422 	uint16_t mqes;
423 
424 	switch (trtype) {
425 	case NVMF_TRTYPE_TCP:
426 		break;
427 	default:
428 		warnx("Unsupported transport %s", nvmf_transport_type(trtype));
429 		return (EX_UNAVAILABLE);
430 	}
431 
432 	if (!init_hostid())
433 		return (EX_IOERR);
434 	if (hostnqn == NULL || !nvmf_nqn_valid(hostnqn)) {
435 		warnx("Invalid HostNQN %s", hostnqn);
436 		return (EX_USAGE);
437 	}
438 
439 	/* Association. */
440 	na = nvmf_allocate_association(trtype, false, aparams);
441 	if (na == NULL) {
442 		warn("Failed to create association for %s", subnqn);
443 		return (EX_IOERR);
444 	}
445 
446 	/* Admin queue. */
447 	memset(&qparams, 0, sizeof(qparams));
448 	qparams.admin = true;
449 	if (!tcp_qpair_params(&qparams, adrfam, address, port)) {
450 		nvmf_free_association(na);
451 		return (EX_NOHOST);
452 	}
453 	error = connect_nvm_adminq(na, &qparams, admin, cntlid, subnqn, hostnqn,
454 	    kato, &mqes);
455 	if (error != 0) {
456 		nvmf_free_association(na);
457 		return (error);
458 	}
459 
460 	/* Validate I/O queue size. */
461 	if (queue_size == 0)
462 		queue_size = (u_int)mqes + 1;
463 	else if (queue_size > (u_int)mqes + 1) {
464 		shutdown_controller(*admin);
465 		nvmf_free_association(na);
466 		warnx("I/O queue size exceeds controller maximum (%u)",
467 		    mqes + 1);
468 		return (EX_USAGE);
469 	}
470 
471 	/* Fetch controller data. */
472 	error = nvmf_host_identify_controller(*admin, cdata);
473 	if (error != 0) {
474 		shutdown_controller(*admin);
475 		nvmf_free_association(na);
476 		warnc(error, "Failed to fetch controller data for %s", subnqn);
477 		return (EX_IOERR);
478 	}
479 
480 	nvmf_update_assocation(na, cdata);
481 
482 	error = nvmf_host_request_queues(*admin, num_io_queues, &queues);
483 	if (error != 0) {
484 		shutdown_controller(*admin);
485 		nvmf_free_association(na);
486 		warnc(error, "Failed to request I/O queues");
487 		return (EX_IOERR);
488 	}
489 	if (queues < num_io_queues) {
490 		shutdown_controller(*admin);
491 		nvmf_free_association(na);
492 		warnx("Controller enabled fewer I/O queues (%u) than requested (%u)",
493 		    queues, num_io_queues);
494 		return (EX_PROTOCOL);
495 	}
496 
497 	/* I/O queues. */
498 	memset(io, 0, sizeof(*io) * num_io_queues);
499 	for (u_int i = 0; i < num_io_queues; i++) {
500 		memset(&qparams, 0, sizeof(qparams));
501 		qparams.admin = false;
502 		if (!tcp_qpair_params(&qparams, adrfam, address, port)) {
503 			error = EX_NOHOST;
504 			goto out;
505 		}
506 		io[i] = nvmf_connect(na, &qparams, i + 1, queue_size, hostid,
507 		    nvmf_cntlid(*admin), subnqn, hostnqn, 0);
508 		if (io[i] == NULL) {
509 			warnx("Failed to create I/O queue: %s",
510 			    nvmf_association_error(na));
511 			error = EX_IOERR;
512 			goto out;
513 		}
514 	}
515 	nvmf_free_association(na);
516 	return (0);
517 
518 out:
519 	disconnect_nvm_queues(*admin, io, num_io_queues);
520 	nvmf_free_association(na);
521 	return (error);
522 }
523 
524 void
525 disconnect_nvm_queues(struct nvmf_qpair *admin, struct nvmf_qpair **io,
526     u_int num_io_queues)
527 {
528 	for (u_int i = 0; i < num_io_queues; i++) {
529 		if (io[i] == NULL)
530 			break;
531 		nvmf_free_qpair(io[i]);
532 	}
533 	shutdown_controller(admin);
534 }
535