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
|
// SPDX-License-Identifier: LGPL-2.1-or-later
/**
* This file is part of libnvme.
* Copyright (c) 2020 Western Digital Corporation or its affiliates.
*
* Authors: Keith Busch <keith.busch@wdc.com>
*/
/**
* Open all nvme controller's uevent and listen for changes. If NVME_AEN event
* is observed with controller telemetry data, read the log and save it to a
* file in /var/log/ with the device's unique name and epoch timestamp.
*/
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <libnvme.h>
#include <sys/stat.h>
#include <ccan/endian/endian.h>
struct events {
nvme_ctrl_t c;
int uevent_fd;
};
static int open_uevent(nvme_ctrl_t c)
{
char buf[0x1000];
if (snprintf(buf, sizeof(buf), "%s/uevent", nvme_ctrl_get_sysfs_dir(c)) < 0)
return -1;
return open(buf, O_RDONLY);
}
static void save_telemetry(nvme_ctrl_t c)
{
char buf[0x1000];
size_t log_size;
int ret, fd;
struct nvme_telemetry_log *log;
time_t s;
/* Clear the log (rae == false) at the end to see new telemetry events later */
ret = nvme_get_ctrl_telemetry(nvme_ctrl_get_fd(c), false, &log, NVME_TELEMETRY_DA_3, &log_size);
if (ret)
return;
s = time(NULL);
ret = snprintf(buf, sizeof(buf), "/var/log/%s-telemetry-%llu",
nvme_ctrl_get_subsysnqn(c), (unsigned long long)s);
if (ret < 0) {
free(log);
return;
}
fd = open(buf, O_CREAT|O_WRONLY, S_IRUSR|S_IRGRP);
if (fd < 0) {
free(log);
return;
}
ret = write(fd, log, log_size);
if (ret < 0)
printf("failed to write telemetry log\n");
else
printf("telemetry log save as %s, wrote:%d size:%zd\n", buf,
ret, log_size);
close(fd);
free(log);
}
static void check_telemetry(nvme_ctrl_t c, int ufd)
{
char buf[0x1000] = { 0 };
char *p, *ptr;
int len;
len = read(ufd, buf, sizeof(buf) - 1);
if (len < 0)
return;
ptr = buf;
while ((p = strsep(&ptr, "\n")) != NULL) {
__u32 aen, type, info, lid;
if (sscanf(p, "NVME_AEN=0x%08x", &aen) != 1)
continue;
type = aen & 0x07;
info = (aen >> 8) & 0xff;
lid = (aen >> 16) & 0xff;
printf("%s: aen type:%x info:%x lid:%d\n",
nvme_ctrl_get_name(c), type, info, lid);
if (type == NVME_AER_NOTICE &&
info == NVME_AER_NOTICE_TELEMETRY)
save_telemetry(c);
}
}
static void wait_events(fd_set *fds, struct events *e, int nr)
{
int ret, i;
for (i = 0; i < nr; i++)
check_telemetry(e[i].c, e[i].uevent_fd);
while (1) {
ret = select(nr, fds, NULL, NULL, NULL);
if (ret < 0)
return;
for (i = 0; i < nr; i++) {
if (!FD_ISSET(e[i].uevent_fd, fds))
continue;
check_telemetry(e[i].c, e[i].uevent_fd);
}
}
}
int main()
{
struct events *e;
fd_set fds;
int i = 0;
nvme_subsystem_t s;
nvme_ctrl_t c;
nvme_host_t h;
nvme_root_t r;
r = nvme_scan(NULL);
if (!r)
return EXIT_FAILURE;
nvme_for_each_host(r, h)
nvme_for_each_subsystem(h, s)
nvme_subsystem_for_each_ctrl(s, c)
i++;
e = calloc(i, sizeof(struct events));
FD_ZERO(&fds);
i = 0;
nvme_for_each_host(r, h) {
nvme_for_each_subsystem(h, s) {
nvme_subsystem_for_each_ctrl(s, c) {
int fd = open_uevent(c);
if (fd < 0)
continue;
FD_SET(fd, &fds);
e[i].uevent_fd = fd;
e[i].c = c;
i++;
}
}
}
wait_events(&fds, e, i);
nvme_free_tree(r);
free(e);
return EXIT_SUCCESS;
}
|