xref: /freebsd/sbin/nvmecontrol/fabrics.c (revision ec282601f720c1ebda50d9c7065d1b629f38f69f)
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
init_hostid(void)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 *
nvmf_default_hostnqn(void)53 nvmf_default_hostnqn(void)
54 {
55 	if (!init_hostid())
56 		exit(EX_IOERR);
57 	return (nqn);
58 }
59 
60 void
nvmf_parse_address(const char * in_address,const char ** address,const char ** port,char ** tofree)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
nvmf_parse_cntlid(const char * cntlid)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
tcp_qpair_params(struct nvmf_qpair_params * params,int adrfam,const char * address,const char * port,struct addrinfo ** aip,struct addrinfo ** listp)150 tcp_qpair_params(struct nvmf_qpair_params *params, int adrfam,
151     const char *address, const char *port, struct addrinfo **aip,
152     struct addrinfo **listp)
153 {
154 	struct addrinfo hints, *ai, *list;
155 	int error, s;
156 
157 	memset(&hints, 0, sizeof(hints));
158 	hints.ai_family = adrfam;
159 	hints.ai_protocol = IPPROTO_TCP;
160 	error = getaddrinfo(address, port, &hints, &list);
161 	if (error != 0) {
162 		warnx("%s", gai_strerror(error));
163 		return (false);
164 	}
165 
166 	for (ai = list; ai != NULL; ai = ai->ai_next) {
167 		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
168 		if (s == -1)
169 			continue;
170 
171 		if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) {
172 			close(s);
173 			continue;
174 		}
175 
176 		params->tcp.fd = s;
177 		if (listp != NULL) {
178 			*aip = ai;
179 			*listp = list;
180 		} else
181 			freeaddrinfo(list);
182 		return (true);
183 	}
184 	warn("Failed to connect to controller at %s:%s", address, port);
185 	freeaddrinfo(list);
186 	return (false);
187 }
188 
189 static bool
tcp_qpair_params_ai(struct nvmf_qpair_params * params,struct addrinfo * ai)190 tcp_qpair_params_ai(struct nvmf_qpair_params *params, struct addrinfo *ai)
191 {
192 	int s;
193 
194 	s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
195 	if (s == -1)
196 		return (false);
197 
198 	if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) {
199 		close(s);
200 		return (false);
201 	}
202 
203 	params->tcp.fd = s;
204 	return (true);
205 }
206 
207 static void
tcp_discovery_association_params(struct nvmf_association_params * params)208 tcp_discovery_association_params(struct nvmf_association_params *params)
209 {
210 	params->tcp.pda = 0;
211 	params->tcp.header_digests = false;
212 	params->tcp.data_digests = false;
213 	params->tcp.maxr2t = 1;
214 }
215 
216 struct nvmf_qpair *
connect_discovery_adminq(enum nvmf_trtype trtype,const char * address,const char * port,const char * hostnqn)217 connect_discovery_adminq(enum nvmf_trtype trtype, const char *address,
218     const char *port, const char *hostnqn)
219 {
220 	struct nvmf_association_params aparams;
221 	struct nvmf_qpair_params qparams;
222 	struct nvmf_association *na;
223 	struct nvmf_qpair *qp;
224 	uint64_t cap, cc, csts;
225 	int error, timo;
226 
227 	memset(&aparams, 0, sizeof(aparams));
228 	aparams.sq_flow_control = false;
229 	switch (trtype) {
230 	case NVMF_TRTYPE_TCP:
231 		/* 7.4.9.3 Default port for discovery */
232 		if (port == NULL)
233 			port = "8009";
234 		tcp_discovery_association_params(&aparams);
235 		break;
236 	default:
237 		errx(EX_UNAVAILABLE, "Unsupported transport %s",
238 		    nvmf_transport_type(trtype));
239 	}
240 
241 	if (!init_hostid())
242 		exit(EX_IOERR);
243 	if (hostnqn != NULL) {
244 		if (!nvmf_nqn_valid(hostnqn))
245 			errx(EX_USAGE, "Invalid HostNQN %s", hostnqn);
246 	} else
247 		hostnqn = nqn;
248 
249 	na = nvmf_allocate_association(trtype, false, &aparams);
250 	if (na == NULL)
251 		err(EX_IOERR, "Failed to create discovery association");
252 	memset(&qparams, 0, sizeof(qparams));
253 	qparams.admin = true;
254 	if (!tcp_qpair_params(&qparams, AF_UNSPEC, address, port, NULL, NULL))
255 		exit(EX_NOHOST);
256 	qp = nvmf_connect(na, &qparams, 0, NVME_MIN_ADMIN_ENTRIES, hostid,
257 	    NVMF_CNTLID_DYNAMIC, NVMF_DISCOVERY_NQN, hostnqn, 0);
258 	if (qp == NULL)
259 		errx(EX_IOERR, "Failed to connect to discovery controller: %s",
260 		    nvmf_association_error(na));
261 	nvmf_free_association(na);
262 
263 	/* Fetch Controller Capabilities Property */
264 	error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
265 	if (error != 0)
266 		errc(EX_IOERR, error, "Failed to fetch CAP");
267 
268 	/* Set Controller Configuration Property (CC.EN=1) */
269 	error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
270 	if (error != 0)
271 		errc(EX_IOERR, error, "Failed to fetch CC");
272 
273 	/* Clear known fields preserving any reserved fields. */
274 	cc &= ~(NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
275 	    NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
276 
277 	/* Leave AMS, MPS, and CSS as 0. */
278 
279 	cc |= NVMEF(NVME_CC_REG_EN, 1);
280 
281 	error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
282 	if (error != 0)
283 		errc(EX_IOERR, error, "Failed to set CC");
284 
285 	/* Wait for CSTS.RDY in Controller Status */
286 	timo = NVME_CAP_LO_TO(cap);
287 	for (;;) {
288 		error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
289 		if (error != 0)
290 			errc(EX_IOERR, error, "Failed to fetch CSTS");
291 
292 		if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
293 			break;
294 
295 		if (timo == 0)
296 			errx(EX_IOERR, "Controller failed to become ready");
297 		timo--;
298 		usleep(500 * 1000);
299 	}
300 
301 	return (qp);
302 }
303 
304 /*
305  * XXX: Should this accept the admin queue size as a parameter rather
306  * than always using NVMF_MIN_ADMIN_MAX_SQ_SIZE?
307  */
308 static int
connect_nvm_adminq(struct nvmf_association * na,const struct nvmf_qpair_params * params,struct nvmf_qpair ** qpp,uint16_t cntlid,const char * subnqn,const char * hostnqn,uint32_t kato,uint16_t * mqes)309 connect_nvm_adminq(struct nvmf_association *na,
310     const struct nvmf_qpair_params *params, struct nvmf_qpair **qpp,
311     uint16_t cntlid, const char *subnqn, const char *hostnqn, uint32_t kato,
312     uint16_t *mqes)
313 {
314 	struct nvmf_qpair *qp;
315 	uint64_t cap, cc, csts;
316 	u_int mps, mpsmin, mpsmax;
317 	int error, timo;
318 
319 	qp = nvmf_connect(na, params, 0, NVMF_MIN_ADMIN_MAX_SQ_SIZE, hostid,
320 	    cntlid, subnqn, hostnqn, kato);
321 	if (qp == NULL) {
322 		warnx("Failed to connect to NVM controller %s: %s", subnqn,
323 		    nvmf_association_error(na));
324 		return (EX_IOERR);
325 	}
326 
327 	/* Fetch Controller Capabilities Property */
328 	error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
329 	if (error != 0) {
330 		warnc(error, "Failed to fetch CAP");
331 		nvmf_free_qpair(qp);
332 		return (EX_IOERR);
333 	}
334 
335 	/* Require the NVM command set. */
336 	if (NVME_CAP_HI_CSS_NVM(cap >> 32) == 0) {
337 		warnx("Controller %s does not support the NVM command set",
338 		    subnqn);
339 		nvmf_free_qpair(qp);
340 		return (EX_UNAVAILABLE);
341 	}
342 
343 	*mqes = NVME_CAP_LO_MQES(cap);
344 
345 	/* Prefer native host page size if it fits. */
346 	mpsmin = NVMEV(NVME_CAP_HI_REG_MPSMIN, cap >> 32);
347 	mpsmax = NVMEV(NVME_CAP_HI_REG_MPSMAX, cap >> 32);
348 	mps = ffs(getpagesize()) - 1;
349 	if (mps < mpsmin + NVME_MPS_SHIFT)
350 		mps = mpsmin;
351 	else if (mps > mpsmax + NVME_MPS_SHIFT)
352 		mps = mpsmax;
353 	else
354 		mps -= NVME_MPS_SHIFT;
355 
356 	/* Configure controller. */
357 	error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
358 	if (error != 0) {
359 		warnc(error, "Failed to fetch CC");
360 		nvmf_free_qpair(qp);
361 		return (EX_IOERR);
362 	}
363 
364 	/* Clear known fields preserving any reserved fields. */
365 	cc &= ~(NVMEM(NVME_CC_REG_IOCQES) | NVMEM(NVME_CC_REG_IOSQES) |
366 	    NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
367 	    NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
368 
369 	cc |= NVMEF(NVME_CC_REG_IOCQES, 4);	/* CQE entry size == 16 */
370 	cc |= NVMEF(NVME_CC_REG_IOSQES, 6);	/* SEQ entry size == 64 */
371 	cc |= NVMEF(NVME_CC_REG_AMS, 0);	/* AMS 0 (Round-robin) */
372 	cc |= NVMEF(NVME_CC_REG_MPS, mps);
373 	cc |= NVMEF(NVME_CC_REG_CSS, 0);	/* NVM command set */
374 	cc |= NVMEF(NVME_CC_REG_EN, 1);		/* EN = 1 */
375 
376 	error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
377 	if (error != 0) {
378 		warnc(error, "Failed to set CC");
379 		nvmf_free_qpair(qp);
380 		return (EX_IOERR);
381 	}
382 
383 	/* Wait for CSTS.RDY in Controller Status */
384 	timo = NVME_CAP_LO_TO(cap);
385 	for (;;) {
386 		error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
387 		if (error != 0) {
388 			warnc(error, "Failed to fetch CSTS");
389 			nvmf_free_qpair(qp);
390 			return (EX_IOERR);
391 		}
392 
393 		if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
394 			break;
395 
396 		if (timo == 0) {
397 			warnx("Controller failed to become ready");
398 			nvmf_free_qpair(qp);
399 			return (EX_IOERR);
400 		}
401 		timo--;
402 		usleep(500 * 1000);
403 	}
404 
405 	*qpp = qp;
406 	return (0);
407 }
408 
409 static void
shutdown_controller(struct nvmf_qpair * qp)410 shutdown_controller(struct nvmf_qpair *qp)
411 {
412 	uint64_t cc;
413 	int error;
414 
415 	error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
416 	if (error != 0) {
417 		warnc(error, "Failed to fetch CC");
418 		goto out;
419 	}
420 
421 	cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_NORMAL);
422 
423 	error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
424 	if (error != 0) {
425 		warnc(error, "Failed to set CC to trigger shutdown");
426 		goto out;
427 	}
428 
429 out:
430 	nvmf_free_qpair(qp);
431 }
432 
433 /* Returns a value from <sysexits.h> */
434 int
connect_nvm_queues(const struct nvmf_association_params * aparams,enum nvmf_trtype trtype,int adrfam,const char * address,const char * port,uint16_t cntlid,const char * subnqn,const char * hostnqn,uint32_t kato,struct nvmf_qpair ** admin,struct nvmf_qpair ** io,u_int num_io_queues,u_int queue_size,struct nvme_controller_data * cdata)435 connect_nvm_queues(const struct nvmf_association_params *aparams,
436     enum nvmf_trtype trtype, int adrfam, const char *address,
437     const char *port, uint16_t cntlid, const char *subnqn, const char *hostnqn,
438     uint32_t kato, struct nvmf_qpair **admin, struct nvmf_qpair **io,
439     u_int num_io_queues, u_int queue_size, struct nvme_controller_data *cdata)
440 {
441 	struct nvmf_qpair_params qparams;
442 	struct nvmf_association *na;
443 	struct addrinfo *ai, *list;
444 	u_int queues;
445 	int error;
446 	uint16_t mqes;
447 
448 	switch (trtype) {
449 	case NVMF_TRTYPE_TCP:
450 		break;
451 	default:
452 		warnx("Unsupported transport %s", nvmf_transport_type(trtype));
453 		return (EX_UNAVAILABLE);
454 	}
455 
456 	if (!init_hostid())
457 		return (EX_IOERR);
458 	if (hostnqn == NULL || !nvmf_nqn_valid(hostnqn)) {
459 		warnx("Invalid HostNQN %s", hostnqn);
460 		return (EX_USAGE);
461 	}
462 
463 	/* Association. */
464 	na = nvmf_allocate_association(trtype, false, aparams);
465 	if (na == NULL) {
466 		warn("Failed to create association for %s", subnqn);
467 		return (EX_IOERR);
468 	}
469 
470 	/* Admin queue. */
471 	memset(&qparams, 0, sizeof(qparams));
472 	qparams.admin = true;
473 	if (!tcp_qpair_params(&qparams, adrfam, address, port, &ai, &list)) {
474 		nvmf_free_association(na);
475 		return (EX_NOHOST);
476 	}
477 	error = connect_nvm_adminq(na, &qparams, admin, cntlid, subnqn, hostnqn,
478 	    kato, &mqes);
479 	if (error != 0) {
480 		nvmf_free_association(na);
481 		freeaddrinfo(list);
482 		return (error);
483 	}
484 
485 	/* Validate I/O queue size. */
486 	memset(io, 0, sizeof(*io) * num_io_queues);
487 	if (queue_size == 0)
488 		queue_size = (u_int)mqes + 1;
489 	else if (queue_size > (u_int)mqes + 1) {
490 		warnx("I/O queue size exceeds controller maximum (%u)",
491 		    mqes + 1);
492 		error = EX_USAGE;
493 		goto out;
494 	}
495 
496 	/* Fetch controller data. */
497 	error = nvmf_host_identify_controller(*admin, cdata);
498 	if (error != 0) {
499 		warnc(error, "Failed to fetch controller data for %s", subnqn);
500 		error = EX_IOERR;
501 		goto out;
502 	}
503 
504 	nvmf_update_assocation(na, cdata);
505 
506 	error = nvmf_host_request_queues(*admin, num_io_queues, &queues);
507 	if (error != 0) {
508 		warnc(error, "Failed to request I/O queues");
509 		error = EX_IOERR;
510 		goto out;
511 	}
512 	if (queues < num_io_queues) {
513 		warnx("Controller enabled fewer I/O queues (%u) than requested (%u)",
514 		    queues, num_io_queues);
515 		error = EX_PROTOCOL;
516 		goto out;
517 	}
518 
519 	/* I/O queues. */
520 	for (u_int i = 0; i < num_io_queues; i++) {
521 		memset(&qparams, 0, sizeof(qparams));
522 		qparams.admin = false;
523 		if (!tcp_qpair_params_ai(&qparams, ai)) {
524 			warn("Failed to connect to controller at %s:%s",
525 			    address, port);
526 			error = EX_NOHOST;
527 			goto out;
528 		}
529 		io[i] = nvmf_connect(na, &qparams, i + 1, queue_size, hostid,
530 		    nvmf_cntlid(*admin), subnqn, hostnqn, 0);
531 		if (io[i] == NULL) {
532 			warnx("Failed to create I/O queue: %s",
533 			    nvmf_association_error(na));
534 			error = EX_IOERR;
535 			goto out;
536 		}
537 	}
538 	nvmf_free_association(na);
539 	freeaddrinfo(list);
540 	return (0);
541 
542 out:
543 	disconnect_nvm_queues(*admin, io, num_io_queues);
544 	nvmf_free_association(na);
545 	freeaddrinfo(list);
546 	return (error);
547 }
548 
549 void
disconnect_nvm_queues(struct nvmf_qpair * admin,struct nvmf_qpair ** io,u_int num_io_queues)550 disconnect_nvm_queues(struct nvmf_qpair *admin, struct nvmf_qpair **io,
551     u_int num_io_queues)
552 {
553 	for (u_int i = 0; i < num_io_queues; i++) {
554 		if (io[i] == NULL)
555 			break;
556 		nvmf_free_qpair(io[i]);
557 	}
558 	shutdown_controller(admin);
559 }
560