1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
* Copyright 2025 Oxide Computer Company
*/
/*
* Logic to fetch and save an instance of the telemetry log page. The telemetry
* log page consists of a 512-byte header followed by a number of data blocks.
* The number of data blocks is indicated by the controller.
*
* Telemetry may either be host-initiated or device-initiated. When the
* telemetry is host-initiated, the host specifies when to create the telemetry
* using a flag in the log-specific parameter field (lsp). Whenever this is a 1,
* then this data is created again. When telemetry is device-initiated, which
* uses a different log page, then the data persists as long as the retain async
* event flag is specified.
*
* In the telemetry header there are two things that we need to pay attention
* to:
*
* 1. There are up to four indicators for the number of telemetry blocks that
* could exist. These are meant to be indicators of short, medium, and long.
* The 4th one requires the kernel to opt into it with a specific set
* features command. We basically always try to get the largest amount that
* exists in the header.
*
* 2. There are a series of generation numbers that exist. We need to ensure
* that these generation numbers are the same across everything that we find.
* So basically we read this initially at the start and then read it again at
* the end. If the values differ, then we throw an error or would otherwise
* have to start over. There are separate generation numbers for
* host-initiated and controller-initiated telemetry.
*
* The telemetry file may have a large number of blocks so we split this up into
* multiple requests of up to 1 MiB (our default maximum size). We do not assume
* that we will get the log all in one go. As such, we also will not assume that
* we can buffer the entire log page in memory and will always write it out.
* This means that the user will be required to use the output file option.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <err.h>
#include <unistd.h>
#include <sys/sysmacros.h>
#include "nvmeadm.h"
/*
* We use a 64 KiB buffer here as that's usually within a device's maximum
* payload.
*/
#define TELEM_BLKSIZE (64 * 1024)
static void
telemetry_read(const nvme_process_arg_t *npa, nvme_log_req_t *req, void *buf,
size_t len, uint64_t off)
{
if (!nvme_log_req_set_output(req, buf, len)) {
nvmeadm_fatal(npa, "failed to set output buffer");
}
if (!nvme_log_req_set_offset(req, off)) {
nvmeadm_fatal(npa, "failed to set offset to 0x%lx", off);
}
if (!nvme_log_req_exec(req)) {
nvmeadm_fatal(npa, "failed to read %zu bytes at 0x%lx", len,
off);
}
}
static void
telemetry_write(int ofd, const void *buf, size_t len)
{
size_t off = 0;
while (len > 0) {
ssize_t ret = write(ofd, buf + off, len - off);
if (ret < 0) {
err(EXIT_FAILURE, "failed to write to log telemetry "
"output file");
}
off += (size_t)ret;
len -= (size_t)ret;
}
}
int
do_get_logpage_telemetry(const nvme_process_arg_t *npa,
const nvme_log_disc_t *disc, nvme_log_req_t *req)
{
int ofd;
const nvmeadm_get_logpage_t *log = npa->npa_cmd_arg;
void *buf;
nvme_telemetry_log_t hdr;
uint64_t len;
if (log->ngl_output == NULL) {
errx(-1, "log page %s requires specifying an output file",
nvme_log_disc_name(disc));
}
ofd = open(log->ngl_output, O_WRONLY | O_TRUNC | O_CREAT, 0644);
if (ofd < 0) {
err(-1, "failed to create output file %s", log->ngl_output);
}
buf = calloc(TELEM_BLKSIZE, sizeof (uint8_t));
if (buf == NULL) {
err(-1, "failed to allocate %u bytes for interim data buffer",
TELEM_BLKSIZE);
}
/*
* First create a new request and read the first 512-bytes.
*/
if (!nvme_log_req_set_lsp(req, NVME_TELMCTRL_LSP_CTHID)) {
nvmeadm_fatal(npa, "failed to set lsp to create host "
"telemetry");
}
telemetry_read(npa, req, &hdr, sizeof (hdr), 0);
telemetry_write(ofd, &hdr, sizeof (hdr));
/*
* Clear the request to create telemetry for the rest of our operation.
*/
if (!nvme_log_req_set_lsp(req, 0)) {
nvmeadm_fatal(npa, "failed to set lsp to create host "
"telemetry");
}
if (!nvme_log_disc_calc_size(disc, &len, &hdr, sizeof (hdr))) {
errx(-1, "failed to determine full %s log length",
npa->npa_argv[0]);
}
size_t off = sizeof (hdr);
while (off < len) {
size_t to_read = MIN(len - off, TELEM_BLKSIZE);
telemetry_read(npa, req, buf, to_read, off);
telemetry_write(ofd, buf, to_read);
off += to_read;
}
telemetry_read(npa, req, buf, sizeof (hdr), 0);
const nvme_telemetry_log_t *final = (const nvme_telemetry_log_t *)buf;
if (hdr.ntl_thdgn != final->ntl_thdgn) {
errx(-1, "log telemetry generation changed: originally was "
"0x%x, ended with 0x%x", hdr.ntl_thdgn, final->ntl_thdgn);
}
free(buf);
(void) close(ofd);
return (0);
}
|