[PATCH v8 2/4] s390/pci: Reuse FMB buffer and preserve state in device re-enablement

From: Omar Elghoul

Date: Mon Oct 05 2026 - 11:56:01 EST


Don't free the FMB buffer when disabling measurement in
zpci_fmb_disable_device(). Instead, make the buffer persistent for the
lifetime of the device and reuse it across enable/disable cycles. Defer
freeing the buffer until teardown in zpci_release_device().

To support the persistent buffers, add the fmb_enabled bool to struct
zpci_dev to decouple whether FMB is enabled from whether the buffer has
been allocated. Audit the only consumer of zdev->fmb as a liveness check
and update it to reflect this change.

Introduce the function zpci_fmb_reenable_device() to ensure that the FMB
is enabled. If it was already enabled, disable it, zero the counters,
and re-enable it. This allows the function to be used in both first-time
enabling and re-enabling measurement. Call it in zpci_reenable_device()
to preserve the FMB enablement if it had been implicitly disabled by
firmware in zpci_disable_device().

Signed-off-by: Omar Elghoul <oelghoul@xxxxxxxxxxxxx>
---
arch/s390/include/asm/pci.h | 2 +
arch/s390/pci/pci.c | 76 ++++++++++++++++++++++++++++---------
arch/s390/pci/pci_debug.c | 2 +-
3 files changed, 62 insertions(+), 18 deletions(-)

diff --git a/arch/s390/include/asm/pci.h b/arch/s390/include/asm/pci.h
index 88a125b92bdd..2920de1f9484 100644
--- a/arch/s390/include/asm/pci.h
+++ b/arch/s390/include/asm/pci.h
@@ -205,6 +205,7 @@ struct zpci_dev {

/* Function measurement block */
struct mutex fmb_lock;
+ bool fmb_enabled;
struct zpci_fmb *fmb;
u16 fmb_update; /* update interval */
u16 fmb_length;
@@ -351,6 +352,7 @@ void zpci_remove_parent_msi_domain(struct zpci_bus *zbus);
/* FMB */
int zpci_fmb_enable_device(struct zpci_dev *);
int zpci_fmb_disable_device(struct zpci_dev *);
+int zpci_fmb_reenable_device(struct zpci_dev *zdev);

/* Debug */
int zpci_debug_init(void);
diff --git a/arch/s390/pci/pci.c b/arch/s390/pci/pci.c
index c055a9ad0972..ec729be91f97 100644
--- a/arch/s390/pci/pci.c
+++ b/arch/s390/pci/pci.c
@@ -175,13 +175,17 @@ int zpci_fmb_enable_device(struct zpci_dev *zdev)

lockdep_assert_held(&zdev->fmb_lock);

- if (zdev->fmb || sizeof(*zdev->fmb) < zdev->fmb_length)
+ if (zdev->fmb_enabled || sizeof(*zdev->fmb) < zdev->fmb_length)
return -EINVAL;

- zdev->fmb = kmem_cache_zalloc(zdev_fmb_cache, GFP_KERNEL);
- if (!zdev->fmb)
- return -ENOMEM;
- WARN_ON((u64) zdev->fmb & 0xf);
+ if (!zdev->fmb) {
+ zdev->fmb = kmem_cache_zalloc(zdev_fmb_cache, GFP_KERNEL);
+ if (!zdev->fmb)
+ return -ENOMEM;
+ } else {
+ /* reuse the same FMB buffer for as long the zdev lives */
+ memset(zdev->fmb, 0, sizeof(*zdev->fmb));
+ }

/* reset software counters */
spin_lock_irqsave(&zdev->dom_lock, flags);
@@ -199,11 +203,11 @@ int zpci_fmb_enable_device(struct zpci_dev *zdev)
fib.fmb_addr = virt_to_phys(zdev->fmb);
fib.gd = zdev->gisa;
cc = zpci_mod_fc(req, &fib, &status);
- if (cc) {
- kmem_cache_free(zdev_fmb_cache, zdev->fmb);
- zdev->fmb = NULL;
- }
- return cc ? -EIO : 0;
+ if (cc)
+ return -EIO;
+
+ zdev->fmb_enabled = true;
+ return 0;
}

