1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2023 Chelsio Communications, Inc.
5 * Written by: John Baldwin <jhb@FreeBSD.org>
6 */
7
8#include <sys/socket.h>
9#include <err.h>
10#include <errno.h>
11#include <inttypes.h>
12#include <libnvmf.h>
13#include <netdb.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <strings.h>
18#include <unistd.h>
19#include <netinet/in.h>
20
21static struct controller_info {
22	uint32_t ioccsz;
23	uint32_t nn;
24	uint16_t mqes;
25	bool	disconnect_supported;
26} info;
27
28enum rw { READ, WRITE };
29
30static bool data_digests, flow_control_disable, header_digests;
31
32static void
33usage(void)
34{
35	fprintf(stderr, "nvmfdd [-FGg] [-c cntlid] [-t transport] [-o offset] [-l length] [-n nsid]\n"
36	    "\tread|write <address:port> <nqn>\n");
37	exit(1);
38}
39
40static void
41tcp_association_params(struct nvmf_association_params *params)
42{
43	params->tcp.pda = 0;
44	params->tcp.header_digests = header_digests;
45	params->tcp.data_digests = data_digests;
46	params->tcp.maxr2t = 1;
47}
48
49static void
50tcp_qpair_params(struct nvmf_qpair_params *params, bool admin,
51    const char *address, const char *port)
52{
53	struct addrinfo hints, *ai, *list;
54	int error, s;
55
56	memset(&hints, 0, sizeof(hints));
57	hints.ai_family = AF_UNSPEC;
58	hints.ai_protocol = IPPROTO_TCP;
59	error = getaddrinfo(address, port, &hints, &list);
60	if (error != 0)
61		errx(1, "%s", gai_strerror(error));
62
63	for (ai = list; ai != NULL; ai = ai->ai_next) {
64		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
65		if (s == -1)
66			continue;
67
68		if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) {
69			close(s);
70			continue;
71		}
72
73		params->admin = admin;
74		params->tcp.fd = s;
75		freeaddrinfo(list);
76		return;
77	}
78	err(1, "Failed to connect to controller");
79}
80
81static struct nvmf_qpair *
82connect_admin_queue(struct nvmf_association *na,
83    const struct nvmf_qpair_params *params, const uint8_t hostid[16],
84    uint16_t cntlid, const char *hostnqn, const char *subnqn)
85{
86	struct nvme_controller_data cdata;
87	struct nvmf_qpair *qp;
88	uint64_t cap, cc, csts;
89	u_int mps, mpsmin, mpsmax;
90	int error, timo;
91
92	qp = nvmf_connect(na, params, 0, NVMF_MIN_ADMIN_MAX_SQ_SIZE, hostid,
93	    cntlid, subnqn, hostnqn, 0);
94	if (qp == NULL)
95		return (NULL);
96
97	error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
98	if (error != 0)
99		errc(1, error, "Failed to fetch CAP");
100
101	/* Require the NVM command set. */
102	if (NVME_CAP_HI_CSS_NVM(cap >> 32) == 0)
103		errx(1, "Controller does not support the NVM command set");
104
105	/* Prefer native host page size if it fits. */
106	mpsmin = NVMEV(NVME_CAP_HI_REG_MPSMIN, cap >> 32);
107	mpsmax = NVMEV(NVME_CAP_HI_REG_MPSMAX, cap >> 32);
108	mps = ffs(getpagesize()) - 1;
109	if (mps < mpsmin + 12)
110		mps = mpsmin;
111	else if (mps > mpsmax + 12)
112		mps = mpsmax;
113	else
114		mps -= 12;
115
116	/* Configure controller. */
117	error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
118	if (error != 0)
119		errc(1, error, "Failed to fetch CC");
120
121	/* Clear known fields preserving any reserved fields. */
122	cc &= ~(NVMEM(NVME_CC_REG_IOCQES) | NVMEM(NVME_CC_REG_IOSQES) |
123	    NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
124	    NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
125
126	cc |= NVMEF(NVME_CC_REG_IOCQES, 4);	/* CQE entry size == 16 */
127	cc |= NVMEF(NVME_CC_REG_IOSQES, 6);	/* SQE entry size == 64 */
128	cc |= NVMEF(NVME_CC_REG_AMS, 0);	/* AMS 0 (Round-robin) */
129	cc |= NVMEF(NVME_CC_REG_MPS, mps);
130	cc |= NVMEF(NVME_CC_REG_CSS, 0);	/* NVM command set */
131	cc |= NVMEF(NVME_CC_REG_EN, 1);		/* EN = 1 */
132
133	error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
134	if (error != 0)
135		errc(1, error, "Failed to set CC");
136
137	/* Wait for CSTS.RDY in Controller Status */
138	timo = NVME_CAP_LO_TO(cap);
139	for (;;) {
140		error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
141		if (error != 0)
142			errc(1, error, "Failed to fetch CSTS");
143
144		if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
145			break;
146
147		if (timo == 0)
148			errx(1, "Controller failed to become ready");
149		timo--;
150		usleep(500 * 1000);
151	}
152
153	/* Fetch controller data. */
154	error = nvmf_host_identify_controller(qp, &cdata);
155	if (error != 0)
156		errc(1, error, "Failed to fetch controller data");
157
158	nvmf_update_assocation(na, &cdata);
159
160	info.mqes = NVME_CAP_LO_MQES(cap);
161	info.nn = cdata.nn;
162	info.ioccsz = cdata.ioccsz;
163	info.disconnect_supported = (cdata.ofcs & 1) != 0;
164
165	return (qp);
166}
167
168static void
169shutdown_controller(struct nvmf_qpair *qp)
170{
171	uint64_t cc;
172	int error;
173
174	error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
175	if (error != 0)
176		errc(1, error, "Failed to fetch CC");
177
178	cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_NORMAL);
179
180	error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
181	if (error != 0)
182		errc(1, error, "Failed to set CC to trigger shutdown");
183
184	nvmf_free_qpair(qp);
185}
186
187static void
188disconnect_queue(struct nvmf_qpair *qp)
189{
190	nvmf_free_qpair(qp);
191}
192
193static int
194validate_namespace(struct nvmf_qpair *qp, u_int nsid, u_int *block_size)
195{
196	struct nvme_namespace_data nsdata;
197	int error;
198	uint8_t lbads, lbaf;
199
200	if (nsid > info.nn) {
201		warnx("Invalid namespace ID %u", nsid);
202		return (ERANGE);
203	}
204
205	error = nvmf_host_identify_namespace(qp, nsid, &nsdata);
206	if (error != 0) {
207		warnc(error, "Failed to identify namespace");
208		return (error);
209	}
210
211	nvme_namespace_data_swapbytes(&nsdata);
212
213	if (NVMEV(NVME_NS_DATA_DPS_PIT, nsdata.dps) != 0) {
214		warnx("End-to-end data protection is not supported");
215		return (EINVAL);
216	}
217
218	lbaf = NVMEV(NVME_NS_DATA_FLBAS_FORMAT, nsdata.flbas);
219	if (lbaf > nsdata.nlbaf) {
220		warnx("Invalid LBA format index");
221		return (EINVAL);
222	}
223
224	if (NVMEV(NVME_NS_DATA_LBAF_MS, nsdata.lbaf[lbaf]) != 0) {
225		warnx("Namespaces with metadata are not supported");
226		return (EINVAL);
227	}
228
229	lbads = NVMEV(NVME_NS_DATA_LBAF_LBADS, nsdata.lbaf[lbaf]);
230	if (lbads == 0) {
231		warnx("Invalid LBA format index");
232		return (EINVAL);
233	}
234
235	*block_size = 1 << lbads;
236	fprintf(stderr, "Detected block size %u\n", *block_size);
237	return (0);
238}
239
240static int
241nvmf_io_command(struct nvmf_qpair *qp, u_int nsid, enum rw command,
242    uint64_t slba, uint16_t nlb, void *buffer, size_t length)
243{
244	struct nvme_command cmd;
245	const struct nvme_completion *cqe;
246	struct nvmf_capsule *cc, *rc;
247	int error;
248	uint16_t status;
249
250	memset(&cmd, 0, sizeof(cmd));
251	cmd.opc = command == WRITE ? NVME_OPC_WRITE : NVME_OPC_READ;
252	cmd.nsid = htole32(nsid);
253	cmd.cdw10 = htole32(slba);
254	cmd.cdw11 = htole32(slba >> 32);
255	cmd.cdw12 = htole32(nlb - 1);
256	/* Sequential Request in cdw13? */
257
258	cc = nvmf_allocate_command(qp, &cmd);
259	if (cc == NULL)
260		return (errno);
261
262	error = nvmf_capsule_append_data(cc, buffer, length,
263	    command == WRITE);
264	if (error != 0) {
265		nvmf_free_capsule(cc);
266		return (error);
267	}
268
269	error = nvmf_host_transmit_command(cc);
270	if (error != 0) {
271		nvmf_free_capsule(cc);
272		return (error);
273	}
274
275	error = nvmf_host_wait_for_response(cc, &rc);
276	nvmf_free_capsule(cc);
277	if (error != 0)
278		return (error);
279
280	cqe = nvmf_capsule_cqe(rc);
281	status = le16toh(cqe->status);
282	if (status != 0) {
283		printf("NVMF: %s failed, status %#x\n", command == WRITE ?
284		    "WRITE" : "READ", status);
285		nvmf_free_capsule(rc);
286		return (EIO);
287	}
288
289	nvmf_free_capsule(rc);
290	return (0);
291}
292
293static int
294nvmf_io(struct nvmf_qpair *qp, u_int nsid, u_int block_size, enum rw command,
295    off_t offset, off_t length)
296{
297	char *buf;
298	ssize_t rv;
299	u_int todo;
300	int error;
301
302	if (offset % block_size != 0) {
303		warnx("Misaligned offset");
304		return (EINVAL);
305	}
306	if (length % block_size != 0 && command == WRITE)
307		warnx("Length is not multiple of block size, will zero pad");
308
309	buf = malloc(block_size);
310	error = 0;
311	while (length != 0) {
312		todo = length;
313		if (todo > block_size)
314			todo = block_size;
315
316		if (command == WRITE) {
317			rv = read(STDIN_FILENO, buf, todo);
318			if (rv == -1) {
319				error = errno;
320				break;
321			}
322			if (rv != todo) {
323				warn("Short read on input");
324				error = EIO;
325				break;
326			}
327
328			if (todo < block_size)
329				memset(buf + todo, 0, block_size - todo);
330		}
331
332		error = nvmf_io_command(qp, nsid, command, offset / block_size,
333		    1, buf, block_size);
334		if (error != 0) {
335			warnc(error, "Failed I/O request");
336			break;
337		}
338
339		if (command == READ)
340			(void)write(STDOUT_FILENO, buf, todo);
341
342		offset += block_size;
343		length -= todo;
344	}
345
346	free(buf);
347	return (error);
348}
349
350int
351main(int ac, char **av)
352{
353	const char *transport;
354	char *address, *port;
355	enum rw command;
356	struct nvmf_association_params aparams;
357	struct nvmf_qpair_params qparams;
358	struct nvmf_association *na;
359	struct nvmf_qpair *admin, *io;
360	char hostnqn[NVMF_NQN_MAX_LEN];
361	uint8_t hostid[16];
362	enum nvmf_trtype trtype;
363	off_t offset, length;
364	int ch, error;
365	u_int block_size, cntlid, nsid, queues;
366
367	cntlid = NVMF_CNTLID_DYNAMIC;
368	offset = 0;
369	length = 512;
370	nsid = 1;
371	port = NULL;
372	transport = "tcp";
373	while ((ch = getopt(ac, av, "FGc:gl:n:o:p:t:")) != -1) {
374		switch (ch) {
375		case 'F':
376			flow_control_disable = true;
377			break;
378		case 'G':
379			data_digests = true;
380			break;
381		case 'c':
382			if (strcasecmp(optarg, "dynamic") == 0)
383				cntlid = NVMF_CNTLID_DYNAMIC;
384			else if (strcasecmp(optarg, "static") == 0)
385				cntlid = NVMF_CNTLID_STATIC_ANY;
386			else
387				cntlid = strtoul(optarg, NULL, 0);
388			break;
389		case 'g':
390			header_digests = true;
391			break;
392		case 'l':
393			length = strtoumax(optarg, NULL, 0);
394			break;
395		case 'n':
396			nsid = strtoul(optarg, NULL, 0);
397			break;
398		case 'o':
399			offset = strtoumax(optarg, NULL, 0);
400			break;
401		case 't':
402			transport = optarg;
403			break;
404		default:
405			usage();
406		}
407	}
408
409	av += optind;
410	ac -= optind;
411
412	if (ac != 3)
413		usage();
414
415	if (nsid == 0 || nsid >= 0xffffffff)
416		errx(1, "Invalid namespace ID %u", nsid);
417
418	if (strcasecmp(av[0], "read") == 0)
419		command = READ;
420	else if (strcasecmp(av[0], "write") == 0)
421		command = WRITE;
422	else
423		errx(1, "Invalid command %s", av[0]);
424
425	address = av[1];
426	port = strrchr(address, ':');
427	if (port == NULL || port[1] == '\0')
428		errx(1, "Invalid address %s", address);
429	*port = '\0';
430	port++;
431
432	memset(&aparams, 0, sizeof(aparams));
433	aparams.sq_flow_control = !flow_control_disable;
434	if (strcasecmp(transport, "tcp") == 0) {
435		trtype = NVMF_TRTYPE_TCP;
436		tcp_association_params(&aparams);
437	} else
438		errx(1, "Invalid transport %s", transport);
439
440	error = nvmf_hostid_from_hostuuid(hostid);
441	if (error != 0)
442		errc(1, error, "Failed to generate hostid");
443	error = nvmf_nqn_from_hostuuid(hostnqn);
444	if (error != 0)
445		errc(1, error, "Failed to generate host NQN");
446
447	na = nvmf_allocate_association(trtype, false, &aparams);
448	if (na == NULL)
449		err(1, "Failed to create association");
450
451	memset(&qparams, 0, sizeof(qparams));
452	tcp_qpair_params(&qparams, true, address, port);
453
454	admin = connect_admin_queue(na, &qparams, hostid, cntlid, hostnqn,
455	    av[2]);
456	if (admin == NULL)
457		errx(1, "Failed to create admin queue: %s",
458		    nvmf_association_error(na));
459
460	error = validate_namespace(admin, nsid, &block_size);
461	if (error != 0) {
462		shutdown_controller(admin);
463		nvmf_free_association(na);
464		return (1);
465	}
466
467	error = nvmf_host_request_queues(admin, 1, &queues);
468	if (error != 0) {
469		shutdown_controller(admin);
470		nvmf_free_association(na);
471		errc(1, error, "Failed to request I/O queues");
472	}
473
474	memset(&qparams, 0, sizeof(qparams));
475	tcp_qpair_params(&qparams, false, address, port);
476
477	io = nvmf_connect(na, &qparams, 1, info.mqes + 1, hostid,
478	    nvmf_cntlid(admin), av[2], hostnqn, 0);
479	if (io == NULL) {
480		warn("Failed to create I/O queue: %s",
481		    nvmf_association_error(na));
482		shutdown_controller(admin);
483		nvmf_free_association(na);
484		return (1);
485	}
486	nvmf_free_association(na);
487
488	error = nvmf_io(io, nsid, block_size, command, offset, length);
489
490	disconnect_queue(io);
491	shutdown_controller(admin);
492	return (error == 0 ? 0 : 1);
493}
494