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