mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Use libudev to monitor for "change" events in the "nvme" subsystem that use the "pcie" transport to trigger NVMe SMART data collection. Only collect data from the NVMe referenced by the udev event. Assisted-by: IBM Bob:1.0.1 Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
311 lines
9.4 KiB
C
311 lines
9.4 KiB
C
/*
|
|
* zpcimon - Report monitoring data to firmware
|
|
*
|
|
* Copyright IBM Corp. 2025
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <libnvme.h>
|
|
#include <libudev.h>
|
|
|
|
#include "lib/pci_list.h"
|
|
#include "lib/pci_sclp.h"
|
|
#include "lib/util_fmt.h"
|
|
|
|
#include "nvmemon.h"
|
|
#include "zpcimon.h"
|
|
|
|
/**
|
|
* Reads a CPU native 128 bit unsigned integer from the binary buffer @data
|
|
* containing the value in the 128 bit little endian format used by the
|
|
* NVMe standard.
|
|
*
|
|
* @return the 128 bit value encoded in the data buffer.
|
|
*/
|
|
static __uint128_t nvme_le128_to_cpu(const __u8 data[16])
|
|
{
|
|
__uint128_t u;
|
|
int i;
|
|
|
|
u = data[0];
|
|
for (i = 1; i < (int)sizeof(u); i++)
|
|
u |= (__uint128_t)data[i] << (8 * i);
|
|
return u;
|
|
}
|
|
|
|
#define MAX_UINT128_DECIMAL_LEN (41) /* length of 2^128-1 in decimal + 0 byte */
|
|
|
|
/**
|
|
* Write the decimal string representation of the passed unsigned 128 bit value @val into
|
|
* the buffer @buf for output in JSON. The representation does not include
|
|
* quotes.
|
|
*/
|
|
static void nvme_u128_to_json_val(__uint128_t val, char buf[MAX_UINT128_DECIMAL_LEN])
|
|
{
|
|
const char *digit_ascii = "0123456789";
|
|
int pos = MAX_UINT128_DECIMAL_LEN - 1;
|
|
int mod_ten;
|
|
int len;
|
|
|
|
/* Write digits right to left at the end of res */
|
|
do {
|
|
mod_ten = val % 10;
|
|
val /= 10;
|
|
buf[pos--] = digit_ascii[mod_ten];
|
|
} while (val);
|
|
/* Move digits to the front */
|
|
len = MAX_UINT128_DECIMAL_LEN - 1 - pos;
|
|
memmove(buf, &buf[pos + 1], len);
|
|
buf[len] = '\0';
|
|
}
|
|
|
|
static void nvme_json_print_smart_log(struct zpcimon_ctx *ctx, struct nvme_smart_log *log)
|
|
{
|
|
char u128_num_buf[MAX_UINT128_DECIMAL_LEN];
|
|
unsigned int temperature;
|
|
|
|
/* While some fields in struct nvme_smart_log are __leXX
|
|
* temperature is an array of two u8 of the little endian data
|
|
* in Kelvin.
|
|
*/
|
|
temperature = log->temperature[1] << 8 | log->temperature[0];
|
|
|
|
util_fmt_obj_start(FMT_DEFAULT, "smart-log");
|
|
util_fmt_pair(FMT_DEFAULT, "critical_warning", "%d", log->critical_warning);
|
|
util_fmt_pair(FMT_DEFAULT, "temperature", "%d", temperature);
|
|
util_fmt_pair(FMT_DEFAULT, "avail_spare", "%d", log->avail_spare);
|
|
util_fmt_pair(FMT_DEFAULT, "spare_thresh", "%d", log->spare_thresh);
|
|
util_fmt_pair(FMT_DEFAULT, "percent_used", "%d", log->percent_used);
|
|
util_fmt_pair(FMT_DEFAULT, "endurance_grp_critical_warning_summary", "%d",
|
|
log->endu_grp_crit_warn_sumry);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->data_units_read), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "data_units_read", "%s", u128_num_buf);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->data_units_written), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "data_units_written", "%s", u128_num_buf);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->host_reads), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "host_read_commands", "%s", u128_num_buf);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->host_writes), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "host_write_commands", "%s", u128_num_buf);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->ctrl_busy_time), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "controller_busy_time", "%s", u128_num_buf);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->power_cycles), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "power_cycles", "%s", u128_num_buf);
|
|
/* 2^128 hours is 2.8*10^24 times the age of the universe ;) */
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->power_on_hours), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "power_on_hours", "%s", u128_num_buf);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->unsafe_shutdowns), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "unsafe_shutdowns", "%s", u128_num_buf);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->media_errors), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "media_errors", "%s", u128_num_buf);
|
|
nvme_u128_to_json_val(nvme_le128_to_cpu(log->num_err_log_entries), u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "num_err_log_entries", "%s", u128_num_buf);
|
|
util_fmt_pair(FMT_DEFAULT, "warning_temp_time", "%d", le32toh(log->warning_temp_time));
|
|
util_fmt_pair(FMT_DEFAULT, "critical_comp_time", "%d", le32toh(log->critical_comp_time));
|
|
util_fmt_pair(FMT_DEFAULT, "temperature_sensor_1", "%d", le16toh(log->temp_sensor[0]));
|
|
util_fmt_pair(FMT_DEFAULT, "temperature_sensor_2", "%d", le16toh(log->temp_sensor[1]));
|
|
util_fmt_pair(FMT_DEFAULT, "temperature_sensor_3", "%d", le16toh(log->temp_sensor[2]));
|
|
util_fmt_pair(FMT_DEFAULT, "thm_temp1_trans_count", "%d",
|
|
le32toh(log->thm_temp1_trans_count));
|
|
util_fmt_pair(FMT_DEFAULT, "thm_temp2_trans_count", "%d",
|
|
le32toh(log->thm_temp2_trans_count));
|
|
util_fmt_pair(FMT_DEFAULT, "thm_temp1_total_time", "%d",
|
|
le32toh(log->thm_temp1_total_time));
|
|
util_fmt_pair(FMT_DEFAULT, "thm_temp2_total_time", "%d",
|
|
le32toh(log->thm_temp2_total_time));
|
|
util_fmt_obj_end(); /* smart-log */
|
|
|
|
if (ctx->opts.smart_blob)
|
|
zpcimon_json_base64_pair("smart-log-raw", (uint8_t *)log, sizeof(*log));
|
|
}
|
|
|
|
static void nvme_json_print(struct zpcimon_ctx *ctx, struct zpci_dev *zdev, const char *name,
|
|
struct nvme_smart_log *log)
|
|
{
|
|
zpci_adapter_json_print_start(zdev);
|
|
util_fmt_obj_start(FMT_DEFAULT, "nvmedev");
|
|
util_fmt_pair(FMT_QUOTE, "dev", name);
|
|
nvme_json_print_smart_log(ctx, log);
|
|
util_fmt_obj_end();
|
|
zpci_adapter_json_print_end();
|
|
fflush(stdout);
|
|
}
|
|
|
|
static int sclp_issue_nvme_smart_report(struct zpci_dev *zdev, const uint8_t *smart, int smart_len)
|
|
{
|
|
char *pci_addr;
|
|
int rc;
|
|
|
|
if (zdev->pft != ZPCI_PFT_NVME)
|
|
return -ENOTSUP;
|
|
pci_addr = zpci_pci_addr(zdev);
|
|
rc = zpci_sclp_issue_action(pci_addr, SCLP_ERRNOTIFY_AQ_NVME_SMART_DATA,
|
|
(char *)smart, smart_len, SCLP_ERRNOTIFY_ID_NVMEMON);
|
|
free(pci_addr);
|
|
return rc;
|
|
}
|
|
|
|
static int nvmemon_collect_adapter_data(struct zpcimon_ctx *ctx, struct zpci_dev *zdev)
|
|
{
|
|
struct nvme_smart_log log = {};
|
|
int nvme_fd, rc = -ENODEV;
|
|
char *dev, *pci_addr;
|
|
|
|
if (zdev->pft != ZPCI_PFT_NVME)
|
|
return -ENODEV;
|
|
|
|
pci_addr = zpci_pci_addr(zdev);
|
|
dev = zpci_get_nvme_device_node(pci_addr);
|
|
if (!dev)
|
|
goto exit_free_addr;
|
|
|
|
nvme_fd = openat(AT_FDCWD, dev, O_RDONLY);
|
|
if (nvme_fd < 0) {
|
|
warn("Failed to open %s", dev);
|
|
rc = -errno;
|
|
goto exit_free_dev;
|
|
}
|
|
|
|
rc = nvme_get_log_smart(nvme_fd, NVME_NSID_ALL, false, &log);
|
|
if (rc) {
|
|
warnx("Getting NVMe SMART log failed %d", rc);
|
|
goto exit_close;
|
|
}
|
|
if (!ctx->opts.quiet)
|
|
nvme_json_print(ctx, zdev, dev, &log);
|
|
if (ctx->opts.report) {
|
|
rc = sclp_issue_nvme_smart_report(zdev, (uint8_t *)&log, sizeof(log));
|
|
if (rc < 0 && rc != -ENOTSUP)
|
|
warnx("Error issuing SCLP for NVMe SMART log failed");
|
|
}
|
|
|
|
exit_close:
|
|
close(nvme_fd);
|
|
exit_free_dev:
|
|
free(dev);
|
|
exit_free_addr:
|
|
free(pci_addr);
|
|
return rc;
|
|
}
|
|
|
|
static int nvmemon_open_monitor(struct zpcimon_ctx *ctx)
|
|
{
|
|
struct nvmemon_ctx *nctx = &ctx->nvmemon_ctx;
|
|
int ret = -EINVAL;
|
|
|
|
nctx->udev = udev_new();
|
|
if (!nctx->udev)
|
|
return -ENOMEM;
|
|
|
|
nctx->mon = udev_monitor_new_from_netlink(nctx->udev, "kernel");
|
|
if (!nctx->mon) {
|
|
ret = -ENOMEM;
|
|
goto error_udev;
|
|
}
|
|
|
|
if (udev_monitor_filter_add_match_subsystem_devtype(nctx->mon, "nvme", NULL) < 0)
|
|
goto error_mon;
|
|
|
|
/* Note: This explicit udev_monitor_enable_receiving() is deprecated and not needed anymore
|
|
* in udev v257+. It's harmless though so we do it for easier backporting.
|
|
*/
|
|
if (udev_monitor_enable_receiving(nctx->mon) < 0)
|
|
goto error_mon;
|
|
|
|
return 0;
|
|
|
|
error_mon:
|
|
udev_monitor_unref(nctx->mon);
|
|
nctx->mon = NULL;
|
|
error_udev:
|
|
udev_unref(nctx->udev);
|
|
nctx->udev = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static int nvmemon_get_monitor_fd(struct zpcimon_ctx *ctx)
|
|
{
|
|
struct nvmemon_ctx *nctx = &ctx->nvmemon_ctx;
|
|
|
|
if (!nctx->mon)
|
|
return -1;
|
|
return udev_monitor_get_fd(nctx->mon);
|
|
}
|
|
|
|
static void nvmemon_monitor_fd_handle(struct zpcimon_ctx *ctx)
|
|
{
|
|
const char *action, *transport, *event_pci_addr;
|
|
struct nvmemon_ctx *nctx = &ctx->nvmemon_ctx;
|
|
struct udev_device *dev, *pci_dev;
|
|
struct zpci_dev *zdev;
|
|
char *zdev_pci_addr;
|
|
|
|
dev = udev_monitor_receive_device(nctx->mon);
|
|
if (!dev)
|
|
return;
|
|
|
|
action = udev_device_get_action(dev);
|
|
transport = udev_device_get_property_value(dev, "NVME_TRTYPE");
|
|
|
|
/* Change events are late enough that the /dev/nvmeX is ready for
|
|
* reading SMART data and we're only interested in directly PCIe
|
|
* attached NVMes
|
|
*/
|
|
if (action && strcmp(action, "change") == 0 && transport &&
|
|
strcmp(transport, "pcie") == 0) {
|
|
pci_dev = udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL);
|
|
if (!pci_dev)
|
|
goto out;
|
|
event_pci_addr = udev_device_get_sysname(pci_dev);
|
|
if (!event_pci_addr)
|
|
goto out;
|
|
|
|
zpci_list_reload(&ctx->zpci_list);
|
|
if (ctx->zpci_list) {
|
|
util_list_iterate(ctx->zpci_list, zdev) {
|
|
if (zdev->pft != ZPCI_PFT_NVME)
|
|
continue;
|
|
zdev_pci_addr = zpci_pci_addr(zdev);
|
|
if (strcmp(zdev_pci_addr, event_pci_addr) == 0) {
|
|
free(zdev_pci_addr);
|
|
nvmemon_collect_adapter_data(ctx, zdev);
|
|
break;
|
|
}
|
|
free(zdev_pci_addr);
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
udev_device_unref(dev);
|
|
}
|
|
|
|
static void nvmemon_close_monitor(struct zpcimon_ctx *ctx)
|
|
{
|
|
struct nvmemon_ctx *nctx = &ctx->nvmemon_ctx;
|
|
|
|
if (nctx->mon) {
|
|
udev_monitor_unref(nctx->mon);
|
|
nctx->mon = NULL;
|
|
}
|
|
if (nctx->udev) {
|
|
udev_unref(nctx->udev);
|
|
nctx->udev = NULL;
|
|
}
|
|
}
|
|
|
|
const struct zpcimon_ops nvmemon_ops = {
|
|
.collect_adapter_data = nvmemon_collect_adapter_data,
|
|
.open_monitor = nvmemon_open_monitor,
|
|
.get_monitor_fd = nvmemon_get_monitor_fd,
|
|
.monitor_fd_handle = nvmemon_monitor_fd_handle,
|
|
.close_monitor = nvmemon_close_monitor,
|
|
};
|