[PATCH v1 1/3] s390/pci: Preserve FMB state in device re-enablement

From: Omar Elghoul

Date: Fri May 01 2026 - 15:27:25 EST


Introduce a function zpci_fmb_reenable_device() that checks for the state
of the FMB and reuses the same buffer where appropriate. If FMB was not
previously enabled, it enables it for the device. Call this function during
a zPCI device re-enablement, which in turn implicitly ensures that the FMB
is enabled for host devices during their KVM registration.

This function also clears out the software counters, so that a program
resetting an FMB would see all its counters restart from zero as expected.
The function to clear the software counters is also separated into a static
function as it is now reused in both zpci_fmb_enable_device() and
zpci_fmb_reenable_device().

Signed-off-by: Omar Elghoul <oelghoul@xxxxxxxxxxxxx>
---
arch/s390/include/asm/pci.h | 1 +
arch/s390/pci/pci.c | 71 ++++++++++++++++++++++++++++++-------
2 files changed, 59 insertions(+), 13 deletions(-)

diff --git a/arch/s390/include/asm/pci.h b/arch/s390/include/asm/pci.h
index 5dcf35f0f325..65014e52d559 100644
--- a/arch/s390/include/asm/pci.h
+++ b/arch/s390/include/asm/pci.h
@@ -323,6 +323,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 39bd2adfc240..9bc38e041130 100644
--- a/arch/s390/pci/pci.c
+++ b/arch/s390/pci/pci.c
@@ -164,6 +164,24 @@ int zpci_unregister_ioat(struct zpci_dev *zdev, u8 dmaas)
return cc;
}

+static void zpci_fmb_clear_iommu_ctrs(struct zpci_dev *zdev)
+{
+ struct zpci_iommu_ctrs *ctrs;
+ unsigned long flags = 0;
+
+ /* reset software counters */
+ spin_lock_irqsave(&zdev->dom_lock, flags);
+ ctrs = zpci_get_iommu_ctrs(zdev);
+ if (ctrs) {
+ atomic64_set(&ctrs->mapped_pages, 0);
+ atomic64_set(&ctrs->unmapped_pages, 0);
+ atomic64_set(&ctrs->global_rpcits, 0);
+ atomic64_set(&ctrs->sync_map_rpcits, 0);
+ atomic64_set(&ctrs->sync_rpcits, 0);
+ }
+ spin_unlock_irqrestore(&zdev->dom_lock, flags);
+}
+
/* Modify PCI: Set PCI function measurement parameters */
int zpci_fmb_enable_device(struct zpci_dev *zdev)
{
@@ -181,18 +199,7 @@ int zpci_fmb_enable_device(struct zpci_dev *zdev)
return -ENOMEM;
WARN_ON((u64) zdev->fmb & 0xf);

- /* reset software counters */
- spin_lock_irqsave(&zdev->dom_lock, flags);
- ctrs = zpci_get_iommu_ctrs(zdev);
- if (ctrs) {
- atomic64_set(&ctrs->mapped_pages, 0);
- atomic64_set(&ctrs->unmapped_pages, 0);
- atomic64_set(&ctrs->global_rpcits, 0);
- atomic64_set(&ctrs->sync_map_rpcits, 0);
- atomic64_set(&ctrs->sync_rpcits, 0);
- }
- spin_unlock_irqrestore(&zdev->dom_lock, flags);
-
+ zpci_fmb_clear_iommu_ctrs(zdev);

fib.fmb_addr = virt_to_phys(zdev->fmb);
fib.gd = zdev->gisa;
@@ -227,6 +234,41 @@ int zpci_fmb_disable_device(struct zpci_dev *zdev)
}
return cc ? -EIO : 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_iommu_ctrs *ctrs;
+ struct zpci_fib fib = {0};
+ unsigned long flags;
+ u8 cc, status;
+
+ if (!zdev->fmb)
+ 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;
+
+ zpci_fmb_clear_iommu_ctrs(zdev);
+
+ fib.fmb_addr = virt_to_phys(zdev->fmb);
+ cc = zpci_mod_fc(req, &fib, &status); /* Re-enable function measurement */
+ if (cc) {
+ kmem_cache_free(zdev_fmb_cache, zdev->fmb);
+ zdev->fmb = NULL;
+ return -EIO;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(zpci_fmb_reenable_device);

static int zpci_cfg_load(struct zpci_dev *zdev, int offset, u32 *val, u8 len)
{
@@ -729,9 +771,12 @@ 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;
+ }

+ zpci_fmb_reenable_device(zdev);
return rc;
}
EXPORT_SYMBOL_GPL(zpci_reenable_device);
--
2.52.0