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