/* Modify PCI: Disable PCI function measurement */
@@ -215,7 +219,7 @@ int zpci_fmb_disable_device(struct zpci_dev *zdev)

lockdep_assert_held(&zdev->fmb_lock);

- if (!zdev->fmb)
+ if (!zdev->fmb_enabled)
return -EINVAL;

fib.gd = zdev->gisa;
@@ -224,13 +228,39 @@ int zpci_fmb_disable_device(struct zpci_dev *zdev)
cc = zpci_mod_fc(req, &fib, &status);
if (cc == 3) /* Function already gone. */
cc = 0;
+ if (cc)
+ return -EIO;

- if (!cc) {
- kmem_cache_free(zdev_fmb_cache, zdev->fmb);
- zdev->fmb = NULL;
- }
- return cc ? -EIO : 0;
+ zdev->fmb_enabled = false;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(zpci_fmb_disable_device);
+
+int zpci_fmb_reenable_device(struct zpci_dev *zdev)
+{
+ u64 req = ZPCI_CREATE_REQ(zdev->fh, 0, ZPCI_MOD_FC_SET_MEASURE);
+ struct zpci_fib fib = {0};
+ u8 cc, status;
+
+ lockdep_assert_held(&zdev->fmb_lock);
+
+ if (!zdev->fmb_enabled)
+ return zpci_fmb_enable_device(zdev);
+
+ fib.gd = zdev->gisa;
+ cc = zpci_mod_fc(req, &fib, &status); /* Disable function measurement */
+
+ /* Unlike in zpci_fmb_disable_device(), cc == 3 is not a valid state here
+ * because we are re-enabling function measurement for the same function
+ * handle.
+ */
+ if (cc)
+ return -EIO;
+
+ zdev->fmb_enabled = false;
+ return zpci_fmb_enable_device(zdev);
}
+EXPORT_SYMBOL_GPL(zpci_fmb_reenable_device);

static int zpci_cfg_load(struct zpci_dev *zdev, int offset, u32 *val, u8 len)
{
@@ -737,8 +767,14 @@ int zpci_reenable_device(struct zpci_dev *zdev)
}

rc = zpci_iommu_register_ioat(zdev, &status);
- if (rc)
+ if (rc) {
zpci_disable_device(zdev);
+ return rc;
+ }
+
+ guard(mutex)(&zdev->fmb_lock);
+ if (zdev->fmb_enabled)
+ zpci_fmb_reenable_device(zdev);

return rc;
}
@@ -1003,6 +1039,12 @@ void zpci_release_device(struct kref *kref)
if (zdev->has_resources)
zpci_cleanup_bus_resources(zdev);

+ if (zdev->fmb) {
+ zdev->fmb_enabled = false;
+ kmem_cache_free(zdev_fmb_cache, zdev->fmb);
+ zdev->fmb = NULL;
+ }
+
zpci_bus_device_unregister(zdev);
zpci_destroy_iommu(zdev);
zpci_dbg(3, "rem fid:%x\n", zdev->fid);
diff --git a/arch/s390/pci/pci_debug.c b/arch/s390/pci/pci_debug.c
index c7ed7bf254b5..44f026ead414 100644
--- a/arch/s390/pci/pci_debug.c
+++ b/arch/s390/pci/pci_debug.c
@@ -97,7 +97,7 @@ static int pci_perf_show(struct seq_file *m, void *v)
return 0;

mutex_lock(&zdev->fmb_lock);
- if (!zdev->fmb) {
+ if (!zdev->fmb_enabled) {
mutex_unlock(&zdev->fmb_lock);
seq_puts(m, "FMB statistics disabled\n");
return 0;
--
2.55.0