/* Max age of data in a diagnostics buffer before it needs a refresh (in ms). */
 #define ZFCP_DIAG_MAX_AGE (5 * 1000)
 
+static DECLARE_WAIT_QUEUE_HEAD(__zfcp_diag_publish_wait);
+
 /**
  * zfcp_diag_adapter_setup() - Setup storage for adapter diagnostics.
  * @adapter: the adapter to setup diagnostics for.
 out:
        spin_unlock_irqrestore(&hdr->access_lock, flags);
 }
+
+/**
+ * zfcp_diag_update_port_data_buffer() - Implementation of
+ *                                      &typedef zfcp_diag_update_buffer_func
+ *                                      to collect and update Port Data.
+ * @adapter: Adapter to collect Port Data from.
+ *
+ * This call is SYNCHRONOUS ! It blocks till the respective command has
+ * finished completely, or has failed in some way.
+ *
+ * Return:
+ * * 0         - Successfully retrieved new Diagnostics and Updated the buffer;
+ *               this also includes cases where data was retrieved, but
+ *               incomplete; you'll have to check the flag ``incomplete``
+ *               of &struct zfcp_diag_header.
+ * * see zfcp_fsf_exchange_port_data_sync() for possible error-codes (
+ *   excluding -EAGAIN)
+ */
+int zfcp_diag_update_port_data_buffer(struct zfcp_adapter *const adapter)
+{
+       int rc;
+
+       rc = zfcp_fsf_exchange_port_data_sync(adapter->qdio, NULL);
+       if (rc == -EAGAIN)
+               rc = 0; /* signaling incomplete via struct zfcp_diag_header */
+
+       /* buffer-data was updated in zfcp_fsf_exchange_port_data_handler() */
+
+       return rc;
+}
+
+static int __zfcp_diag_update_buffer(struct zfcp_adapter *const adapter,
+                                    struct zfcp_diag_header *const hdr,
+                                    zfcp_diag_update_buffer_func buffer_update,
+                                    unsigned long *const flags)
+       __must_hold(hdr->access_lock)
+{
+       int rc;
+
+       if (hdr->updating == 1) {
+               rc = wait_event_interruptible_lock_irq(__zfcp_diag_publish_wait,
+                                                      hdr->updating == 0,
+                                                      hdr->access_lock);
+               rc = (rc == 0 ? -EAGAIN : -EINTR);
+       } else {
+               hdr->updating = 1;
+               spin_unlock_irqrestore(&hdr->access_lock, *flags);
+
+               /* unlocked, because update function sleeps */
+               rc = buffer_update(adapter);
+
+               spin_lock_irqsave(&hdr->access_lock, *flags);
+               hdr->updating = 0;
+
+               /*
+                * every thread waiting here went via an interruptible wait,
+                * so its fine to only wake those
+                */
+               wake_up_interruptible_all(&__zfcp_diag_publish_wait);
+       }
+
+       return rc;
+}
+
+static bool
+__zfcp_diag_test_buffer_age_isfresh(const struct zfcp_diag_header *const hdr)
+       __must_hold(hdr->access_lock)
+{
+       const unsigned long now = jiffies;
+
+       /*
+        * Should not happen (data is from the future).. if it does, still
+        * signal that it needs refresh
+        */
+       if (!time_after_eq(now, hdr->timestamp))
+               return false;
+
+       if (jiffies_to_msecs(now - hdr->timestamp) >= ZFCP_DIAG_MAX_AGE)
+               return false;
+
+       return true;
+}
+
+/**
+ * zfcp_diag_update_buffer_limited() - Collect diagnostics and update a
+ *                                    diagnostics buffer rate limited.
+ * @adapter: Adapter to collect the diagnostics from.
+ * @hdr: buffer-header for which to update with the collected diagnostics.
+ * @buffer_update: Specific implementation for collecting and updating.
+ *
+ * This function will cause an update of the given @hdr by calling the also
+ * given @buffer_update function. If called by multiple sources at the same
+ * time, it will synchornize the update by only allowing one source to call
+ * @buffer_update and the others to wait for that source to complete instead
+ * (the wait is interruptible).
+ *
+ * Additionally this version is rate-limited and will only exit if either the
+ * buffer is fresh enough (within the limit) - it will do nothing if the buffer
+ * is fresh enough to begin with -, or if the source/thread that started this
+ * update is the one that made the update (to prevent endless loops).
+ *
+ * Return:
+ * * 0         - If the update was successfully published and/or the buffer is
+ *               fresh enough
+ * * -EINTR    - If the thread went into the wait-state and was interrupted
+ * * whatever @buffer_update returns
+ */
+int zfcp_diag_update_buffer_limited(struct zfcp_adapter *const adapter,
+                                   struct zfcp_diag_header *const hdr,
+                                   zfcp_diag_update_buffer_func buffer_update)
+{
+       unsigned long flags;
+       int rc;
+
+       spin_lock_irqsave(&hdr->access_lock, flags);
+
+       for (rc = 0; !__zfcp_diag_test_buffer_age_isfresh(hdr); rc = 0) {
+               rc = __zfcp_diag_update_buffer(adapter, hdr, buffer_update,
+                                              &flags);
+               if (rc != -EAGAIN)
+                       break;
+       }
+
+       spin_unlock_irqrestore(&hdr->access_lock, flags);
+
+       return rc;
+}