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