xref: /freebsd/sbin/nvmecontrol/telemetry.c (revision 3d966ae7895b40c7a4985f61ff4819c25c8618c9)
1c1fccf0fSWarner Losh /*-
2c1fccf0fSWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3c1fccf0fSWarner Losh  *
4c1fccf0fSWarner Losh  * Copyright (C) 2024 Netflix, Inc
5c1fccf0fSWarner Losh  *
6c1fccf0fSWarner Losh  * Redistribution and use in source and binary forms, with or without
7c1fccf0fSWarner Losh  * modification, are permitted provided that the following conditions
8c1fccf0fSWarner Losh  * are met:
9c1fccf0fSWarner Losh  * 1. Redistributions of source code must retain the above copyright
10c1fccf0fSWarner Losh  *    notice, this list of conditions and the following disclaimer.
11c1fccf0fSWarner Losh  * 2. Redistributions in binary form must reproduce the above copyright
12c1fccf0fSWarner Losh  *    notice, this list of conditions and the following disclaimer in the
13c1fccf0fSWarner Losh  *    documentation and/or other materials provided with the distribution.
14c1fccf0fSWarner Losh  *
15c1fccf0fSWarner Losh  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16c1fccf0fSWarner Losh  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17c1fccf0fSWarner Losh  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18c1fccf0fSWarner Losh  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19c1fccf0fSWarner Losh  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20c1fccf0fSWarner Losh  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21c1fccf0fSWarner Losh  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22c1fccf0fSWarner Losh  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23c1fccf0fSWarner Losh  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24c1fccf0fSWarner Losh  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25c1fccf0fSWarner Losh  * SUCH DAMAGE.
26c1fccf0fSWarner Losh  */
27c1fccf0fSWarner Losh 
28c1fccf0fSWarner Losh #include <sys/param.h>
29c1fccf0fSWarner Losh #include <sys/ioccom.h>
30c1fccf0fSWarner Losh 
31c1fccf0fSWarner Losh #include <ctype.h>
32c1fccf0fSWarner Losh #include <err.h>
33c1fccf0fSWarner Losh #include <fcntl.h>
34c1fccf0fSWarner Losh #include <stdbool.h>
35c1fccf0fSWarner Losh #include <stddef.h>
36c1fccf0fSWarner Losh #include <stdio.h>
37c1fccf0fSWarner Losh #include <stdlib.h>
38c1fccf0fSWarner Losh #include <string.h>
39c1fccf0fSWarner Losh #include <sysexits.h>
40c1fccf0fSWarner Losh #include <unistd.h>
41c1fccf0fSWarner Losh #include <sys/endian.h>
42c1fccf0fSWarner Losh 
43c1fccf0fSWarner Losh #include "nvmecontrol.h"
44c1fccf0fSWarner Losh 
45c1fccf0fSWarner Losh /* Tables for command line parsing */
46c1fccf0fSWarner Losh 
47c1fccf0fSWarner Losh static cmd_fn_t telemetry_log;
48c1fccf0fSWarner Losh 
49c1fccf0fSWarner Losh #define NONE 0xffffffffu
50c1fccf0fSWarner Losh static struct options {
51c1fccf0fSWarner Losh 	const char *outfn;
52c1fccf0fSWarner Losh 	const char *dev;
53c1fccf0fSWarner Losh 	uint8_t da;
54c1fccf0fSWarner Losh } opt = {
55c1fccf0fSWarner Losh 	.outfn = NULL,
56c1fccf0fSWarner Losh 	.dev = NULL,
57c1fccf0fSWarner Losh 	.da = 3,
58c1fccf0fSWarner Losh };
59c1fccf0fSWarner Losh 
60c1fccf0fSWarner Losh static const struct opts telemetry_log_opts[] = {
61c1fccf0fSWarner Losh #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
62c1fccf0fSWarner Losh 	OPT("output-file", 'O', arg_string, opt, outfn,
63c1fccf0fSWarner Losh 	    "output file for telemetry data"),
64c1fccf0fSWarner Losh 	OPT("data-area", 'd', arg_uint8, opt, da,
65c1fccf0fSWarner Losh 	    "output file for telemetry data"),
66c1fccf0fSWarner Losh 	{ NULL, 0, arg_none, NULL, NULL }
67c1fccf0fSWarner Losh };
68c1fccf0fSWarner Losh #undef OPT
69c1fccf0fSWarner Losh 
70c1fccf0fSWarner Losh static const struct args telemetry_log_args[] = {
71c1fccf0fSWarner Losh 	{ arg_string, &opt.dev, "<controller id|namespace id>" },
72c1fccf0fSWarner Losh 	{ arg_none, NULL, NULL },
73c1fccf0fSWarner Losh };
74c1fccf0fSWarner Losh 
75c1fccf0fSWarner Losh static struct cmd telemetry_log_cmd = {
76c1fccf0fSWarner Losh 	.name = "telemetry-log",
77c1fccf0fSWarner Losh 	.fn = telemetry_log,
78c1fccf0fSWarner Losh 	.descr = "Retrieves telemetry log pages from drive",
79c1fccf0fSWarner Losh 	.ctx_size = sizeof(opt),
80c1fccf0fSWarner Losh 	.opts = telemetry_log_opts,
81c1fccf0fSWarner Losh 	.args = telemetry_log_args,
82c1fccf0fSWarner Losh };
83c1fccf0fSWarner Losh 
84c1fccf0fSWarner Losh CMD_COMMAND(telemetry_log_cmd);
85c1fccf0fSWarner Losh 
86c1fccf0fSWarner Losh /* End of tables for command line parsing */
87c1fccf0fSWarner Losh 
88c1fccf0fSWarner Losh /*
89c1fccf0fSWarner Losh  * Note: Even though this is a logpage, it's variable size and tricky
90c1fccf0fSWarner Losh  * to get with some weird options, so it's its own command.
91c1fccf0fSWarner Losh  */
92c1fccf0fSWarner Losh 
93c1fccf0fSWarner Losh static void
telemetry_log(const struct cmd * f,int argc,char * argv[])94c1fccf0fSWarner Losh telemetry_log(const struct cmd *f, int argc, char *argv[])
95c1fccf0fSWarner Losh {
96c1fccf0fSWarner Losh 	int				fd, fdout;
97c1fccf0fSWarner Losh 	char				*path;
98c1fccf0fSWarner Losh 	uint32_t			nsid;
99*3d966ae7SWarner Losh 	ssize_t				size;
100c1fccf0fSWarner Losh 	uint64_t			off;
101*3d966ae7SWarner Losh 	ssize_t				chunk;
102c1fccf0fSWarner Losh 	struct nvme_controller_data	cdata;
103c1fccf0fSWarner Losh 	bool				can_telemetry;
104c1fccf0fSWarner Losh 	struct nvme_telemetry_log_page  tlp, buf;
105c1fccf0fSWarner Losh 
106c1fccf0fSWarner Losh 	if (arg_parse(argc, argv, f))
107c1fccf0fSWarner Losh 		return;
108c1fccf0fSWarner Losh 	if (opt.da < 1 || opt.da > 3)
109c1fccf0fSWarner Losh 		errx(EX_USAGE, "Data area %d is not in the range 1-3\n", opt.da);
110c1fccf0fSWarner Losh 	if (opt.outfn == NULL)
111c1fccf0fSWarner Losh 		errx(EX_USAGE, "No output file specified");
112c1fccf0fSWarner Losh 
113c1fccf0fSWarner Losh 	open_dev(opt.dev, &fd, 0, 1);
114c1fccf0fSWarner Losh 	get_nsid(fd, &path, &nsid);
115c1fccf0fSWarner Losh 	if (nsid == 0) {
116c1fccf0fSWarner Losh 		nsid = NVME_GLOBAL_NAMESPACE_TAG;
117c1fccf0fSWarner Losh 	} else {
118c1fccf0fSWarner Losh 		close(fd);
119c1fccf0fSWarner Losh 		open_dev(path, &fd, 0, 1);
120c1fccf0fSWarner Losh 	}
121c1fccf0fSWarner Losh 	free(path);
122c1fccf0fSWarner Losh 
123c1fccf0fSWarner Losh 	if (read_controller_data(fd, &cdata))
124c1fccf0fSWarner Losh 		errx(EX_IOERR, "Identify request failed");
125c1fccf0fSWarner Losh 
126c1fccf0fSWarner Losh 	can_telemetry = NVMEV(NVME_CTRLR_DATA_LPA_TELEMETRY, cdata.lpa);
127c1fccf0fSWarner Losh 	if (!can_telemetry)
128c1fccf0fSWarner Losh 		errx(EX_UNAVAILABLE, "Drive does not support telemetry");
129c1fccf0fSWarner Losh 	if (nsid != NVME_GLOBAL_NAMESPACE_TAG)
130c1fccf0fSWarner Losh 		errx(EX_UNAVAILABLE, "Cannot operate on namespace");
131c1fccf0fSWarner Losh 
132c1fccf0fSWarner Losh 	fdout = open(opt.outfn, O_WRONLY | O_CREAT, 0664);
133c1fccf0fSWarner Losh 	if (fdout == -1)
134c1fccf0fSWarner Losh 		err(EX_IOERR, "Can't create %s", opt.outfn);
135c1fccf0fSWarner Losh 
136c1fccf0fSWarner Losh 	/* Read the log page */
137c1fccf0fSWarner Losh 	size = sizeof(tlp);
138c1fccf0fSWarner Losh 	off = 0;
139c1fccf0fSWarner Losh 	read_logpage(fd, NVME_LOG_TELEMETRY_HOST_INITIATED, nsid, 0, 0, 0,
140c1fccf0fSWarner Losh 	    off, 0, 0, 0, &tlp, size);
141c1fccf0fSWarner Losh 	switch(opt.da) {
142c1fccf0fSWarner Losh 	case 1:
143c1fccf0fSWarner Losh 		size = letoh(tlp.da1_last);
144c1fccf0fSWarner Losh 		break;
145c1fccf0fSWarner Losh 	case 2:
146c1fccf0fSWarner Losh 		size = letoh(tlp.da2_last);
147c1fccf0fSWarner Losh 		break;
148c1fccf0fSWarner Losh 	case 3:
149c1fccf0fSWarner Losh 		size = letoh(tlp.da3_last);
150c1fccf0fSWarner Losh 		break;
151c1fccf0fSWarner Losh 	default:
152c1fccf0fSWarner Losh 		errx(EX_USAGE, "Impossible data area %d", opt.da);
153c1fccf0fSWarner Losh 	}
154c1fccf0fSWarner Losh 	size = (size + 1) * 512; /* The count of additional pages */
155c1fccf0fSWarner Losh 	chunk = 4096;
156c1fccf0fSWarner Losh 
157c1fccf0fSWarner Losh 	printf("Extracting %llu bytes\n", (unsigned long long)size);
158c1fccf0fSWarner Losh 	do {
159c1fccf0fSWarner Losh 		if (chunk > size)
160c1fccf0fSWarner Losh 			chunk = size;
161c1fccf0fSWarner Losh 		read_logpage(fd, NVME_LOG_TELEMETRY_HOST_INITIATED, nsid, 0, 0, 0,
162c1fccf0fSWarner Losh 		    off, 0, 0, 0, &buf, chunk);
163c1fccf0fSWarner Losh 		if (off == 0) {
164c1fccf0fSWarner Losh 			/*
165c1fccf0fSWarner Losh 			 * Sanity check to make sure that the generation number
166c1fccf0fSWarner Losh 			 * didn't change between the two reads.
167c1fccf0fSWarner Losh 			 */
168c1fccf0fSWarner Losh 			if (tlp.hi_gen != buf.hi_gen)
169c1fccf0fSWarner Losh 				warnx(
170c1fccf0fSWarner Losh 				    "Generation number changed from %d to %d",
171c1fccf0fSWarner Losh 				    tlp.hi_gen, buf.hi_gen);
172c1fccf0fSWarner Losh 		}
173c1fccf0fSWarner Losh 		if (write(fdout, &buf, chunk) != chunk)
174c1fccf0fSWarner Losh 			err(EX_IOERR, "Error writing %s", opt.outfn);
175c1fccf0fSWarner Losh 		off += chunk;
176c1fccf0fSWarner Losh 		size -= chunk;
177c1fccf0fSWarner Losh 	} while (size > 0);
178c1fccf0fSWarner Losh 
179c1fccf0fSWarner Losh 	close(fdout);
180c1fccf0fSWarner Losh 	close(fd);
181c1fccf0fSWarner Losh 	exit(0);
182c1fccf0fSWarner Losh }
183