From 4fd607ae5ca9ec3714ca2354475f9a8b8caace09 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Thu, 23 Apr 2026 14:20:47 +0200 Subject: [PATCH] zpcimon: Monitor for hotplug of NVMes to trigger SMART collection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Niklas Schnelle Signed-off-by: Jan Höppner --- README.md | 2 +- zpcimon/Makefile | 18 ++++++++ zpcimon/nvmemon.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++ zpcimon/nvmemon.h | 7 ++- 4 files changed, 135 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab3dcd4e..35bb7ada 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ build options: | | | rust/pvattest, rust/pvsecret, | | libxml2 | `HAVE_LIBXML2` | libkmipclient | | systemd | `HAVE_SYSTEMD` | hsavmcore | -| libudev | `HAVE_LIBUDEV` | cpacfstatsd | +| libudev | `HAVE_LIBUDEV` | cpacfstatsd, zpcimon | | libnl3 | `HAVE_LIBNL3` | zpcimon | | libnvme | `HAVE_LIBNVME` | zpcimon | diff --git a/zpcimon/Makefile b/zpcimon/Makefile index ce815a31..a9792164 100644 --- a/zpcimon/Makefile +++ b/zpcimon/Makefile @@ -39,6 +39,16 @@ check_dep_libnvme: BUILDTARGET += check_dep_libnvme endif # HAVE_LIBNVME +ifneq (${HAVE_LIBUDEV},0) +check_dep_libudev: + $(call check_dep, \ + "zpcimon", \ + "libudev.h", \ + "systemd-devel", \ + "HAVE_LIBNVME=0") +BUILDTARGET += check_dep_libudev +endif # HAVE_LIBUDEV + ifeq (${HAVE_OPENSSL},0) all: @@ -61,6 +71,12 @@ all: install: $(SKIP) HAVE_LIBNVME=0 +else ifeq (${HAVE_LIBUDEV},0) +all: + $(SKIP) HAVE_LIBUDEV=0 + +install: + $(SKIP) HAVE_LIBUDEV=0 else ifneq ($(shell sh -c 'command -v pkg-config'),) @@ -76,6 +92,7 @@ LIB_LFLAGS += $(shell $(PKG_CONFIG) --silence-errors --libs libnl-route-3.0) LIB_LFLAGS += $(shell $(PKG_CONFIG) --silence-errors --libs libcrypto) LIB_LFLAGS += $(shell $(PKG_CONFIG) --silence-errors --libs libnvme) +LIB_LFLAGS += $(shell $(PKG_CONFIG) --silence-errors --libs libudev) else LIB_CFLAGS += -I /usr/include/libnl3/ LIB_LFLAGS += -lnl-route-3 -lnl-genl-3 -lnl-3 @@ -84,6 +101,7 @@ LIB_CFLAGS += -I /usr/include/openssl/ LIB_LFLAGS += -lcrypto LIB_LFLAGS += -lnvme +LIB_LFLAGS += -ludev endif ALL_CPPFLAGS += $(LIB_CFLAGS) diff --git a/zpcimon/nvmemon.c b/zpcimon/nvmemon.c index a516b4aa..14f088c0 100644 --- a/zpcimon/nvmemon.c +++ b/zpcimon/nvmemon.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "lib/pci_list.h" #include "lib/pci_sclp.h" @@ -195,6 +196,115 @@ exit_free_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, }; diff --git a/zpcimon/nvmemon.h b/zpcimon/nvmemon.h index 0658bcaa..b5a31193 100644 --- a/zpcimon/nvmemon.h +++ b/zpcimon/nvmemon.h @@ -7,7 +7,12 @@ #ifndef ZPCIMON_NVMEMON_H #define ZPCIMON_NVMEMON_H -struct nvmemon_ctx {}; +#include + +struct nvmemon_ctx { + struct udev *udev; + struct udev_monitor *mon; +}; extern const struct zpcimon_ops nvmemon_ops; #endif /* ZPCIMON_NVMEMON_H */