public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
To: git-commits@fedoraproject.org
Subject: [rpms/s390utils] rawhide: - include zpcimon feature
Date: Tue, 28 Jul 2026 09:37:36 GMT [thread overview]
Message-ID: <178523145619.1.14802920710269703868.rpms-s390utils-aef14f49a019@fedoraproject.org> (raw)
A new commit has been pushed.
Repo : rpms/s390utils
Branch : rawhide
Commit : aef14f49a0191f513073108840969270d29b204b
Author : Dan Horák <dan@danny.cz>
Date : 2026-07-28T09:35:29+00:00
Stats : +3385/-3 in 2 file(s)
URL : https://src.fedoraproject.org/rpms/s390utils/c/aef14f49a0191f513073108840969270d29b204b?branch=rawhide
Log:
- include zpcimon feature
---
diff --git a/s390utils-2.43.1-fedora.patch b/s390utils-2.43.1-fedora.patch
index e69de29..5c7544b 100644
--- a/s390utils-2.43.1-fedora.patch
+++ b/s390utils-2.43.1-fedora.patch
@@ -0,0 +1,3374 @@
+From 9d9fca6e72321fa83e2908f29ebe192257085833 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20H=C3=B6ppner?= <hoeppner@linux.ibm.com>
+Date: Wed, 22 Jul 2026 18:33:53 +0200
+Subject: [PATCH 01/23] Prepare for next release
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
+---
+ CHANGELOG.md | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/CHANGELOG.md b/CHANGELOG.md
+index 8da80dd0..97fa3457 100644
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -1,6 +1,16 @@
+ Release history for s390-tools (MIT version)
+ --------------------------------------------
+
++* __v2.xx.x (20xx-xx-xx)__
++
++ For Linux kernel version: 7.x
++
++ Add new tools / libraries:
++
++ Changes of existing tools:
++
++ Bug Fixes:
++
+ * __v2.43.1 (2026-07-22)__
+
+ For Linux kernel version: 7.1
+--
+2.55.0
+
+
+From e55e0e470a27cc224502fd3969fc99de677fc13c Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 16 Apr 2026 14:05:37 +0200
+Subject: [PATCH 02/23] opticsmon: Fix wrong JSON print for tx_fault
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The "tx_fault" field in the JSON output accidentally contained the
+rx_los value instead of the tx_fault one. Change it to the correct
+value. Note that this does not impact the data relayed to firmware as
+that uses the raw module data.
+
+Fixes: c34adb9cabee ("opticsmon: Introduce opticsmon tool")
+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>
+---
+ opticsmon/opticsmon.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/opticsmon/opticsmon.c b/opticsmon/opticsmon.c
+index ebae75ad..5a52f8a8 100644
+--- a/opticsmon/opticsmon.c
++++ b/opticsmon/opticsmon.c
+@@ -143,7 +143,7 @@ static void optics_json_print(struct opticsmon_ctx *ctx, struct zpci_netdev *nd,
+ util_fmt_pair(FMT_QUOTE, "type", optics_type_str(optics_type(oi)));
+ util_fmt_pair(FMT_QUOTE, "rx_los", optics_los_str(optics_rx_los(oi)));
+ util_fmt_pair(FMT_QUOTE, "tx_los", optics_los_str(optics_tx_los(oi)));
+- util_fmt_pair(FMT_QUOTE, "tx_fault", optics_los_str(optics_rx_los(oi)));
++ util_fmt_pair(FMT_QUOTE, "tx_fault", optics_los_str(optics_tx_fault(oi)));
+ if (ctx->opts.module_info)
+ module_info_pair(oi);
+ util_fmt_obj_end();
+--
+2.55.0
+
+
+From 905c3921af31b096149738a448e5e0120f538fb2 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 16 Apr 2026 13:47:58 +0200
+Subject: [PATCH 03/23] opticsmon: Close epoll fd in monitor_wait_loop()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The epoll fd (epfd) in monitor_wait_loop() is never closed. Close it in
+all return paths. Also there is no explicit check for an error return of
+epoll_create1() add that too.
+
+Fixes: c34adb9cabee ("opticsmon: Introduce opticsmon tool")
+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>
+---
+ opticsmon/opticsmon.c | 25 +++++++++++++++----------
+ 1 file changed, 15 insertions(+), 10 deletions(-)
+
+diff --git a/opticsmon/opticsmon.c b/opticsmon/opticsmon.c
+index 5a52f8a8..a7fa9fc2 100644
+--- a/opticsmon/opticsmon.c
++++ b/opticsmon/opticsmon.c
+@@ -271,54 +271,57 @@ void on_link_change(struct zpci_netdev *netdev, void *arg)
+ static int monitor_wait_loop(struct opticsmon_ctx *ctx, int sigfd, int timerfd)
+ {
+ struct epoll_event events[MAX_EVENTS];
++ int i, nlfd, epfd, nfds, ret = -EIO;
+ struct signalfd_siginfo fdsi;
+- int i, nlfd, epfd, nfds;
+ struct epoll_event ev;
+ uint64_t expirations;
+ ssize_t sread;
+
+ epfd = epoll_create1(EPOLL_CLOEXEC);
++ if (epfd < 0)
++ return -EIO;
+
+ ev.events = EPOLLIN;
+ ev.data.fd = sigfd;
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &ev) == -1)
+- return -EIO;
++ goto out_close;
+
+ ev.events = EPOLLIN;
+ ev.data.fd = timerfd;
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev) == -1)
+- return -EIO;
++ goto out_close;
+
+ nlfd = link_mon_nl_waitfd_getfd(&ctx->lctx);
+ ev.events = EPOLLIN;
+ ev.data.fd = nlfd;
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, nlfd, &ev) == -1)
+- return -EIO;
++ goto out_close;
+
+ while (1) {
+ nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
+ if (nfds < 0)
+- return nfds;
++ goto out_close;
+ for (i = 0; i < nfds; i++) {
+ /* signal fd */
+ if (events[i].data.fd == sigfd) {
+ sread = read(sigfd, &fdsi, sizeof(fdsi));
+ if (sread != sizeof(fdsi))
+- return -EIO;
++ goto out_close;
+ switch (fdsi.ssi_signo) {
+ case SIGINT:
+ case SIGTERM:
+ case SIGQUIT:
+- return 0;
++ ret = 0;
++ goto out_close;
+ /* Unexpected signal */
+ default:
+- return -EIO;
++ goto out_close;
+ }
+ /* timer fd */
+ } else if (events[i].data.fd == timerfd) {
+ sread = read(timerfd, &expirations, sizeof(uint64_t));
+ if (sread != sizeof(uint64_t))
+- return -EIO;
++ goto out_close;
+ if (!expirations)
+ continue;
+ dump_all_adapter_data(ctx);
+@@ -328,7 +331,9 @@ static int monitor_wait_loop(struct opticsmon_ctx *ctx, int sigfd, int timerfd)
+ }
+ }
+ }
+- return 0;
++out_close:
++ close(epfd);
++ return ret;
+ }
+
+ static int monitor_mode(struct opticsmon_ctx *ctx)
+--
+2.55.0
+
+
+From 1f339a9e7ee60611b5055735a0296326c792b704 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 16 Apr 2026 13:08:27 +0200
+Subject: [PATCH 04/23] opticsmon: Return errors from monitor_wait_loop() in
+ monitor_mode()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Don't ignore the return value of monitor_wait_loop() but instead forward
+it to the caller.
+
+Fixes: c34adb9cabee ("opticsmon: Introduce opticsmon tool")
+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>
+---
+ opticsmon/opticsmon.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/opticsmon/opticsmon.c b/opticsmon/opticsmon.c
+index a7fa9fc2..6d7595b1 100644
+--- a/opticsmon/opticsmon.c
++++ b/opticsmon/opticsmon.c
+@@ -381,7 +381,7 @@ static int monitor_mode(struct opticsmon_ctx *ctx)
+ goto close_timerfd;
+ }
+
+- monitor_wait_loop(ctx, sigfd, timerfd);
++ ret = monitor_wait_loop(ctx, sigfd, timerfd);
+
+ link_mon_nl_waitfd_destroy(&ctx->lctx);
+ util_fmt_exit();
+--
+2.55.0
+
+
+From 6847b6a8cf60510a63c5e4f32ca429ca9f5d24a7 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 16 Apr 2026 11:46:59 +0200
+Subject: [PATCH 05/23] opticsmon: Fix error path free of struct optics in
+ ethtool_nl_get_optics()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+When ethtool_nl_get_optics() fails after ethtool_nl_cb() has allocated
+oi->raw this memory would leak because only the struct optics* itself
+was freed. Fix this by using optics_free() and relying on free(oi->raw)
+being a no-op for NULL pointers in combination with using util_zalloc()
+to handle errors both before and after oi->raw was allocated.
+
+Fixes: c34adb9cabee ("opticsmon: Introduce opticsmon tool")
+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>
+---
+ opticsmon/ethtool.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/opticsmon/ethtool.c b/opticsmon/ethtool.c
+index 103644a1..a448eb25 100644
+--- a/opticsmon/ethtool.c
++++ b/opticsmon/ethtool.c
+@@ -272,7 +272,7 @@ int ethtool_nl_get_optics(struct ethtool_nl_ctx *ctx, const char *netdev, struct
+ return rc;
+
+ out_err_free_oi:
+- free(*oi);
++ optics_free(*oi);
+ *oi = NULL;
+ return rc;
+ }
+--
+2.55.0
+
+
+From 3696d6bd0858006bace1819c3ba436491f750c7b Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 16 Apr 2026 14:40:47 +0200
+Subject: [PATCH 06/23] opticsmon: Handle error return of ethtool_nl_connect()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Don't just ignore the return of ethtool_nl_connect(). This would also
+otherwise lead to invalid socket accesses later. Also make
+ethtool_nl_connect() return negative error values instead of
+EXIT_FAILURE.
+
+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>
+---
+ opticsmon/ethtool.c | 6 +++---
+ opticsmon/opticsmon.c | 4 +++-
+ 2 files changed, 6 insertions(+), 4 deletions(-)
+
+diff --git a/opticsmon/ethtool.c b/opticsmon/ethtool.c
+index a448eb25..ac1a07fb 100644
+--- a/opticsmon/ethtool.c
++++ b/opticsmon/ethtool.c
+@@ -56,13 +56,13 @@ int ethtool_nl_connect(struct ethtool_nl_ctx *ctx)
+ sk = nl_socket_alloc();
+ if (!sk) {
+ nl_perror(NLE_NOMEM, "alloc");
+- return EXIT_FAILURE;
++ return -ENOMEM;
+ }
+
+ rc = genl_connect(sk);
+ if (rc) {
+ nl_perror(rc, "connect");
+- rc = EXIT_FAILURE;
++ rc = -EIO;
+ goto err_free;
+ }
+
+@@ -72,7 +72,7 @@ int ethtool_nl_connect(struct ethtool_nl_ctx *ctx)
+ fprintf(stderr, "Ethtool netlink family not found\n");
+ else
+ nl_perror(ethtool_id, "ctrl resolve");
+- rc = EXIT_FAILURE;
++ rc = -EIO;
+ goto err_close;
+ }
+ ctx->sk = sk;
+diff --git a/opticsmon/opticsmon.c b/opticsmon/opticsmon.c
+index 6d7595b1..c50275d4 100644
+--- a/opticsmon/opticsmon.c
++++ b/opticsmon/opticsmon.c
+@@ -398,7 +398,9 @@ int main(int argc, char **argv)
+ int ret;
+
+ parse_cmdline(argc, argv, &ctx.opts);
+- ethtool_nl_connect(&ctx.ethtool_ctx);
++ ret = ethtool_nl_connect(&ctx.ethtool_ctx);
++ if (ret)
++ return ret;
+ if (ctx.opts.monitor)
+ ret = monitor_mode(&ctx);
+ else
+--
+2.55.0
+
+
+From 8b38bf37b06ad81d932f3545330c3daa64695bbe Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 30 Apr 2026 17:13:16 +0200
+Subject: [PATCH 07/23] opticsmon: Fix wrong size check for
+ OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The code accesses OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET so the size needs
+to be one larger for the LOS to be accessible.
+
+Fixes: c34adb9cabee ("opticsmon: Introduce opticsmon tool")
+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>
+---
+ opticsmon/optics_info.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/opticsmon/optics_info.c b/opticsmon/optics_info.c
+index fc041da3..00d8d723 100644
+--- a/opticsmon/optics_info.c
++++ b/opticsmon/optics_info.c
+@@ -66,7 +66,7 @@ bool optics_los_implemented(struct optics *oi)
+ } else if (type == OPTICS_TYPE_QSFP28) {
+ if (oi->size < OPTICS_QSFP28_LOS_OFFSET + 1)
+ return false;
+- if (oi->size < OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET)
++ if (oi->size < OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET + 1)
+ return false;
+ implemented = oi->raw[OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET];
+ /*
+--
+2.55.0
+
+
+From e7bc4a5d3103f745cad575a3acf48baf27a4d762 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Mon, 4 May 2026 13:41:34 +0200
+Subject: [PATCH 08/23] opticsmon: Enforce 1 second as minimal interval
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+So far it was possible for the user to specify an interval of 0 which
+would result in not re-arming the timer at all thus disabling interval
+based data collection. While this would still do optics data collection
+on startup and link changes the intention is to collect data at least
+every 24 hours. If interval is given as 0 treat this as minimum update
+interval of 1 second.
+
+Fixes: c34adb9cabee ("opticsmon: Introduce opticsmon tool")
+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>
+---
+ opticsmon/opticsmon.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/opticsmon/opticsmon.c b/opticsmon/opticsmon.c
+index c50275d4..f86056d4 100644
+--- a/opticsmon/opticsmon.c
++++ b/opticsmon/opticsmon.c
+@@ -96,6 +96,8 @@ static void parse_cmdline(int argc, char *argv[], struct options *opts)
+ }
+ if (seconds < 86400)
+ opts->interval_seconds = seconds;
++ if (seconds < 1)
++ opts->interval_seconds = 1;
+ break;
+ case 'h':
+ util_prg_print_help();
+--
+2.55.0
+
+
+From e8550a4f8dc61822c1816da5b17d18544868ee06 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Tue, 9 Dec 2025 17:53:57 +0100
+Subject: [PATCH 09/23] opticsmon: zpcimon: Rename opticsmon to zpcimon
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The opticsmon tool started out as a tool for monitoring the health of
+optical modules in directly attached PCI NICs. In the future however it
+will also monitor the health of other PCI devices. In particular in
+a first step it will monitor the health of directly attached NVMe
+devices.
+
+To reflect this broadening of its scope rename opticsmon to zpcimon. Add
+zpcimon.service and install it both under the new name and symlinked as
+opticsmon.service for backwards compatibility. Since users are expected
+to mostly just enable the service this keeps old instructions just
+working.
+
+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>
+---
+ .gitignore | 8 +++++---
+ Makefile | 2 +-
+ README.md | 10 +++++-----
+ scripts/dbginfo.sh | 2 +-
+ systemd/Makefile | 5 ++++-
+ ...pticsmon.service.in => zpcimon.service.in} | 6 +++---
+ {opticsmon => zpcimon}/Makefile | 20 ++++++++++---------
+ .../autocompletion_generator_host.c | 3 ++-
+ {opticsmon => zpcimon}/ethtool.c | 0
+ {opticsmon => zpcimon}/ethtool.h | 0
+ {opticsmon => zpcimon}/link_mon.c | 0
+ {opticsmon => zpcimon}/link_mon.h | 0
+ {opticsmon => zpcimon}/optics_info.c | 0
+ {opticsmon => zpcimon}/optics_info.h | 0
+ {opticsmon => zpcimon}/optics_sclp.c | 0
+ {opticsmon => zpcimon}/optics_sclp.h | 0
+ opticsmon/opticsmon.8 => zpcimon/zpcimon.8 | 10 +++++-----
+ opticsmon/opticsmon.c => zpcimon/zpcimon.c | 5 ++---
+ .../opticsmon_cli.h => zpcimon/zpcimon_cli.h | 0
+ 19 files changed, 39 insertions(+), 32 deletions(-)
+ rename systemd/{opticsmon.service.in => zpcimon.service.in} (66%)
+ rename {opticsmon => zpcimon}/Makefile (79%)
+ rename {opticsmon => zpcimon}/autocompletion_generator_host.c (72%)
+ rename {opticsmon => zpcimon}/ethtool.c (100%)
+ rename {opticsmon => zpcimon}/ethtool.h (100%)
+ rename {opticsmon => zpcimon}/link_mon.c (100%)
+ rename {opticsmon => zpcimon}/link_mon.h (100%)
+ rename {opticsmon => zpcimon}/optics_info.c (100%)
+ rename {opticsmon => zpcimon}/optics_info.h (100%)
+ rename {opticsmon => zpcimon}/optics_sclp.c (100%)
+ rename {opticsmon => zpcimon}/optics_sclp.h (100%)
+ rename opticsmon/opticsmon.8 => zpcimon/zpcimon.8 (88%)
+ rename opticsmon/opticsmon.c => zpcimon/zpcimon.c (98%)
+ rename opticsmon/opticsmon_cli.h => zpcimon/zpcimon_cli.h (100%)
+
+diff --git a/.gitignore b/.gitignore
+index 36a2c851..37ec4d8b 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -102,9 +102,11 @@ lsstp/_lsstp
+ lsstp/lsstp.bash
+ mon_tools/mon_fsstatd
+ mon_tools/mon_procd
+-opticsmon/opticsmon
+-opticsmon/_opticsmon
+-opticsmon/opticsmon.bash
++zpcimon/zpcimon
++zpcimon/zpcimon.bash
++zpcimon/_zpcimon
++zpcimon/opticsmon.bash
++zpcimon/_opticsmon
+ osasnmpd/osasnmpd
+ qetharp/qetharp
+ qethqoat/qethqoat
+diff --git a/Makefile b/Makefile
+index 4dbce8cc..5aafb56c 100644
+--- a/Makefile
++++ b/Makefile
+@@ -15,7 +15,7 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
+ vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
+ ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
+ systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
+- lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon \
++ lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust zpcimon \
+ zpwr zmemtopo
+
+ else
+diff --git a/README.md b/README.md
+index 91d61c5a..4045b0f8 100644
+--- a/README.md
++++ b/README.md
+@@ -316,7 +316,7 @@ build options:
+ | glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
+ | openssl | `HAVE_OPENSSL` | zkey, libekmfweb, libkmipclient, |
+ | | | zgetdump, rust/pvattest, rust/pvimg, |
+-| | | zgetdump/pvsecret, opticsmon |
++| | | zgetdump/pvsecret, zpcimon |
+ | cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
+ | json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
+ | | | libkmipclient |
+@@ -326,7 +326,7 @@ build options:
+ | libxml2 | `HAVE_LIBXML2` | libkmipclient |
+ | systemd | `HAVE_SYSTEMD` | hsavmcore |
+ | libudev | `HAVE_LIBUDEV` | cpacfstatsd |
+-| libnl3 | `HAVE_LIBNL3` | opticsmon |
++| libnl3 | `HAVE_LIBNL3` | zpcimon |
+
+ This table lists additional build or install options:
+
+@@ -374,10 +374,10 @@ the different tools are provided:
+
+ The runtime requirements are: openssl-libs (>= 1.1.1) and libcurl.
+
+-* opticsmon:
+- For building opticsmon OpenSSL and the Netlink Library Suite (libnl3) are
++* zpcimon:
++ For building zpcimon OpenSSL and the Netlink Library Suite (libnl3) are
+ required.
+- Tip: you may skip the opticsmon build by adding
++ Tip: you may skip the zpcimon build by adding
+ `HAVE_OPENSSL=0` or `HAVE_LIBNL3=0`
+
+ * osasnmpd:
+diff --git a/scripts/dbginfo.sh b/scripts/dbginfo.sh
+index e5c35bbf..ec6227f1 100755
+--- a/scripts/dbginfo.sh
++++ b/scripts/dbginfo.sh
+@@ -629,7 +629,7 @@ NETWORK_CMDS="ip -br a\
+ :nstat -az\
+ :openssl engine # deprecated in OpenSSL 3.n\
+ :openssl list --providers 2>/dev/null # redirect help on older systems\
+- :opticsmon --module-info\
++ :opticsmon --module-info\ # Now called zpcimon but kept for compatibility
+ :route -n\
+ "
+
+diff --git a/systemd/Makefile b/systemd/Makefile
+index 9388fd9c..29e29b63 100644
+--- a/systemd/Makefile
++++ b/systemd/Makefile
+@@ -4,7 +4,7 @@ SYSTEM_UNITS = ttyrun-getty@.service iucvtty-login@.service \
+ cpacfstatsd.service cpuplugd.service \
+ dumpconf.service cpi.service \
+ mon_fsstatd.service mon_procd.service \
+- opticsmon.service
++ zpcimon.service
+
+ all:
+
+@@ -19,6 +19,9 @@ install: system_units
+ $(INSTALL) -g $(GROUP) -o $(OWNER) \
+ -m 644 $$unit $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) ; \
+ done
++ # Make the legacy opticsmon.service a link to zpcimon.service
++ ln -sf zpcimon.service \
++ $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)/opticsmon.service
+
+ else
+
+diff --git a/systemd/opticsmon.service.in b/systemd/zpcimon.service.in
+similarity index 66%
+rename from systemd/opticsmon.service.in
+rename to systemd/zpcimon.service.in
+index 335d9165..cf1ea122 100644
+--- a/systemd/opticsmon.service.in
++++ b/systemd/zpcimon.service.in
+@@ -1,5 +1,5 @@
+ #
+-# Systemd unit for mon_fsstatd (monitor z/VM guest file systems)
++# Systemd unit for zpcimon PCI NIC optical module monitoring
+ #
+ # Copyright IBM Corp. 2024
+ #
+@@ -9,11 +9,11 @@
+
+ [Unit]
+ Description=Monitor health of directly attached PCI NIC optical modules
+-# Documentation=man:opticsmon
++# Documentation=man:zpcimon
+
+ [Service]
+ Type=exec
+-ExecStart=@usrsbin_path@/opticsmon --send-report --monitor --interval 86400
++ExecStart=@usrsbin_path@/zpcimon --send-report --monitor --interval 86400
+ KillMode=control-group
+
+ [Install]
+diff --git a/opticsmon/Makefile b/zpcimon/Makefile
+similarity index 79%
+rename from opticsmon/Makefile
+rename to zpcimon/Makefile
+index e42b6329..43fc2a04 100644
+--- a/opticsmon/Makefile
++++ b/zpcimon/Makefile
+@@ -2,8 +2,8 @@ include ../common.mak
+
+ TESTS := tests/
+
+-zsh-completions = _opticsmon
+-bash-completions = opticsmon.bash
++zsh-completions = _zpcimon
++bash-completions = zpcimon.bash
+
+ include ../common_autocomp.mak
+
+@@ -12,7 +12,7 @@ libs =$(rootdir)/libzpci/libzpci.a $(rootdir)/libutil/libutil.a
+ ifneq (${HAVE_OPENSSL},0)
+ check_dep_openssl:
+ $(call check_dep, \
+- "opticsmon", \
++ "zpcimon", \
+ "openssl/evp.h", \
+ "openssl-devel", \
+ "HAVE_OPENSSL=0")
+@@ -22,7 +22,7 @@ endif # HAVE_OPENSSL
+ ifneq (${HAVE_LIBNL3},0)
+ check_dep_libnl3:
+ $(call check_dep, \
+- "opticsmon", \
++ "zpcimon", \
+ "netlink/socket.h", \
+ "libnl3-devel", \
+ "HAVE_LIBNL3=0")
+@@ -69,20 +69,22 @@ endif
+ ALL_CPPFLAGS += $(LIB_CFLAGS)
+ LDLIBS += $(LIB_LFLAGS)
+
+-BUILDTARGET += opticsmon
++BUILDTARGET += zpcimon
+
+ all: ${BUILDTARGET}
+
+-opticsmon: opticsmon.o optics_info.o optics_sclp.o ethtool.o link_mon.o $(libs)
++zpcimon: zpcimon.o optics_info.o optics_sclp.o ethtool.o link_mon.o $(libs)
+
+ install: all
+ $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8
+- $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 opticsmon $(DESTDIR)$(BINDIR)
+- $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 opticsmon.8 \
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zpcimon $(DESTDIR)$(BINDIR)
++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 zpcimon.8 \
+ $(DESTDIR)$(MANDIR)/man8
++ ln -sf zpcimon $(DESTDIR)$(BINDIR)/opticsmon
++ ln -sf zpcimon.8 $(DESTDIR)$(MANDIR)/man8/opticsmon.8
+ endif # HAVE_OPENSSL3=0 or HAVE_LIBNL3=0
+
+ clean:
+- rm -f *.o *~ opticsmon core
++ rm -f *.o *~ zpcimon core
+
+ .PHONY: all install clean
+diff --git a/opticsmon/autocompletion_generator_host.c b/zpcimon/autocompletion_generator_host.c
+similarity index 72%
+rename from opticsmon/autocompletion_generator_host.c
+rename to zpcimon/autocompletion_generator_host.c
+index 4ce9fdf1..aeefab2f 100644
+--- a/opticsmon/autocompletion_generator_host.c
++++ b/zpcimon/autocompletion_generator_host.c
+@@ -6,10 +6,11 @@
+
+ #include "lib/util_autocomp.h"
+
+-#include "opticsmon_cli.h"
++#include "zpcimon_cli.h"
+
+ int main(void)
+ {
++ generate_autocomp(opt_vec, "zpcimon");
+ generate_autocomp(opt_vec, "opticsmon");
+
+ return 0;
+diff --git a/opticsmon/ethtool.c b/zpcimon/ethtool.c
+similarity index 100%
+rename from opticsmon/ethtool.c
+rename to zpcimon/ethtool.c
+diff --git a/opticsmon/ethtool.h b/zpcimon/ethtool.h
+similarity index 100%
+rename from opticsmon/ethtool.h
+rename to zpcimon/ethtool.h
+diff --git a/opticsmon/link_mon.c b/zpcimon/link_mon.c
+similarity index 100%
+rename from opticsmon/link_mon.c
+rename to zpcimon/link_mon.c
+diff --git a/opticsmon/link_mon.h b/zpcimon/link_mon.h
+similarity index 100%
+rename from opticsmon/link_mon.h
+rename to zpcimon/link_mon.h
+diff --git a/opticsmon/optics_info.c b/zpcimon/optics_info.c
+similarity index 100%
+rename from opticsmon/optics_info.c
+rename to zpcimon/optics_info.c
+diff --git a/opticsmon/optics_info.h b/zpcimon/optics_info.h
+similarity index 100%
+rename from opticsmon/optics_info.h
+rename to zpcimon/optics_info.h
+diff --git a/opticsmon/optics_sclp.c b/zpcimon/optics_sclp.c
+similarity index 100%
+rename from opticsmon/optics_sclp.c
+rename to zpcimon/optics_sclp.c
+diff --git a/opticsmon/optics_sclp.h b/zpcimon/optics_sclp.h
+similarity index 100%
+rename from opticsmon/optics_sclp.h
+rename to zpcimon/optics_sclp.h
+diff --git a/opticsmon/opticsmon.8 b/zpcimon/zpcimon.8
+similarity index 88%
+rename from opticsmon/opticsmon.8
+rename to zpcimon/zpcimon.8
+index a810792d..3c274b45 100644
+--- a/opticsmon/opticsmon.8
++++ b/zpcimon/zpcimon.8
+@@ -30,20 +30,20 @@
+ . br
+ ..
+ .
+-.TH opticsmon 8 "Oct 2024" s390-tools zpcictl
++.TH zpcimon 8 "Oct 2024" s390-tools zpcictl
+ .
+ .SH NAME
+-opticsmon - Monitor optical modules for directly attached PCI based NICs
++zpcimon - Monitor optical modules for directly attached PCI based NICs
+ .
+ .
+ .SH SYNOPSIS
+-.B "opticsmon"
++.B "zpcimon"
+ .I "OPTIONS"
+ .
+ .
+ .SH DESCRIPTION
+ Use
+-.B opticsmon
++.B zpcimon
+ to monitor the health of the optical modules of directly attached PCI based
+ NICs. When executed without the \fB--monitor\fR option it will collect optical
+ module data from all available PCI network interface physical functions and
+@@ -80,7 +80,7 @@ on".
+
+ .B Example: Extract module information for the first adapter
+
+-.CL opticsmon --module-info | jq -r '.adapters[0].netdevs[0].optics.module_info' | base64 -d | hexdump -C
++.CL zpcimon --module-info | jq -r '.adapters[0].netdevs[0].optics.module_info' | base64 -d | hexdump -C
+ .PP
+ .PP
+ .
+diff --git a/opticsmon/opticsmon.c b/zpcimon/zpcimon.c
+similarity index 98%
+rename from opticsmon/opticsmon.c
+rename to zpcimon/zpcimon.c
+index f86056d4..7df87e81 100644
+--- a/opticsmon/opticsmon.c
++++ b/zpcimon/zpcimon.c
+@@ -31,7 +31,7 @@
+ #include "link_mon.h"
+ #include "optics_info.h"
+ #include "optics_sclp.h"
+-#include "opticsmon_cli.h"
++#include "zpcimon_cli.h"
+
+ #define API_LEVEL 1
+
+@@ -52,8 +52,7 @@ struct opticsmon_ctx {
+ };
+
+ static const struct util_prg prg = {
+- .desc = "Use opticsmon to monitor the health of the optical modules\n"
+- "of directly attached PCI based NICs",
++ .desc = "Use zpcimon to monitor the health of PCI devices",
+ .copyright_vec = { {
+ .owner = "IBM Corp.",
+ .pub_first = 2024,
+diff --git a/opticsmon/opticsmon_cli.h b/zpcimon/zpcimon_cli.h
+similarity index 100%
+rename from opticsmon/opticsmon_cli.h
+rename to zpcimon/zpcimon_cli.h
+--
+2.55.0
+
+
+From 8a4a4e05579eb697721b59b8e718cdb1d96aeb5f Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Wed, 17 Jun 2026 21:45:35 +0200
+Subject: [PATCH 10/23] libutil/util_fmt: Add util_fmt_type_to_name()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add a function to get the textual name of an enum util_fmt_t value. To
+make this robust to changes in the order of elements in the format array
+initialize this using named indices.
+
+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>
+---
+ include/lib/util_fmt.h | 10 ++++++++++
+ libutil/util_fmt.c | 30 +++++++++++++++++++++++++-----
+ 2 files changed, 35 insertions(+), 5 deletions(-)
+
+diff --git a/include/lib/util_fmt.h b/include/lib/util_fmt.h
+index 1d3b9af9..ac57cd4f 100644
+--- a/include/lib/util_fmt.h
++++ b/include/lib/util_fmt.h
+@@ -157,6 +157,16 @@ void util_fmt_exit(void);
+ */
+ bool util_fmt_name_to_type(const char *name, enum util_fmt_t *type);
+
++/**
++ * util_fmt_type_to_name() - Get name of given format type identifier.
++ * @type: Format type identifier
++ *
++ * Get the name corresponding to the given format type identifier.
++ *
++ * Return: name of the format type
++ */
++const char *util_fmt_type_to_name(enum util_fmt_t type);
++
+ /**
+ * util_fmt_set_indent() - Set indentation parameters.
+ * @base : Base indentation level to apply to all output lines (default 0)
+diff --git a/libutil/util_fmt.c b/libutil/util_fmt.c
+index 31bbe704..f4e05fae 100644
+--- a/libutil/util_fmt.c
++++ b/libutil/util_fmt.c
+@@ -89,11 +89,26 @@ static const struct {
+ const char *name;
+ enum util_fmt_t fmt;
+ } formats[] = {
+- { "json", FMT_JSON },
+- { "json-seq", FMT_JSONSEQ },
+- { "jsonl", FMT_JSONL },
+- { "pairs", FMT_PAIRS },
+- { "csv", FMT_CSV },
++ [FMT_JSON] = {
++ .name = "json",
++ .fmt = FMT_JSON
++ },
++ [FMT_JSONSEQ] = {
++ .name = "json-seq",
++ .fmt = FMT_JSONSEQ
++ },
++ [FMT_JSONL] = {
++ .name = "jsonl",
++ .fmt = FMT_JSONL
++ },
++ [FMT_PAIRS] = {
++ .name = "pairs",
++ .fmt = FMT_PAIRS
++ },
++ [FMT_CSV] = {
++ .name = "csv",
++ .fmt = FMT_CSV
++ },
+ };
+
+ /* Signal mask for blocking INT and TERM signals. */
+@@ -112,6 +127,11 @@ bool util_fmt_name_to_type(const char *name, enum util_fmt_t *type)
+ return false;
+ }
+
++const char *util_fmt_type_to_name(enum util_fmt_t type)
++{
++ return formats[type].name;
++}
++
+ bool util_fmt_is_json(enum util_fmt_t type)
+ {
+ switch (type) {
+--
+2.55.0
+
+
+From 2c63c69af448fd84222645f53aa98cdfd54c7130 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Wed, 17 Jun 2026 21:48:47 +0200
+Subject: [PATCH 11/23] zpcimon: Allow setting output format and use JSON Lines
+ in systemd unit
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Introduce separate '--format' option to set output format. Use the new
+JSON Lines format explicitly in the systemd unit. Since JSON Lines
+format only uses printable characters this removes the need for passing
+'-a' to systemctl status. At the same time JSON-SEQ is more easily
+parsable using 'jq --seq' so keep it the default for easier scripting.
+
+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>
+---
+ systemd/zpcimon.service.in | 2 +-
+ zpcimon/zpcimon.8 | 8 +++++++
+ zpcimon/zpcimon.c | 43 ++++++++++++++++++++++++++++++++++++--
+ zpcimon/zpcimon_cli.h | 9 ++++++++
+ 4 files changed, 59 insertions(+), 3 deletions(-)
+
+diff --git a/systemd/zpcimon.service.in b/systemd/zpcimon.service.in
+index cf1ea122..9be0caa4 100644
+--- a/systemd/zpcimon.service.in
++++ b/systemd/zpcimon.service.in
+@@ -13,7 +13,7 @@ Description=Monitor health of directly attached PCI NIC optical modules
+
+ [Service]
+ Type=exec
+-ExecStart=@usrsbin_path@/zpcimon --send-report --monitor --interval 86400
++ExecStart=@usrsbin_path@/zpcimon --send-report --monitor --format jsonl --interval 86400
+ KillMode=control-group
+
+ [Install]
+diff --git a/zpcimon/zpcimon.8 b/zpcimon/zpcimon.8
+index 3c274b45..9b16fa91 100644
+--- a/zpcimon/zpcimon.8
++++ b/zpcimon/zpcimon.8
+@@ -84,6 +84,14 @@ on".
+ .PP
+ .PP
+ .
++.SS Output Format Options
++.OD format "" <FORMAT>
++Set the output format out of json, json-seq, jsonl, pairs, and csv. Note that
++only json-seq and jsonl sequence formats are supported for monitoring mode as
++they handle a continuous stream of updates well. While they are not supported
++in query mode.
++.PP
++.
+ .SS General Options
+ .OD help "h" ""
+ Print usage information, then exit.
+diff --git a/zpcimon/zpcimon.c b/zpcimon/zpcimon.c
+index 7df87e81..98a2871a 100644
+--- a/zpcimon/zpcimon.c
++++ b/zpcimon/zpcimon.c
+@@ -40,6 +40,8 @@ struct options {
+ bool report;
+ bool module_info;
+ bool quiet;
++ enum util_fmt_t format;
++ bool explicit_format;
+
+ uint32_t interval_seconds;
+ };
+@@ -63,6 +65,7 @@ static const struct util_prg prg = {
+
+ static void parse_cmdline(int argc, char *argv[], struct options *opts)
+ {
++ enum util_fmt_t fmt;
+ uint32_t seconds;
+ int cmd, ret;
+
+@@ -85,6 +88,12 @@ static void parse_cmdline(int argc, char *argv[], struct options *opts)
+ case OPT_DUMP:
+ opts->module_info = true;
+ break;
++ case OPT_FORMAT:
++ if (!util_fmt_name_to_type(optarg, &fmt))
++ errx(EXIT_FAILURE, "Unknown format %s", optarg);
++ opts->format = fmt;
++ opts->explicit_format = true;
++ break;
+ case 'i':
+ ret = sscanf(optarg, "%u", &seconds);
+ if (ret != 1) {
+@@ -224,7 +233,7 @@ static void dump_all_adapter_data(struct opticsmon_ctx *ctx)
+
+ static int oneshot_mode(struct opticsmon_ctx *ctx)
+ {
+- util_fmt_init(stdout, FMT_JSON, FMT_DEFAULT, API_LEVEL);
++ util_fmt_init(stdout, ctx->opts.format, FMT_DEFAULT, API_LEVEL);
+ if (!ctx->opts.quiet)
+ util_fmt_obj_start(FMT_LIST, "adapters");
+ dump_all_adapter_data(ctx);
+@@ -375,7 +384,7 @@ static int monitor_mode(struct opticsmon_ctx *ctx)
+ goto close_timerfd;
+ }
+
+- util_fmt_init(stdout, FMT_JSONSEQ, FMT_DEFAULT, API_LEVEL);
++ util_fmt_init(stdout, ctx->opts.format, FMT_DEFAULT, API_LEVEL);
+ ret = link_mon_nl_waitfd_create(&ctx->lctx, on_link_change, ctx);
+ if (ret) {
+ fprintf(stderr, "Failed to create link monitoring socket\n");
+@@ -393,12 +402,42 @@ close_timerfd:
+ return ret;
+ }
+
++static bool is_supported_fmt(enum util_fmt_t fmt, bool monitor)
++{
++ switch (fmt) {
++ case FMT_JSON:
++ case FMT_PAIRS:
++ return monitor ? false : true;
++ case FMT_JSONL:
++ case FMT_JSONSEQ:
++ return monitor ? true : false;
++ default:
++ return false;
++ }
++}
++
++static int set_format(struct options *opts)
++{
++ if (!opts->explicit_format)
++ opts->format = (opts->monitor) ? FMT_JSONSEQ : FMT_JSON;
++
++ if (!is_supported_fmt(opts->format, opts->monitor)) {
++ warnx("Format %s is not supported in %s mode",
++ util_fmt_type_to_name(opts->format), (opts->monitor) ? "monitor" : "query");
++ return -EINVAL;
++ }
++ return 0;
++}
++
+ int main(int argc, char **argv)
+ {
+ struct opticsmon_ctx ctx = { .opts = { .interval_seconds = 86400 } };
+ int ret;
+
+ parse_cmdline(argc, argv, &ctx.opts);
++ ret = set_format(&ctx.opts);
++ if (ret)
++ return ret;
+ ret = ethtool_nl_connect(&ctx.ethtool_ctx);
+ if (ret)
+ return ret;
+diff --git a/zpcimon/zpcimon_cli.h b/zpcimon/zpcimon_cli.h
+index c69f349a..f5a0dc3e 100644
+--- a/zpcimon/zpcimon_cli.h
++++ b/zpcimon/zpcimon_cli.h
+@@ -7,9 +7,11 @@
+ #ifndef OPTICSMON_CLI_H
+ #define OPTICSMON_CLI_H
+
++#include "lib/util_fmt.h"
+ #include "lib/util_opt.h"
+
+ #define OPT_DUMP 128
++#define OPT_FORMAT 129
+
+ static struct util_opt opt_vec[] = {
+ UTIL_OPT_SECTION("OPERATION OPTIONS"),
+@@ -41,6 +43,13 @@ static struct util_opt opt_vec[] = {
+ "in the absence of link state changes. A value larger than "
+ "24 hours (86400 seconds) is clamped down to 24 hours.",
+ },
++ UTIL_OPT_SECTION("OUTPUT FORMAT OPTIONS"),
++ {
++ .option = { "format", required_argument, NULL, OPT_FORMAT },
++ .argument = "FORMAT",
++ .flags = UTIL_OPT_FLAG_NOSHORT,
++ .desc = "Output format (" FMT_TYPE_NAMES ")",
++ },
+ UTIL_OPT_SECTION("GENERAL OPTIONS"),
+ UTIL_OPT_HELP,
+ UTIL_OPT_VERSION,
+--
+2.55.0
+
+
+From f9ef8a010669e651d736e23a1dd76e124483677a Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Mon, 13 Apr 2026 13:52:37 +0200
+Subject: [PATCH 12/23] util_time: zpcimon: Use a constant for seconds per day
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Replace the magic value 86400 with a new util_time.h constant
+SEC_PER_DAY.
+
+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>
+---
+ include/lib/util_time.h | 2 ++
+ zpcimon/zpcimon.c | 3 ++-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/include/lib/util_time.h b/include/lib/util_time.h
+index 42b9741a..21e2d78e 100644
+--- a/include/lib/util_time.h
++++ b/include/lib/util_time.h
+@@ -19,6 +19,8 @@
+
+ #define MSEC_PER_SEC 1000L
+
++#define SEC_PER_DAY (24 * 60 * 60)
++
+ /**
+ * Convert nanoseconds to microseconds
+ *
+diff --git a/zpcimon/zpcimon.c b/zpcimon/zpcimon.c
+index 98a2871a..40b6edd7 100644
+--- a/zpcimon/zpcimon.c
++++ b/zpcimon/zpcimon.c
+@@ -24,6 +24,7 @@
+ #include "lib/util_opt.h"
+ #include "lib/util_fmt.h"
+ #include "lib/util_libc.h"
++#include "lib/util_time.h"
+
+ #include <openssl/evp.h>
+
+@@ -102,7 +103,7 @@ static void parse_cmdline(int argc, char *argv[], struct options *opts)
+ optarg);
+ exit(EXIT_FAILURE);
+ }
+- if (seconds < 86400)
++ if (seconds < SEC_PER_DAY)
+ opts->interval_seconds = seconds;
+ if (seconds < 1)
+ opts->interval_seconds = 1;
+--
+2.55.0
+
+
+From 113e9ebfef67253f4faee798d2fe785f182f9eec Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 9 Apr 2026 15:32:47 +0200
+Subject: [PATCH 13/23] zpcimon: Split optics monitoring out into separate
+ monitor
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Introduce the concept of a generic zPCI device monitor by creating the
+zpcimon_ops operations struct turning all optics specific calls into
+abstract monitor calls. Handle monitors as a static array of zpcimon_ops
+based monitors of which the now split out optics monitor is currently
+the only one. All operations are in principle optional though a monitor
+which provides an init operation must also provide the corresponding
+destroy operation.
+
+Keep the base64 based JSON pair as non optics specific for later re-use
+and make it just skip the output in the very unlikely case that encoding
+fails. In follow on commits a monitor for NVMe devices collecting SMART
+data will be added.
+
+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>
+---
+ zpcimon/Makefile | 2 +-
+ zpcimon/opticsmon.c | 177 +++++++++++++++++++
+ zpcimon/opticsmon.h | 20 +++
+ zpcimon/zpcimon.c | 406 ++++++++++++++++++++++----------------------
+ zpcimon/zpcimon.h | 52 ++++++
+ 5 files changed, 454 insertions(+), 203 deletions(-)
+ create mode 100644 zpcimon/opticsmon.c
+ create mode 100644 zpcimon/opticsmon.h
+ create mode 100644 zpcimon/zpcimon.h
+
+diff --git a/zpcimon/Makefile b/zpcimon/Makefile
+index 43fc2a04..cc0a8af0 100644
+--- a/zpcimon/Makefile
++++ b/zpcimon/Makefile
+@@ -73,7 +73,7 @@ BUILDTARGET += zpcimon
+
+ all: ${BUILDTARGET}
+
+-zpcimon: zpcimon.o optics_info.o optics_sclp.o ethtool.o link_mon.o $(libs)
++zpcimon: zpcimon.o opticsmon.o optics_info.o optics_sclp.o ethtool.o link_mon.o $(libs)
+
+ install: all
+ $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8
+diff --git a/zpcimon/opticsmon.c b/zpcimon/opticsmon.c
+new file mode 100644
+index 00000000..4da456f1
+--- /dev/null
++++ b/zpcimon/opticsmon.c
+@@ -0,0 +1,177 @@
++/*
++ * zpcimon - Report monitoring data to firmware
++ *
++ * Copyright IBM Corp. 2024
++ *
++ * 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 <errno.h>
++#include <linux/if.h>
++#include <stdbool.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++#include "lib/pci_list.h"
++#include "lib/util_fmt.h"
++#include "lib/util_libc.h"
++
++#include "ethtool.h"
++#include "link_mon.h"
++#include "optics_info.h"
++#include "optics_sclp.h"
++#include "opticsmon.h"
++#include "zpcimon.h"
++
++static void optics_json_print(struct options *opts, struct zpci_netdev *nd, struct optics *oi)
++{
++ util_fmt_obj_start(FMT_DEFAULT, "netdev");
++ util_fmt_pair(FMT_QUOTE, "name", nd->name);
++ util_fmt_pair(FMT_QUOTE, "operstate", zpci_operstate_str(nd->operstate));
++ util_fmt_obj_start(FMT_DEFAULT, "optics");
++ util_fmt_pair(FMT_QUOTE, "type", optics_type_str(optics_type(oi)));
++ util_fmt_pair(FMT_QUOTE, "rx_los", optics_los_str(optics_rx_los(oi)));
++ util_fmt_pair(FMT_QUOTE, "tx_los", optics_los_str(optics_tx_los(oi)));
++ util_fmt_pair(FMT_QUOTE, "tx_fault", optics_los_str(optics_tx_fault(oi)));
++ if (opts->module_info)
++ zpcimon_json_base64_pair("module_info", oi->raw, (int)oi->size);
++ util_fmt_obj_end();
++ util_fmt_obj_end();
++}
++
++static int opticsmon_collect_adapter_data(struct zpcimon_ctx *ctx, struct zpci_dev *zdev)
++{
++ struct options *opts = &ctx->opts;
++ struct optics **ois;
++ int num_ois = 0;
++ char *pci_addr;
++ int i, rc;
++
++ /* Filter non-NIC devices and VFs */
++ if (zpci_is_vf(zdev) || !zdev->num_netdevs)
++ return -ENODEV;
++ ois = (struct optics **)util_zalloc(sizeof(ois[0]) * zdev->num_netdevs);
++ for (i = 0; i < zdev->num_netdevs; i++) {
++ rc = ethtool_nl_get_optics(&ctx->opticsmon_ctx.ethtool_ctx, zdev->netdevs[i].name,
++ &ois[i]);
++ if (rc)
++ goto free_ois;
++ num_ois++;
++ }
++ if (!opts->quiet) {
++ util_fmt_obj_start(FMT_DEFAULT, "adapter");
++ util_fmt_pair(FMT_QUOTE, "pft", zpci_pft_str(zdev));
++ util_fmt_obj_start(FMT_DEFAULT, "ids");
++ util_fmt_pair(FMT_QUOTE, "fid", "0x%0x", zdev->fid);
++ if (zdev->uid_is_unique)
++ util_fmt_pair(FMT_QUOTE, "uid", "0x%0x", zdev->uid);
++ pci_addr = zpci_pci_addr(zdev);
++ util_fmt_pair(FMT_QUOTE, "pci_address", pci_addr);
++ free(pci_addr);
++ util_fmt_obj_end();
++ util_fmt_obj_start(FMT_LIST, "netdevs");
++ for (i = 0; i < zdev->num_netdevs; i++)
++ optics_json_print(opts, &zdev->netdevs[i], ois[i]);
++ util_fmt_obj_end(); /* netdevs list */
++ util_fmt_obj_end(); /* adapter */
++ fflush(stdout);
++ }
++ if (opts->report) {
++ for (i = 0; i < zdev->num_netdevs; i++) {
++ rc = sclp_issue_optics_report(zdev, ois[i]);
++ if (rc == -ENOTSUP) {
++ fprintf(stderr, "Skipping %s which does not support reporting\n",
++ zdev->netdevs[i].name);
++ } else if (rc < 0) {
++ fprintf(stderr, "Error issuing SCLP for optics data failed: %s\n",
++ strerror(-rc));
++ }
++ }
++ }
++free_ois:
++ for (i = 0; i < num_ois; i++)
++ optics_free(ois[i]);
++ free((void *)ois);
++ return rc;
++}
++
++static void on_link_change(struct zpci_netdev *change_netdev, void *arg)
++{
++ struct zpcimon_ctx *ctx = arg;
++ struct zpci_dev *zdev = NULL;
++ struct zpci_netdev *netdev;
++ int reloads = 1;
++
++ do {
++ if (ctx->zpci_list) {
++ zdev = zpci_find_by_netdev(ctx->zpci_list, change_netdev->name,
++ &netdev);
++ if (zdev) {
++ /* Skip data collection if operational state is
++ * unchanged
++ */
++ if (netdev->operstate == change_netdev->operstate)
++ return;
++ /* Update operation state for VFs even though
++ * they are skipped just for a consistent view
++ */
++ netdev->operstate = change_netdev->operstate;
++ /* Only collect optics data for PFs */
++ if (!zpci_is_vf(zdev))
++ opticsmon_collect_adapter_data(ctx, zdev);
++ return;
++ }
++ }
++ /* Could be uninitalized list or a new device, retry after reload */
++ zpci_list_reload(&ctx->zpci_list);
++ reloads--;
++ } while (reloads > 0);
++}
++
++static int opticsmon_open_monitor(struct zpcimon_ctx *ctx)
++{
++ int ret;
++
++ ret = link_mon_nl_waitfd_create(&ctx->opticsmon_ctx.lctx, on_link_change, ctx);
++ if (ret) {
++ fprintf(stderr, "Failed to create link monitoring socket\n");
++ ret = -EIO;
++ }
++ return ret;
++}
++
++static int opticsmon_get_monitor_fd(struct zpcimon_ctx *ctx)
++{
++ return link_mon_nl_waitfd_getfd(&ctx->opticsmon_ctx.lctx);
++}
++
++static void opticsmon_monitor_fd_handle(struct zpcimon_ctx *ctx)
++{
++ link_mon_nl_waitfd_read(&ctx->opticsmon_ctx.lctx);
++}
++
++static void opticsmon_close_monitor(struct zpcimon_ctx *ctx)
++{
++ link_mon_nl_waitfd_destroy(&ctx->opticsmon_ctx.lctx);
++}
++
++static int opticsmon_init(struct zpcimon_ctx *ctx)
++{
++ return ethtool_nl_connect(&ctx->opticsmon_ctx.ethtool_ctx);
++}
++
++static void opticsmon_destroy(struct zpcimon_ctx *ctx)
++{
++ ethtool_nl_close(&ctx->opticsmon_ctx.ethtool_ctx);
++}
++
++const struct zpcimon_ops opticsmon_ops = {
++ .collect_adapter_data = opticsmon_collect_adapter_data,
++ .open_monitor = opticsmon_open_monitor,
++ .get_monitor_fd = opticsmon_get_monitor_fd,
++ .monitor_fd_handle = opticsmon_monitor_fd_handle,
++ .close_monitor = opticsmon_close_monitor,
++ .init = opticsmon_init,
++ .destroy = opticsmon_destroy,
++};
+diff --git a/zpcimon/opticsmon.h b/zpcimon/opticsmon.h
+new file mode 100644
+index 00000000..04798201
+--- /dev/null
++++ b/zpcimon/opticsmon.h
+@@ -0,0 +1,20 @@
++/*
++ * 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.
++ */
++#ifndef ZPCIMON_OPTICSMON_H
++#define ZPCIMON_OPTICSMON_H
++#include "ethtool.h"
++#include "link_mon.h"
++
++struct options;
++
++struct opticsmon_ctx {
++ struct ethtool_nl_ctx ethtool_ctx;
++ struct link_mon_nl_ctx lctx;
++};
++
++extern const struct zpcimon_ops opticsmon_ops;
++#endif /* ZPCIMON_OPTICSMON_H */
+diff --git a/zpcimon/zpcimon.c b/zpcimon/zpcimon.c
+index 40b6edd7..32550ff0 100644
+--- a/zpcimon/zpcimon.c
++++ b/zpcimon/zpcimon.c
+@@ -1,57 +1,47 @@
+ /*
+- * opticsmon - Report optics monitoring data to firmware
++ * zpcimon - Report monitoring data to firmware
+ *
+ * Copyright IBM Corp. 2024
+ *
+ * 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 <errno.h>
++#include <signal.h>
++#include <stdbool.h>
++#include <stdint.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+-#include <stdbool.h>
+-#include <errno.h>
+
++#include <linux/if.h>
+ #include <sys/epoll.h>
+ #include <sys/signalfd.h>
+-#include <signal.h>
+ #include <sys/timerfd.h>
+-#include <time.h>
+-#include <linux/if.h>
+
+-#include "lib/util_list.h"
++#include <openssl/evp.h>
++
+ #include "lib/pci_list.h"
+-#include "lib/util_prg.h"
+-#include "lib/util_opt.h"
+ #include "lib/util_fmt.h"
+ #include "lib/util_libc.h"
++#include "lib/util_opt.h"
++#include "lib/util_prg.h"
+ #include "lib/util_time.h"
++#include "lib/zt_common.h"
+
+-#include <openssl/evp.h>
+-
+-#include "ethtool.h"
+-#include "link_mon.h"
+-#include "optics_info.h"
+-#include "optics_sclp.h"
++#include "opticsmon.h"
++#include "zpcimon.h"
+ #include "zpcimon_cli.h"
+
+ #define API_LEVEL 1
+
+-struct options {
+- bool monitor;
+- bool report;
+- bool module_info;
+- bool quiet;
+- enum util_fmt_t format;
+- bool explicit_format;
+-
+- uint32_t interval_seconds;
++struct zpcimon_monitor {
++ const struct zpcimon_ops *ops;
++ uint32_t initialized : 1;
++ uint32_t opened : 1;
+ };
+
+-struct opticsmon_ctx {
+- struct options opts;
+- struct ethtool_nl_ctx ethtool_ctx;
+- struct link_mon_nl_ctx lctx;
+- struct util_list *zpci_list;
++static struct zpcimon_monitor monitors[] = {
++ {.ops = &opticsmon_ops, .initialized = 0, .opened = 0},
+ };
+
+ static const struct util_prg prg = {
+@@ -122,171 +112,131 @@ static void parse_cmdline(int argc, char *argv[], struct options *opts)
+ } while (cmd != -1);
+ }
+
+-static int module_info_pair(struct optics *oi)
++void zpcimon_json_base64_pair(char *name, uint8_t *buf, int len)
+ {
+- size_t b64_calclen, b64len;
+- int rc = EXIT_SUCCESS;
++ int b64_calclen, b64len;
+ char *b64;
+
+- b64_calclen = (oi->size / 3) * 4;
+- if (oi->size % 3 > 0)
++ b64_calclen = (len / 3) * 4;
++ if (len % 3 > 0)
+ b64_calclen += 4;
+
+ b64 = util_zalloc(b64_calclen + 1); /* adds NUL byte */
+- b64len = EVP_EncodeBlock((unsigned char *)b64, oi->raw, oi->size);
++ b64len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)buf, len);
+ if (b64len != b64_calclen) {
+- fprintf(stderr, "encoding base64 via openssl failed\n");
+- rc = EXIT_FAILURE;
++ warnx("encoding base64 via openssl failed\n");
+ goto out;
+ }
+- util_fmt_pair(FMT_QUOTE, "module_info", b64);
++ util_fmt_pair(FMT_QUOTE, name, b64);
+ out:
+ free(b64);
+- return rc;
+-}
+-
+-static void optics_json_print(struct opticsmon_ctx *ctx, struct zpci_netdev *nd, struct optics *oi)
+-{
+- util_fmt_obj_start(FMT_DEFAULT, "netdev");
+- util_fmt_pair(FMT_QUOTE, "name", nd->name);
+- util_fmt_pair(FMT_QUOTE, "operstate", zpci_operstate_str(nd->operstate));
+- util_fmt_obj_start(FMT_DEFAULT, "optics");
+- util_fmt_pair(FMT_QUOTE, "type", optics_type_str(optics_type(oi)));
+- util_fmt_pair(FMT_QUOTE, "rx_los", optics_los_str(optics_rx_los(oi)));
+- util_fmt_pair(FMT_QUOTE, "tx_los", optics_los_str(optics_tx_los(oi)));
+- util_fmt_pair(FMT_QUOTE, "tx_fault", optics_los_str(optics_tx_fault(oi)));
+- if (ctx->opts.module_info)
+- module_info_pair(oi);
+- util_fmt_obj_end();
+- util_fmt_obj_end();
+-}
+-
+-static int dump_adapter_data(struct opticsmon_ctx *ctx, struct zpci_dev *zdev)
+-{
+- struct optics **ois;
+- int num_ois = 0;
+- char *pci_addr;
+- int i, rc;
+-
+- ois = util_zalloc(sizeof(ois[0]) * zdev->num_netdevs);
+- for (i = 0; i < zdev->num_netdevs; i++) {
+- rc = ethtool_nl_get_optics(&ctx->ethtool_ctx, zdev->netdevs[i].name, &ois[i]);
+- if (rc)
+- goto free_ois;
+- num_ois++;
+- }
+- if (!ctx->opts.quiet) {
+- util_fmt_obj_start(FMT_DEFAULT, "adapter");
+- util_fmt_pair(FMT_QUOTE, "pft", zpci_pft_str(zdev));
+- util_fmt_obj_start(FMT_DEFAULT, "ids");
+- util_fmt_pair(FMT_QUOTE, "fid", "0x%0x", zdev->fid);
+- if (zdev->uid_is_unique)
+- util_fmt_pair(FMT_QUOTE, "uid", "0x%0x", zdev->uid);
+- pci_addr = zpci_pci_addr(zdev);
+- util_fmt_pair(FMT_QUOTE, "pci_address", pci_addr);
+- free(pci_addr);
+- util_fmt_obj_end();
+- util_fmt_obj_start(FMT_LIST, "netdevs");
+- for (i = 0; i < zdev->num_netdevs; i++)
+- optics_json_print(ctx, &zdev->netdevs[i], ois[i]);
+- util_fmt_obj_end(); /* netdevs list */
+- util_fmt_obj_end(); /* adapter */
+- fflush(stdout);
+- }
+- if (ctx->opts.report) {
+- for (i = 0; i < zdev->num_netdevs; i++) {
+- rc = sclp_issue_optics_report(zdev, ois[i]);
+- if (rc == -ENOTSUP) {
+- fprintf(stderr, "Skipping %s which does not support reporting\n",
+- zdev->netdevs[i].name);
+- } else if (rc < 0) {
+- fprintf(stderr, "Error issuing SCLP for optics data failed: %s\n",
+- strerror(-rc));
+- }
+- }
+- }
+-free_ois:
+- for (i = 0; i < num_ois; i++)
+- optics_free(ois[i]);
+- free(ois);
+- return rc;
+ }
+
+-static void zpci_list_reload(struct util_list **zpci_list)
++void zpci_list_reload(struct util_list **zpci_list)
+ {
+ if (*zpci_list)
+ zpci_free_dev_list(*zpci_list);
+ *zpci_list = zpci_dev_list();
+ }
+
+-static void dump_all_adapter_data(struct opticsmon_ctx *ctx)
++static void collect_all_adapter_data(struct zpcimon_ctx *ctx)
+ {
+ struct zpci_dev *zdev;
++ int i;
+
+ zpci_list_reload(&ctx->zpci_list);
+ util_list_iterate(ctx->zpci_list, zdev) {
+- /* Filter non-NIC devices and VFs */
+- if (zpci_is_vf(zdev) || !zdev->num_netdevs)
+- continue;
+- dump_adapter_data(ctx, zdev);
++ for (i = 0; i < (int)ARRAY_SIZE(monitors); i++)
++ if (monitors[i].ops->collect_adapter_data)
++ monitors[i].ops->collect_adapter_data(ctx, zdev);
+ }
+ }
+
+-static int oneshot_mode(struct opticsmon_ctx *ctx)
++#define MAX_EVENTS 8
++
++static int monitor_epoll_mon_fds_prepare(struct zpcimon_ctx *ctx, int epfd, int mon_fds[])
+ {
+- util_fmt_init(stdout, ctx->opts.format, FMT_DEFAULT, API_LEVEL);
+- if (!ctx->opts.quiet)
+- util_fmt_obj_start(FMT_LIST, "adapters");
+- dump_all_adapter_data(ctx);
+- if (!ctx->opts.quiet)
+- util_fmt_obj_end();
+- util_fmt_exit();
++ struct epoll_event ev;
++ int mon_idx;
+
+- return EXIT_SUCCESS;
++ for (mon_idx = 0; mon_idx < (int)ARRAY_SIZE(monitors); mon_idx++) {
++ if (!monitors[mon_idx].ops->get_monitor_fd) {
++ mon_fds[mon_idx] = -1;
++ continue;
++ }
++ mon_fds[mon_idx] = monitors[mon_idx].ops->get_monitor_fd(ctx);
++ if (mon_fds[mon_idx] < 0)
++ return -EIO;
++ ev.events = EPOLLIN;
++ ev.data.fd = mon_fds[mon_idx];
++ if (epoll_ctl(epfd, EPOLL_CTL_ADD, mon_fds[mon_idx], &ev) == -1)
++ return -EIO;
++ }
++ return 0;
+ }
+
+-void on_link_change(struct zpci_netdev *netdev, void *arg)
++static void monitor_epoll_mon_fds(struct zpcimon_ctx *ctx, struct epoll_event event,
++ const int mon_fds[])
+ {
+- struct opticsmon_ctx *ctx = arg;
+- struct zpci_netdev *found_netdev;
+- struct zpci_dev *zdev = NULL;
+- int reloads = 1;
++ int mon_idx;
+
+- do {
+- if (ctx->zpci_list) {
+- zdev = zpci_find_by_netdev(ctx->zpci_list, netdev->name, &found_netdev);
+- if (zdev) {
+- /* Skip data collection if operational state is
+- * unchanged
+- */
+- if (found_netdev->operstate == netdev->operstate)
+- return;
+- /* Update operation state for VFs even though
+- * they are skipped just for a consistent view
+- */
+- found_netdev->operstate = netdev->operstate;
+- /* Only collect optics data for PFs */
+- if (!zpci_is_vf(zdev))
+- dump_adapter_data(ctx, zdev);
+- return;
+- }
+- }
+- /* Could be uninitalized list or a new device, retry after reload */
+- zpci_list_reload(&ctx->zpci_list);
+- reloads--;
+- } while (reloads > 0);
++ for (mon_idx = 0; mon_idx < (int)ARRAY_SIZE(monitors); mon_idx++) {
++ if (event.data.fd != mon_fds[mon_idx])
++ continue;
++ if (!monitors[mon_idx].ops->monitor_fd_handle)
++ continue;
++ monitors[mon_idx].ops->monitor_fd_handle(ctx);
++ }
+ }
+
+-#define MAX_EVENTS 8
+-
+-static int monitor_wait_loop(struct opticsmon_ctx *ctx, int sigfd, int timerfd)
++static int monitor_epoll(struct zpcimon_ctx *ctx, const int mon_fds[], int epfd, int sigfd,
++ int timerfd)
+ {
+ struct epoll_event events[MAX_EVENTS];
+- int i, nlfd, epfd, nfds, ret = -EIO;
+ struct signalfd_siginfo fdsi;
+- struct epoll_event ev;
+ uint64_t expirations;
+ ssize_t sread;
++ int i, nfds;
++
++ nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
++ if (nfds < 0)
++ return nfds;
++ for (i = 0; i < nfds; i++) {
++ /* signal fd */
++ if (events[i].data.fd == sigfd) {
++ sread = read(sigfd, &fdsi, sizeof(fdsi));
++ if (sread != sizeof(fdsi))
++ return -EIO;
++ switch (fdsi.ssi_signo) {
++ case SIGINT:
++ case SIGTERM:
++ case SIGQUIT:
++ return -EINTR;
++ /* Unexpected signal */
++ default:
++ return -EIO;
++ }
++ /* timer fd */
++ } else if (events[i].data.fd == timerfd) {
++ sread = read(timerfd, &expirations, sizeof(uint64_t));
++ if (sread != sizeof(uint64_t))
++ return -EIO;
++ if (!expirations)
++ continue;
++ collect_all_adapter_data(ctx);
++ /* netlink fd */
++ } else {
++ monitor_epoll_mon_fds(ctx, events[i], mon_fds);
++ }
++ }
++ return 0;
++}
++
++static int monitor_wait_loop(struct zpcimon_ctx *ctx, int sigfd, int timerfd)
++{
++ int mon_fds[ARRAY_SIZE(monitors)];
++ struct epoll_event ev;
++ int epfd, ret = -EIO;
+
+ epfd = epoll_create1(EPOLL_CLOEXEC);
+ if (epfd < 0)
+@@ -302,52 +252,57 @@ static int monitor_wait_loop(struct opticsmon_ctx *ctx, int sigfd, int timerfd)
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev) == -1)
+ goto out_close;
+
+- nlfd = link_mon_nl_waitfd_getfd(&ctx->lctx);
+- ev.events = EPOLLIN;
+- ev.data.fd = nlfd;
+- if (epoll_ctl(epfd, EPOLL_CTL_ADD, nlfd, &ev) == -1)
++ ret = monitor_epoll_mon_fds_prepare(ctx, epfd, mon_fds);
++ if (ret)
+ goto out_close;
+
+ while (1) {
+- nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
+- if (nfds < 0)
+- goto out_close;
+- for (i = 0; i < nfds; i++) {
+- /* signal fd */
+- if (events[i].data.fd == sigfd) {
+- sread = read(sigfd, &fdsi, sizeof(fdsi));
+- if (sread != sizeof(fdsi))
+- goto out_close;
+- switch (fdsi.ssi_signo) {
+- case SIGINT:
+- case SIGTERM:
+- case SIGQUIT:
+- ret = 0;
+- goto out_close;
+- /* Unexpected signal */
+- default:
+- goto out_close;
+- }
+- /* timer fd */
+- } else if (events[i].data.fd == timerfd) {
+- sread = read(timerfd, &expirations, sizeof(uint64_t));
+- if (sread != sizeof(uint64_t))
+- goto out_close;
+- if (!expirations)
+- continue;
+- dump_all_adapter_data(ctx);
+- /* netlink fd */
+- } else if (events[i].data.fd == nlfd) {
+- link_mon_nl_waitfd_read(&ctx->lctx);
+- }
+- }
++ ret = monitor_epoll(ctx, mon_fds, epfd, sigfd, timerfd);
++ if (ret)
++ break;
+ }
+ out_close:
++ /* Getting interrupted by a signal is not an error */
++ if (ret == -EINTR)
++ ret = 0;
+ close(epfd);
+ return ret;
+ }
+
+-static int monitor_mode(struct opticsmon_ctx *ctx)
++static void zpcimon_close_monitor(struct zpcimon_ctx *ctx)
++{
++ int i;
++
++ for (i = 0; i < (int)ARRAY_SIZE(monitors); i++) {
++ if (!monitors[i].ops->close_monitor || !monitors[i].opened)
++ continue;
++ monitors[i].ops->close_monitor(ctx);
++ monitors[i].opened = 0;
++ }
++}
++
++static int zpcimon_open_monitor(struct zpcimon_ctx *ctx)
++{
++ int i, ret = -ENXIO;
++
++ for (i = 0; i < (int)ARRAY_SIZE(monitors); i++) {
++ if (!monitors[i].ops->open_monitor || monitors[i].opened)
++ continue;
++ if (monitors[i].opened)
++ goto error;
++ ret = monitors[i].ops->open_monitor(ctx);
++ if (ret)
++ goto error;
++ monitors[i].opened = 1;
++ }
++ return 0;
++
++error:
++ zpcimon_close_monitor(ctx);
++ return ret;
++}
++
++static int monitor_mode(struct zpcimon_ctx *ctx)
+ {
+ struct itimerspec timerspec;
+ int sigfd, timerfd, ret;
+@@ -386,20 +341,67 @@ static int monitor_mode(struct opticsmon_ctx *ctx)
+ }
+
+ util_fmt_init(stdout, ctx->opts.format, FMT_DEFAULT, API_LEVEL);
+- ret = link_mon_nl_waitfd_create(&ctx->lctx, on_link_change, ctx);
+- if (ret) {
+- fprintf(stderr, "Failed to create link monitoring socket\n");
+- goto close_timerfd;
+- }
++ ret = zpcimon_open_monitor(ctx);
++ if (ret < 0)
++ goto cleanup_fmt;
+
+ ret = monitor_wait_loop(ctx, sigfd, timerfd);
+
+- link_mon_nl_waitfd_destroy(&ctx->lctx);
++ zpcimon_close_monitor(ctx);
++cleanup_fmt:
+ util_fmt_exit();
+-close_signalfd:
+- close(sigfd);
+ close_timerfd:
+ close(timerfd);
++close_signalfd:
++ close(sigfd);
++ return ret;
++}
++
++static int oneshot_mode(struct zpcimon_ctx *ctx)
++{
++ util_fmt_init(stdout, ctx->opts.format, FMT_DEFAULT, API_LEVEL);
++ if (!ctx->opts.quiet)
++ util_fmt_obj_start(FMT_LIST, "adapters");
++
++ collect_all_adapter_data(ctx);
++
++ if (!ctx->opts.quiet)
++ util_fmt_obj_end();
++ util_fmt_exit();
++
++ return EXIT_SUCCESS;
++}
++
++static void zpcimon_destroy(struct zpcimon_ctx *ctx)
++{
++ int i;
++
++ for (i = 0; i < (int)ARRAY_SIZE(monitors); i++) {
++ if (!monitors[i].ops->destroy || !monitors[i].initialized)
++ continue;
++ monitors[i].ops->destroy(ctx);
++ monitors[i].initialized = false;
++ }
++}
++
++static int zpcimon_init(struct zpcimon_ctx *ctx)
++{
++ int i, ret = -ENXIO;
++
++ for (i = 0; i < (int)ARRAY_SIZE(monitors); i++) {
++ if (!monitors[i].ops->init)
++ continue;
++ if (monitors[i].initialized)
++ goto error;
++ ret = monitors[i].ops->init(ctx);
++ if (ret)
++ goto error;
++ monitors[i].initialized = 1;
++ }
++ return 0;
++
++error:
++ zpcimon_destroy(ctx);
+ return ret;
+ }
+
+@@ -432,21 +434,21 @@ static int set_format(struct options *opts)
+
+ int main(int argc, char **argv)
+ {
+- struct opticsmon_ctx ctx = { .opts = { .interval_seconds = 86400 } };
++ struct zpcimon_ctx ctx = { .opts = { .interval_seconds = SEC_PER_DAY } };
+ int ret;
+
+ parse_cmdline(argc, argv, &ctx.opts);
+ ret = set_format(&ctx.opts);
+ if (ret)
+ return ret;
+- ret = ethtool_nl_connect(&ctx.ethtool_ctx);
++ ret = zpcimon_init(&ctx);
+ if (ret)
+ return ret;
+ if (ctx.opts.monitor)
+ ret = monitor_mode(&ctx);
+ else
+ ret = oneshot_mode(&ctx);
+- ethtool_nl_close(&ctx.ethtool_ctx);
++ zpcimon_destroy(&ctx);
+
+ if (ctx.zpci_list)
+ zpci_free_dev_list(ctx.zpci_list);
+diff --git a/zpcimon/zpcimon.h b/zpcimon/zpcimon.h
+new file mode 100644
+index 00000000..768d5921
+--- /dev/null
++++ b/zpcimon/zpcimon.h
+@@ -0,0 +1,52 @@
++/*
++ * Copyright IBM Corp. 2024
++ *
++ * s390-tools is free software; you can redistribute it and/or modify
++ * it under the terms of the MIT license. See LICENSE for details.
++ */
++#ifndef ZPCIMON_ZPCIMON_H
++#define ZPCIMON_ZPCIMON_H
++#include <stdbool.h>
++#include <stdint.h>
++
++#include "lib/util_fmt.h"
++
++#include "opticsmon.h"
++
++#define API_LEVEL 1
++
++struct options {
++ uint32_t interval_seconds;
++
++ bool monitor;
++ bool report;
++ bool quiet;
++ enum util_fmt_t format;
++ bool explicit_format;
++
++ /* Optics Monitoring Specific */
++ bool module_info;
++};
++
++struct zpcimon_ctx {
++ struct options opts;
++ struct util_list *zpci_list;
++ struct opticsmon_ctx opticsmon_ctx;
++};
++
++struct zpcimon_ops {
++ int (*collect_adapter_data)(struct zpcimon_ctx *ctx, struct zpci_dev *zdev);
++
++ int (*open_monitor)(struct zpcimon_ctx *ctx);
++ int (*get_monitor_fd)(struct zpcimon_ctx *ctx);
++ void (*monitor_fd_handle)(struct zpcimon_ctx *ctx);
++ void (*close_monitor)(struct zpcimon_ctx *ctx);
++
++ int (*init)(struct zpcimon_ctx *ctx);
++ void (*destroy)(struct zpcimon_ctx *ctx);
++};
++
++void zpci_list_reload(struct util_list **zpci_list);
++
++void zpcimon_json_base64_pair(char *name, uint8_t *buf, int len);
++#endif /* ZPCIMON_ZPCIMON_H */
+--
+2.55.0
+
+
+From 78dd82d129c9ef0338619dfa3061c6db9cec3f9d Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 18 Jun 2026 16:40:09 +0200
+Subject: [PATCH 14/23] libzpci: Extract get_device_node() from zpcictl to
+ libzpci
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The get_device_node() function for getting the name of an NVMe's
+controller device e.g. "nvme0" as implemented in zpcictl is generally
+useful and will be needed for NVMe SMART data collection in zpcimon as
+well. Move it to libzpci and rename it to zpci_get_nvme_device_node() to
+account for the change in namespace.
+
+It would be tempting to pass a struct zpci_dev* instead of a const char*
+but as zpcictl uses an incompatible struct zpci_device* this will
+require a larger rework. Prioritize minimizing code duplication for now.
+
+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>
+---
+ include/lib/pci_list.h | 4 ++++
+ libzpci/pci_list.c | 46 +++++++++++++++++++++++++++++++++++++++++-
+ zpcictl/zpcictl.c | 33 ++----------------------------
+ 3 files changed, 51 insertions(+), 32 deletions(-)
+
+diff --git a/include/lib/pci_list.h b/include/lib/pci_list.h
+index 5b2918bc..226a82d9 100644
+--- a/include/lib/pci_list.h
++++ b/include/lib/pci_list.h
+@@ -17,6 +17,8 @@
+
+ #include "util_list.h"
+
++#define PCI_BDF_LEN 13 /* DDDD:BB:dd.f\0 */
++
+ enum zpci_pft {
+ ZPCI_PFT_UNCLASSIFIED = 0x00,
+
+@@ -96,4 +98,6 @@ operstate_t zpci_operstate_from_str(const char *oper_str);
+ struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name,
+ struct zpci_netdev **netdev);
+
++char *zpci_get_nvme_device_node(const char *pci_addr);
++
+ #endif /* LIB_ZPCI_PCI_LIST_H */
+diff --git a/libzpci/pci_list.c b/libzpci/pci_list.c
+index e0d56e44..e81bb4b8 100644
+--- a/libzpci/pci_list.c
++++ b/libzpci/pci_list.c
+@@ -20,6 +20,7 @@
+ #include "lib/util_list.h"
+ #include "lib/util_path.h"
+ #include "lib/util_scandir.h"
++#include "lib/util_sys.h"
+
+ /**
+ * Get the function type name for the given device
+@@ -137,7 +138,7 @@ const char *zpci_operstate_str(operstate_t state)
+ static int zpci_populate_from_slot_dir(struct zpci_dev *zdev, const char *slot_dir,
+ const char *slot_name)
+ {
+- char buf_addr[11]; /* "dddd:bb:dd\0" */
++ char buf_addr[PCI_BDF_LEN];
+ uint8_t bus, df;
+ uint32_t domain;
+ int val, rc;
+@@ -387,3 +388,46 @@ struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_n
+ }
+ return NULL;
+ }
++
++/**
++ * Get the NVMe device file name given a PCI address
++ *
++ * This function retrieves the NVMe device file "/dev/nvmeX"
++ * for a given PCI address. The device name can be used to construct
++ * the path /dev/nvmeX which is the NVMe's controller's character
++ * device used for example to retrieve S.M.A.R.T. data.
++ *
++ * @param[in] pci_addr The "DDDD:bb:dd.f" format PCI address
++ *
++ * @return The NVMe device file name if one is found NULL otherwise
++ */
++char *zpci_get_nvme_device_node(const char *pci_addr)
++{
++ char *path, *dev = NULL;
++ char dev_addr[PCI_BDF_LEN];
++ struct dirent **de_vec;
++ int count, i;
++
++ path = util_path_sysfs("bus/pci/devices/%s/nvme", pci_addr);
++ count = util_scandir(&de_vec, alphasort, path, "nvme*");
++ if (count == -1) {
++ warnx("Could not read directory %s: %s", path, strerror(errno));
++ goto exit_path;
++ }
++
++ for (i = 0; i < count; i++) {
++ util_asprintf(&dev, "/dev/%s", de_vec[i]->d_name);
++ if (util_sys_get_dev_addr(dev, dev_addr) != 0)
++ goto free_continue;
++ if (strcmp(dev_addr, pci_addr) == 0)
++ break;
++free_continue:
++ free(dev);
++ dev = NULL;
++ }
++
++ util_scandir_free(de_vec, count);
++exit_path:
++ free(path);
++ return dev;
++}
+diff --git a/zpcictl/zpcictl.c b/zpcictl/zpcictl.c
+index 421403ac..8c8ce482 100644
+--- a/zpcictl/zpcictl.c
++++ b/zpcictl/zpcictl.c
+@@ -18,8 +18,8 @@
+ #include "lib/util_opt.h"
+ #include "lib/util_path.h"
+ #include "lib/util_prg.h"
+-#include "lib/util_scandir.h"
+ #include "lib/util_sys.h"
++#include "lib/pci_list.h"
+ #include "lib/pci_sclp.h"
+
+ #include "zpcictl.h"
+@@ -187,35 +187,6 @@ static void sysfs_write_value(struct zpci_device *pdev, const char *attr,
+ free(path);
+ }
+
+-static void get_device_node(struct zpci_device *pdev)
+-{
+- struct dirent **de_vec;
+- char *path, *dev;
+- char slot[13];
+- int count, i;
+-
+- path = util_path_sysfs("bus/pci/devices/%s/nvme", pdev->slot);
+- count = util_scandir(&de_vec, alphasort, path, "nvme*");
+- if (count == -1) {
+- warnx("Could not read directory %s: %s", path, strerror(errno));
+- free(path);
+- return;
+- }
+-
+- for (i = 0; i < count; i++) {
+- util_asprintf(&dev, "/dev/%s", de_vec[i]->d_name);
+- if (util_sys_get_dev_addr(dev, slot) != 0)
+- continue;
+- if (strcmp(slot, pdev->slot) == 0) {
+- pdev->device = dev;
+- break;
+- }
+- }
+-
+- util_scandir_free(de_vec, count);
+- free(path);
+-}
+-
+ static int device_exists(char *dev)
+ {
+ char *path;
+@@ -258,7 +229,7 @@ static void get_device_info(struct zpci_device *pdev, char *dev)
+ * S.M.A.R.T. data at a later point.
+ */
+ if (!pdev->device && pdev->class == PCI_CLASS_NVME)
+- get_device_node(pdev);
++ pdev->device = zpci_get_nvme_device_node(pdev->slot);
+ }
+
+ /*
+--
+2.55.0
+
+
+From a9dbb8a1a2bcfefeffee0c80ec73a801c543c7e7 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Mon, 22 Jun 2026 12:36:30 +0200
+Subject: [PATCH 15/23] zpcimon: Factor out JSON printing of common PCI adapter
+ attributes
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Some of the attributes printed as JSON by opticsmon are common to all
+PCI adapters factor these out into reusable helper functions
+zpci_adapter_json_print_start() and zpci_adapter_json_print_end().
+
+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>
+---
+ zpcimon/opticsmon.c | 14 ++------------
+ zpcimon/zpcimon.c | 21 +++++++++++++++++++++
+ zpcimon/zpcimon.h | 2 ++
+ 3 files changed, 25 insertions(+), 12 deletions(-)
+
+diff --git a/zpcimon/opticsmon.c b/zpcimon/opticsmon.c
+index 4da456f1..2faa21d0 100644
+--- a/zpcimon/opticsmon.c
++++ b/zpcimon/opticsmon.c
+@@ -45,7 +45,6 @@ static int opticsmon_collect_adapter_data(struct zpcimon_ctx *ctx, struct zpci_d
+ struct options *opts = &ctx->opts;
+ struct optics **ois;
+ int num_ois = 0;
+- char *pci_addr;
+ int i, rc;
+
+ /* Filter non-NIC devices and VFs */
+@@ -60,21 +59,12 @@ static int opticsmon_collect_adapter_data(struct zpcimon_ctx *ctx, struct zpci_d
+ num_ois++;
+ }
+ if (!opts->quiet) {
+- util_fmt_obj_start(FMT_DEFAULT, "adapter");
+- util_fmt_pair(FMT_QUOTE, "pft", zpci_pft_str(zdev));
+- util_fmt_obj_start(FMT_DEFAULT, "ids");
+- util_fmt_pair(FMT_QUOTE, "fid", "0x%0x", zdev->fid);
+- if (zdev->uid_is_unique)
+- util_fmt_pair(FMT_QUOTE, "uid", "0x%0x", zdev->uid);
+- pci_addr = zpci_pci_addr(zdev);
+- util_fmt_pair(FMT_QUOTE, "pci_address", pci_addr);
+- free(pci_addr);
+- util_fmt_obj_end();
++ zpci_adapter_json_print_start(zdev);
+ util_fmt_obj_start(FMT_LIST, "netdevs");
+ for (i = 0; i < zdev->num_netdevs; i++)
+ optics_json_print(opts, &zdev->netdevs[i], ois[i]);
+ util_fmt_obj_end(); /* netdevs list */
+- util_fmt_obj_end(); /* adapter */
++ zpci_adapter_json_print_end();
+ fflush(stdout);
+ }
+ if (opts->report) {
+diff --git a/zpcimon/zpcimon.c b/zpcimon/zpcimon.c
+index 32550ff0..e30f251e 100644
+--- a/zpcimon/zpcimon.c
++++ b/zpcimon/zpcimon.c
+@@ -112,6 +112,27 @@ static void parse_cmdline(int argc, char *argv[], struct options *opts)
+ } while (cmd != -1);
+ }
+
++void zpci_adapter_json_print_start(struct zpci_dev *zdev)
++{
++ char *pci_addr;
++
++ util_fmt_obj_start(FMT_DEFAULT, "adapter");
++ util_fmt_pair(FMT_QUOTE, "pft", zpci_pft_str(zdev));
++ util_fmt_obj_start(FMT_DEFAULT, "ids");
++ util_fmt_pair(FMT_QUOTE, "fid", "0x%0x", zdev->fid);
++ if (zdev->uid_is_unique)
++ util_fmt_pair(FMT_QUOTE, "uid", "0x%0x", zdev->uid);
++ pci_addr = zpci_pci_addr(zdev);
++ util_fmt_pair(FMT_QUOTE, "pci_address", pci_addr);
++ free(pci_addr);
++ util_fmt_obj_end();
++}
++
++void zpci_adapter_json_print_end(void)
++{
++ util_fmt_obj_end(); /* adapter */
++}
++
+ void zpcimon_json_base64_pair(char *name, uint8_t *buf, int len)
+ {
+ int b64_calclen, b64len;
+diff --git a/zpcimon/zpcimon.h b/zpcimon/zpcimon.h
+index 768d5921..cb124056 100644
+--- a/zpcimon/zpcimon.h
++++ b/zpcimon/zpcimon.h
+@@ -48,5 +48,7 @@ struct zpcimon_ops {
+
+ void zpci_list_reload(struct util_list **zpci_list);
+
++void zpci_adapter_json_print_start(struct zpci_dev *zdev);
++void zpci_adapter_json_print_end(void);
+ void zpcimon_json_base64_pair(char *name, uint8_t *buf, int len);
+ #endif /* ZPCIMON_ZPCIMON_H */
+--
+2.55.0
+
+
+From 2eceedeb111453949ac78685c21c0f6a05f02bcd Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Mon, 22 Jun 2026 12:43:07 +0200
+Subject: [PATCH 16/23] zpcimon: Add NVMe SMART data monitor
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add a new monitor which uses libnvme to collect SMART data from NVMes.
+This monitor only implements the .collect_adapter_data operation since
+it carries no state across data collections. Nevertheless for future
+symmetry and possible future expansion also add an empty struct
+nvmemon_ctx.
+
+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>
+---
+ README.md | 7 +--
+ include/lib/pci_sclp.h | 2 +
+ zpcimon/Makefile | 22 +++++++++-
+ zpcimon/nvmemon.c | 98 ++++++++++++++++++++++++++++++++++++++++++
+ zpcimon/nvmemon.h | 13 ++++++
+ zpcimon/zpcimon.8 | 26 +++++++----
+ zpcimon/zpcimon.c | 5 +++
+ zpcimon/zpcimon.h | 4 ++
+ zpcimon/zpcimon_cli.h | 8 ++++
+ 9 files changed, 172 insertions(+), 13 deletions(-)
+ create mode 100644 zpcimon/nvmemon.c
+ create mode 100644 zpcimon/nvmemon.h
+
+diff --git a/README.md b/README.md
+index 4045b0f8..ab3dcd4e 100644
+--- a/README.md
++++ b/README.md
+@@ -327,6 +327,7 @@ build options:
+ | systemd | `HAVE_SYSTEMD` | hsavmcore |
+ | libudev | `HAVE_LIBUDEV` | cpacfstatsd |
+ | libnl3 | `HAVE_LIBNL3` | zpcimon |
++| libnvme | `HAVE_LIBNVME` | zpcimon |
+
+ This table lists additional build or install options:
+
+@@ -375,10 +376,10 @@ the different tools are provided:
+ The runtime requirements are: openssl-libs (>= 1.1.1) and libcurl.
+
+ * zpcimon:
+- For building zpcimon OpenSSL and the Netlink Library Suite (libnl3) are
+- required.
++ For building zpcimon OpenSSL, the Netlink Library Suite (libnl3),
++ and libnvme are required.
+ Tip: you may skip the zpcimon build by adding
+- `HAVE_OPENSSL=0` or `HAVE_LIBNL3=0`
++ `HAVE_OPENSSL=0` or `HAVE_LIBNL3=0` or `HAVE_LIBNVME=0`.
+
+ * osasnmpd:
+ You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm)
+diff --git a/include/lib/pci_sclp.h b/include/lib/pci_sclp.h
+index 688a7b76..c075b264 100644
+--- a/include/lib/pci_sclp.h
++++ b/include/lib/pci_sclp.h
+@@ -21,9 +21,11 @@
+ #define SCLP_ERRNOTIFY_AQ_DECONF 1
+ #define SCLP_ERRNOTIFY_AQ_REPORT_ERR 2
+ #define SCLP_ERRNOTIFY_AQ_OPTICS_DATA 3
++#define SCLP_ERRNOTIFY_AQ_NVME_SMART_DATA 4
+
+ #define SCLP_ERRNOTIFY_ID_ZPCICTL 0x4713
+ #define SCLP_ERRNOTIFY_ID_OPTICSMON 0x4714
++#define SCLP_ERRNOTIFY_ID_NVMEMON 0x4715
+
+ #define SCLP_ERRNOTIFY_DATA_SIZE 4054
+
+diff --git a/zpcimon/Makefile b/zpcimon/Makefile
+index cc0a8af0..ce815a31 100644
+--- a/zpcimon/Makefile
++++ b/zpcimon/Makefile
+@@ -29,6 +29,16 @@ check_dep_libnl3:
+ BUILDTARGET += check_dep_libnl3
+ endif # HAVE_LIBNL3
+
++ifneq (${HAVE_LIBNVME},0)
++check_dep_libnvme:
++ $(call check_dep, \
++ "zpcimon", \
++ "libnvme.h", \
++ "libnvme-devel", \
++ "HAVE_LIBNVME=0")
++BUILDTARGET += check_dep_libnvme
++endif # HAVE_LIBNVME
++
+ ifeq (${HAVE_OPENSSL},0)
+
+ all:
+@@ -44,6 +54,13 @@ all:
+ install:
+ $(SKIP) HAVE_LIBNL3=0
+
++else ifeq (${HAVE_LIBNVME},0)
++all:
++ $(SKIP) HAVE_LIBNVME=0
++
++install:
++ $(SKIP) HAVE_LIBNVME=0
++
+ else
+
+ ifneq ($(shell sh -c 'command -v pkg-config'),)
+@@ -58,12 +75,15 @@ LIB_LFLAGS += $(shell $(PKG_CONFIG) --silence-errors --libs libnl-genl-3.0)
+ 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)
+ else
+ LIB_CFLAGS += -I /usr/include/libnl3/
+ LIB_LFLAGS += -lnl-route-3 -lnl-genl-3 -lnl-3
+
+ LIB_CFLAGS += -I /usr/include/openssl/
+ LIB_LFLAGS += -lcrypto
++
++LIB_LFLAGS += -lnvme
+ endif
+
+ ALL_CPPFLAGS += $(LIB_CFLAGS)
+@@ -73,7 +93,7 @@ BUILDTARGET += zpcimon
+
+ all: ${BUILDTARGET}
+
+-zpcimon: zpcimon.o opticsmon.o optics_info.o optics_sclp.o ethtool.o link_mon.o $(libs)
++zpcimon: zpcimon.o nvmemon.o opticsmon.o optics_info.o optics_sclp.o ethtool.o link_mon.o $(libs)
+
+ install: all
+ $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8
+diff --git a/zpcimon/nvmemon.c b/zpcimon/nvmemon.c
+new file mode 100644
+index 00000000..85a5d0ad
+--- /dev/null
++++ b/zpcimon/nvmemon.c
+@@ -0,0 +1,98 @@
++/*
++ * 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 "lib/pci_list.h"
++#include "lib/pci_sclp.h"
++#include "lib/util_fmt.h"
++
++#include "nvmemon.h"
++#include "zpcimon.h"
++
++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);
++ if (ctx->opts.smart_blob)
++ zpcimon_json_base64_pair("smart-log-raw", (uint8_t *)log, sizeof(*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;
++}
++
++const struct zpcimon_ops nvmemon_ops = {
++ .collect_adapter_data = nvmemon_collect_adapter_data,
++};
+diff --git a/zpcimon/nvmemon.h b/zpcimon/nvmemon.h
+new file mode 100644
+index 00000000..0658bcaa
+--- /dev/null
++++ b/zpcimon/nvmemon.h
+@@ -0,0 +1,13 @@
++/*
++ * 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.
++ */
++#ifndef ZPCIMON_NVMEMON_H
++#define ZPCIMON_NVMEMON_H
++
++struct nvmemon_ctx {};
++
++extern const struct zpcimon_ops nvmemon_ops;
++#endif /* ZPCIMON_NVMEMON_H */
+diff --git a/zpcimon/zpcimon.8 b/zpcimon/zpcimon.8
+index 9b16fa91..6cc6ab29 100644
+--- a/zpcimon/zpcimon.8
++++ b/zpcimon/zpcimon.8
+@@ -33,7 +33,7 @@
+ .TH zpcimon 8 "Oct 2024" s390-tools zpcictl
+ .
+ .SH NAME
+-zpcimon - Monitor optical modules for directly attached PCI based NICs
++zpcimon - Monitor health of directly attached PCI devices including NVMes and NICs.
+ .
+ .
+ .SH SYNOPSIS
+@@ -45,26 +45,29 @@ zpcimon - Monitor optical modules for directly attached PCI based NICs
+ Use
+ .B zpcimon
+ to monitor the health of the optical modules of directly attached PCI based
+-NICs. When executed without the \fB--monitor\fR option it will collect optical
+-module data from all available PCI network interface physical functions and
+-print a summary in JSON format. Add the \fB--send-report\fR option to report
+-this data to the support element.
+-
++NICs as well as the health of NVMe devices using their SMART data. When
++executed without the \fB--monitor\fR option it will collect health data from
++all available and relevant PCI devices and print a summary in JSON format. Add
++the \fB--send-report\fR option to report this data to the support element.
+
+ .
+ .
+ .SH OPTIONS
+ .SS Operation Options
+ .OD monitor "m"
+-Run continuously and report on link state changes and periodically
++Run continuously and monitor all supported devices. On link state changes
++optical module data is collected and NVMe SMART data is collected on hotplug.
++Moreover every
++.B --interval
++seconds data from all adapters is collected as a whole system snapshot.
+ .PP
+ .
+ .OD send-report "r"
+-Report the optics health data to the Support Element (SE)
++Report the optics and NVMe health data to the Support Element (SE)
+ .PP
+ .
+ .OD quiet "q"
+-Be quiet and don't print optics health summary
++Be quiet and don't print health summaries.
+ .PP
+ .
+ .OD interval "i" "seconds"
+@@ -84,6 +87,11 @@ on".
+ .PP
+ .PP
+ .
++.OD smart-log-raw ""
++Include a base64 encoded binary dump of each NVMe's SMART data. This matches
++"nvme smart-log --output-format=binary /dev/nvmeX".
++.PP
++.
+ .SS Output Format Options
+ .OD format "" <FORMAT>
+ Set the output format out of json, json-seq, jsonl, pairs, and csv. Note that
+diff --git a/zpcimon/zpcimon.c b/zpcimon/zpcimon.c
+index e30f251e..625a1363 100644
+--- a/zpcimon/zpcimon.c
++++ b/zpcimon/zpcimon.c
+@@ -28,6 +28,7 @@
+ #include "lib/util_time.h"
+ #include "lib/zt_common.h"
+
++#include "nvmemon.h"
+ #include "opticsmon.h"
+ #include "zpcimon.h"
+ #include "zpcimon_cli.h"
+@@ -42,6 +43,7 @@ struct zpcimon_monitor {
+
+ static struct zpcimon_monitor monitors[] = {
+ {.ops = &opticsmon_ops, .initialized = 0, .opened = 0},
++ {.ops = &nvmemon_ops, .initialized = 0, .opened = 0},
+ };
+
+ static const struct util_prg prg = {
+@@ -79,6 +81,9 @@ static void parse_cmdline(int argc, char *argv[], struct options *opts)
+ case OPT_DUMP:
+ opts->module_info = true;
+ break;
++ case OPT_SMART_DUMP:
++ opts->smart_blob = true;
++ break;
+ case OPT_FORMAT:
+ if (!util_fmt_name_to_type(optarg, &fmt))
+ errx(EXIT_FAILURE, "Unknown format %s", optarg);
+diff --git a/zpcimon/zpcimon.h b/zpcimon/zpcimon.h
+index cb124056..da22eabe 100644
+--- a/zpcimon/zpcimon.h
++++ b/zpcimon/zpcimon.h
+@@ -11,6 +11,7 @@
+
+ #include "lib/util_fmt.h"
+
++#include "nvmemon.h"
+ #include "opticsmon.h"
+
+ #define API_LEVEL 1
+@@ -26,12 +27,15 @@ struct options {
+
+ /* Optics Monitoring Specific */
+ bool module_info;
++ /* NVMe Monitoring Specific */
++ bool smart_blob;
+ };
+
+ struct zpcimon_ctx {
+ struct options opts;
+ struct util_list *zpci_list;
+ struct opticsmon_ctx opticsmon_ctx;
++ struct nvmemon_ctx nvmemon_ctx;
+ };
+
+ struct zpcimon_ops {
+diff --git a/zpcimon/zpcimon_cli.h b/zpcimon/zpcimon_cli.h
+index f5a0dc3e..3a0f21a1 100644
+--- a/zpcimon/zpcimon_cli.h
++++ b/zpcimon/zpcimon_cli.h
+@@ -12,6 +12,7 @@
+
+ #define OPT_DUMP 128
+ #define OPT_FORMAT 129
++#define OPT_SMART_DUMP 130
+
+ static struct util_opt opt_vec[] = {
+ UTIL_OPT_SECTION("OPERATION OPTIONS"),
+@@ -35,6 +36,13 @@ static struct util_opt opt_vec[] = {
+ "This matches 'ethtool --module-info <netdev> raw on'",
+ .flags = UTIL_OPT_FLAG_NOSHORT,
+ },
++ {
++ .option = { "smart-log-raw", no_argument, NULL, OPT_SMART_DUMP },
++ .desc = "Include a base64 encoded binary dump of the "
++ "SMART log data for each NVMe. "
++ "This matches 'nvme smart-log --raw-binary <dev>'",
++ .flags = UTIL_OPT_FLAG_NOSHORT,
++ },
+ UTIL_OPT_SECTION("OPTIONS WITH ARGUMENTS"),
+ {
+ .option = { "interval", required_argument, NULL, 'i' },
+--
+2.55.0
+
+
+From f1a11a01dff9632c07ba9ec3c001bed1b0ef2ffd Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Tue, 21 Apr 2026 11:44:09 +0200
+Subject: [PATCH 17/23] zpcimon: Add JSON output for SMART data details
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This uses the same format as:
+
+ nvme smart-log --output-format=json /dev/nvmeX
+
+One complication being that several values in the SMART data use
+unsigned 128 bit integers. Both GCC and Clang support __uint128_t as
+a C extension but don't offer printf() support for it. Just like
+nvme-cli add a custom uint128_t to string function..
+
+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>
+---
+ zpcimon/nvmemon.c | 106 +++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 104 insertions(+), 2 deletions(-)
+
+diff --git a/zpcimon/nvmemon.c b/zpcimon/nvmemon.c
+index 85a5d0ad..a516b4aa 100644
+--- a/zpcimon/nvmemon.c
++++ b/zpcimon/nvmemon.c
+@@ -23,14 +23,116 @@
+ #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);
+- if (ctx->opts.smart_blob)
+- zpcimon_json_base64_pair("smart-log-raw", (uint8_t *)log, sizeof(*log));
++ nvme_json_print_smart_log(ctx, log);
+ util_fmt_obj_end();
+ zpci_adapter_json_print_end();
+ fflush(stdout);
+--
+2.55.0
+
+
+From 4fd607ae5ca9ec3714ca2354475f9a8b8caace09 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 23 Apr 2026 14:20:47 +0200
+Subject: [PATCH 18/23] 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 <hoeppner@linux.ibm.com>
+Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
+Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
+---
+ 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 <string.h>
+ #include <unistd.h>
+ #include <libnvme.h>
++#include <libudev.h>
+
+ #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 <libudev.h>
++
++struct nvmemon_ctx {
++ struct udev *udev;
++ struct udev_monitor *mon;
++};
+
+ extern const struct zpcimon_ops nvmemon_ops;
+ #endif /* ZPCIMON_NVMEMON_H */
+--
+2.55.0
+
+
+From f96f09a5c9c7ba81132aa6bc25df7abf3e9031f4 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 30 Apr 2026 15:33:43 +0200
+Subject: [PATCH 19/23] nvmemon: libzpci: Skip SCLP on NVMes with non-IBM
+ subsystem vendor ID
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The SCLP Write Event Data Action Qualifier 4 is only available for
+NVMes with subsystem vendor ID matching IBM. Add both IDs to struct
+zpci_dev in libzpci. Also add them to the libzpci_example output
+and adjust its output to multiline to handle more data.
+
+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>
+---
+ include/lib/pci_list.h | 4 ++++
+ libzpci/libzpci_example.c | 12 ++++++++----
+ libzpci/pci_list.c | 10 ++++++++++
+ zpcimon/nvmemon.c | 2 +-
+ 4 files changed, 23 insertions(+), 5 deletions(-)
+
+diff --git a/include/lib/pci_list.h b/include/lib/pci_list.h
+index 226a82d9..204b710d 100644
+--- a/include/lib/pci_list.h
++++ b/include/lib/pci_list.h
+@@ -18,6 +18,7 @@
+ #include "util_list.h"
+
+ #define PCI_BDF_LEN 13 /* DDDD:BB:dd.f\0 */
++#define PCI_VENDOR_ID_IBM 0x1014
+
+ enum zpci_pft {
+ ZPCI_PFT_UNCLASSIFIED = 0x00,
+@@ -67,6 +68,9 @@ struct zpci_dev {
+ bool uid_is_unique;
+ /* Configuration state 0 - Standby, 1 Configured */
+ bool conf;
++ /* Common PCI attributes */
++ uint16_t subsystem_vendor;
++ uint16_t subsystem_device;
+
+ /* Associated netdevs if any */
+ int num_netdevs;
+diff --git a/libzpci/libzpci_example.c b/libzpci/libzpci_example.c
+index 583846b4..a9b8509e 100644
+--- a/libzpci/libzpci_example.c
++++ b/libzpci/libzpci_example.c
+@@ -12,13 +12,17 @@ static void zpci_print(struct zpci_dev *zdev)
+ int i;
+
+ if (!zdev->conf) {
+- printf("fid: %8x address: %s\n", zdev->fid, pci_addr);
++ printf("fid:\t%4x address: %s\n", zdev->fid, pci_addr);
+ } else {
+- printf("fid: %8x address: %s uid: %4x%s pchid: %4x vfn: %4d port: %1d pft: %s ",
++ printf("fid:\t%4x address: %s uid: %4x%s pchid: %4x vfn: %4d\n",
+ zdev->fid, pci_addr, zdev->uid, (zdev->uid_is_unique) ? " (unique)" : "",
+- zdev->pchid, zdev->vfn, zdev->port, zpci_pft_str(zdev));
++ zdev->pchid, zdev->vfn);
++ printf("\n");
++ printf("\tport: %1d pft: %s subsystem_device: %4x subsystem_vendor: %4x\n",
++ zdev->port, zpci_pft_str(zdev), zdev->subsystem_device,
++ zdev->subsystem_vendor);
+ if (zdev->num_netdevs) {
+- printf("netdevs: ");
++ printf("\tnetdevs: ");
+ for (i = 0; i < zdev->num_netdevs; i++) {
+ printf("%s (%s)", zdev->netdevs[i].name,
+ zpci_operstate_str(zdev->netdevs[i].operstate));
+diff --git a/libzpci/pci_list.c b/libzpci/pci_list.c
+index e81bb4b8..a125bf71 100644
+--- a/libzpci/pci_list.c
++++ b/libzpci/pci_list.c
+@@ -262,6 +262,16 @@ static int zpci_populate_from_dev_dir(struct zpci_dev *zdev)
+ goto out_path;
+ zdev->pft = val;
+
++ rc = util_file_read_i(&val, 16, "%s/subsystem_device", path);
++ if (rc)
++ goto out_path;
++ zdev->subsystem_device = val;
++
++ rc = util_file_read_i(&val, 16, "%s/subsystem_vendor", path);
++ if (rc)
++ goto out_path;
++ zdev->subsystem_vendor = val;
++
+ if (util_path_is_readable("%s/net", path)) {
+ rc = zpci_populate_netdevices(zdev, path);
+ if (rc)
+diff --git a/zpcimon/nvmemon.c b/zpcimon/nvmemon.c
+index 14f088c0..bd5ef1a7 100644
+--- a/zpcimon/nvmemon.c
++++ b/zpcimon/nvmemon.c
+@@ -144,7 +144,7 @@ static int sclp_issue_nvme_smart_report(struct zpci_dev *zdev, const uint8_t *sm
+ char *pci_addr;
+ int rc;
+
+- if (zdev->pft != ZPCI_PFT_NVME)
++ if (zdev->pft != ZPCI_PFT_NVME || zdev->subsystem_vendor != PCI_VENDOR_ID_IBM)
+ return -ENOTSUP;
+ pci_addr = zpci_pci_addr(zdev);
+ rc = zpci_sclp_issue_action(pci_addr, SCLP_ERRNOTIFY_AQ_NVME_SMART_DATA,
+--
+2.55.0
+
+
+From 1da328921b419445d24f569e7cf85082bec9c688 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Fri, 26 Jun 2026 15:13:22 +0200
+Subject: [PATCH 20/23] dbginfo.sh: Let zpcimon log both optical module and
+ SMART data
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add zpcimon data collection with both SMART log and optical module data.
+Do so in the Z device subsystem commands section to account for the fact
+that zpcimon is no longer network specific. At the same time keep the
+existing opticsmon invocation for compatibility when executed on systems
+without zpcimon.
+
+Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
+Reviewed-by: Jörn Siglen <siglen@de.ibm.com>
+Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
+Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
+---
+ scripts/dbginfo.sh | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/scripts/dbginfo.sh b/scripts/dbginfo.sh
+index ec6227f1..a5e3866e 100755
+--- a/scripts/dbginfo.sh
++++ b/scripts/dbginfo.sh
+@@ -515,6 +515,7 @@ CMDS="${CMDS}\
+ :lspci -vvv\
+ :lstape\
+ :smc_dbg\
++ :zpcimon --module-info --smart-log-raw\
+ "
+
+ # block device and other scsi device commands (in non alphabetical order)
+--
+2.55.0
+
+
+From 7586a1ad593f99bd7eb2daec39f01d4aeee3cf08 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 23 Jul 2026 16:55:50 +0200
+Subject: [PATCH 21/23] zpcimon: Add missing include for __packed attribute
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The __packed attribute is used without including the necessary
+zt_common.h header, add it. Note that luckily the struct as it currently
+exists doesn't actually required __packed as the natural alignment
+leaves no holes. Still if in the future the reserved field is split up
+it would need it and this keeps it consistent with the kernel
+declaration.
+
+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>
+---
+ zpcimon/optics_sclp.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/zpcimon/optics_sclp.h b/zpcimon/optics_sclp.h
+index b211e9ac..4b295bc5 100644
+--- a/zpcimon/optics_sclp.h
++++ b/zpcimon/optics_sclp.h
+@@ -1,6 +1,7 @@
+ #include <stdint.h>
+
+ #include "lib/pci_list.h"
++#include "lib/zt_common.h"
+
+ #include "optics_info.h"
+
+--
+2.55.0
+
+
+From 0f0e8cf33b807caebb9d8e8a652c0e7326ea48d7 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 23 Jul 2026 16:55:53 +0200
+Subject: [PATCH 22/23] zpcimon: Make optics_los_implemente() static
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Sparse noted that optics_los_implemente() can be static. Make it so.
+
+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>
+---
+ zpcimon/optics_info.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/zpcimon/optics_info.c b/zpcimon/optics_info.c
+index 00d8d723..b3bc3535 100644
+--- a/zpcimon/optics_info.c
++++ b/zpcimon/optics_info.c
+@@ -53,7 +53,7 @@ enum optics_type optics_type(struct optics *oi)
+ };
+ }
+
+-bool optics_los_implemented(struct optics *oi)
++static bool optics_los_implemented(struct optics *oi)
+ {
+ enum optics_type type = optics_type(oi);
+ uint8_t implemented;
+--
+2.55.0
+
+
+From 91123e5d45ed9fd227d6e9c590ec82484dbb7bc7 Mon Sep 17 00:00:00 2001
+From: Niklas Schnelle <schnelle@linux.ibm.com>
+Date: Thu, 23 Jul 2026 16:55:54 +0200
+Subject: [PATCH 23/23] lib/zt_common.h: zpcimon: Add __force and use it to
+ annotate le32toh() conversions
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+When using sparse on zpcimon several warnings like the one below are
+generated:
+
+ warning: incorrect type in argument 1 (different base types)
+ expected unsigned int [usertype] __bsx
+ got restricted __le32 [usertype] warning_temp_time
+
+This is because several members in struct nvme_smart_log are marked as
+__le32. These members are correctly converted to host endianness before
+use via le32toh() respectively le16toh(). Since these functions take
+their parameters as plain uint32_t or uint16_t however the implicit
+conversion triggers the above warning. Fix this by adding the __force
+attribute and using it to mark type conversions in the leXXtoh() calls.
+
+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>
+---
+ include/lib/zt_common.h | 5 +++++
+ zpcimon/nvmemon.c | 24 +++++++++++++++---------
+ 2 files changed, 20 insertions(+), 9 deletions(-)
+
+diff --git a/include/lib/zt_common.h b/include/lib/zt_common.h
+index c5363fe2..78f2a9b8 100644
+--- a/include/lib/zt_common.h
++++ b/include/lib/zt_common.h
+@@ -72,6 +72,11 @@
+ #define __section(x) __attribute__((__section__(#x)))
+ #define __noinline __attribute__((__noinline__))
+ #define __big_endian
++#ifdef __CHECKER__
++# define __force __attribute__((force))
++#else
++# define __force
++#endif
+ /* The Linux kernel (in stddef.h) and glibc (sys/cdefs.h) define
+ * __always_inline. Therefore undefine it first to allow the headers
+ * to be included first.
+diff --git a/zpcimon/nvmemon.c b/zpcimon/nvmemon.c
+index bd5ef1a7..1e4c1aa8 100644
+--- a/zpcimon/nvmemon.c
++++ b/zpcimon/nvmemon.c
+@@ -20,6 +20,7 @@
+ #include "lib/pci_list.h"
+ #include "lib/pci_sclp.h"
+ #include "lib/util_fmt.h"
++#include "lib/zt_common.h"
+
+ #include "nvmemon.h"
+ #include "zpcimon.h"
+@@ -108,19 +109,24 @@ static void nvme_json_print_smart_log(struct zpcimon_ctx *ctx, struct nvme_smart
+ 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, "warning_temp_time", "%d",
++ le32toh((__force uint32_t)log->warning_temp_time));
++ util_fmt_pair(FMT_DEFAULT, "critical_comp_time", "%d",
++ le32toh((__force uint32_t)log->critical_comp_time));
++ util_fmt_pair(FMT_DEFAULT, "temperature_sensor_1", "%d",
++ le16toh((__force uint16_t)log->temp_sensor[0]));
++ util_fmt_pair(FMT_DEFAULT, "temperature_sensor_2", "%d",
++ le16toh((__force uint16_t)log->temp_sensor[1]));
++ util_fmt_pair(FMT_DEFAULT, "temperature_sensor_3", "%d",
++ le16toh((__force uint16_t)log->temp_sensor[2]));
+ util_fmt_pair(FMT_DEFAULT, "thm_temp1_trans_count", "%d",
+- le32toh(log->thm_temp1_trans_count));
++ le32toh((__force uint32_t)log->thm_temp1_trans_count));
+ util_fmt_pair(FMT_DEFAULT, "thm_temp2_trans_count", "%d",
+- le32toh(log->thm_temp2_trans_count));
++ le32toh((__force uint32_t)log->thm_temp2_trans_count));
+ util_fmt_pair(FMT_DEFAULT, "thm_temp1_total_time", "%d",
+- le32toh(log->thm_temp1_total_time));
++ le32toh((__force uint32_t)log->thm_temp1_total_time));
+ util_fmt_pair(FMT_DEFAULT, "thm_temp2_total_time", "%d",
+- le32toh(log->thm_temp2_total_time));
++ le32toh((__force uint32_t)log->thm_temp2_total_time));
+ util_fmt_obj_end(); /* smart-log */
+
+ if (ctx->opts.smart_blob)
+--
+2.55.0
+
diff --git a/s390utils.spec b/s390utils.spec
index 52e9298..cac8e7b 100644
--- a/s390utils.spec
+++ b/s390utils.spec
@@ -17,7 +17,7 @@
Name: s390utils
Summary: Utilities and daemons for IBM z Systems
Version: 2.43.1
-Release: 1%{?dist}
+Release: 2%{?dist}
Epoch: 2
# MIT covers nearly all the files, except init files (LGPL-2.1-or-later)
#
@@ -78,7 +78,7 @@ Patch0: s390-tools-zipl-invert-script-options.patch
Patch1: s390-tools-zipl-blscfg-rpm-nvr-sort.patch
# upstream fixes/updates
-#Patch100: s390utils-%%{version}-fedora.patch
+Patch100: s390utils-%{version}-fedora.patch
# https://fedoraproject.org/wiki/Changes/EncourageI686LeafRemoval
ExcludeArch: %{ix86}
@@ -124,7 +124,8 @@ The s390utils packages contain a set of user space utilities that should to
be used together with the zSeries (s390) Linux kernel and device drivers.
%prep
-%autosetup -n s390-tools-%{version} -p1
+# git_am method needed for the patchset with file renamings
+%autosetup -n s390-tools-%{version} -p1 -S git_am
%if 0%{?rhel}
pushd rust
@@ -462,6 +463,7 @@ BuildRequires: json-c-devel
BuildRequires: rpm-devel
BuildRequires: libxml2-devel
BuildRequires: libnl3-devel
+BuildRequires: libnvme-devel
%description base
@@ -645,6 +647,7 @@ For more information refer to the following publications:
%{_sbindir}/zmemtopo
%{_sbindir}/znetconf
%{_sbindir}/zpcictl
+%{_sbindir}/zpcimon
%{_bindir}/cpacfinfo
%{_bindir}/dump2tar
%{_bindir}/genprotimg
@@ -663,6 +666,7 @@ For more information refer to the following publications:
%{_bindir}/zpwr
%{_unitdir}/dumpconf.service
%{_unitdir}/opticsmon.service
+%{_unitdir}/zpcimon.service
%ghost %config(noreplace) %{_sysconfdir}/zipl.conf
%config(noreplace) %{_sysconfdir}/sysconfig/dumpconf
%{_sysconfdir}/mdevctl.d/*
@@ -764,6 +768,7 @@ For more information refer to the following publications:
%{_mandir}/man8/zmemtopo.8*
%{_mandir}/man8/znetconf.8*
%{_mandir}/man8/zpcictl.8*
+%{_mandir}/man8/zpcimon.8*
%dir %{_datadir}/s390-tools
%{_datadir}/s390-tools/netboot/
%{bash_completions_dir}/*.bash
@@ -1141,6 +1146,9 @@ User-space development files for the s390/s390x architecture.
%changelog
+* Mon Jul 27 2026 Dan Horák <dan[at]danny.cz> - 2:2.43.1-2
+- include zpcimon feature
+
* Thu Jul 23 2026 Dan Horák <dan[at]danny.cz> - 2:2.43.1-1
- rebased to 2.43.1 (rhbz#2506343)
reply other threads:[~2026-07-28 9:37 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178523145619.1.14802920710269703868.rpms-s390utils-aef14f49a019@fedoraproject.org \
--to=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox