#include <linux/spinlock.h>
 #include <linux/jiffies.h>
 #include <linux/string.h>
+#include <linux/kernfs.h>
+#include <linux/sysfs.h>
 #include <linux/errno.h>
 #include <linux/slab.h>
 
        adapter->diagnostics = NULL;
 }
 
+/**
+ * zfcp_diag_sysfs_setup() - Setup the sysfs-group for adapter-diagnostics.
+ * @adapter: target adapter to which the group should be added.
+ *
+ * Return: 0 on success; Something else otherwise (see sysfs_create_group()).
+ */
+int zfcp_diag_sysfs_setup(struct zfcp_adapter *const adapter)
+{
+       int rc = sysfs_create_group(&adapter->ccw_device->dev.kobj,
+                                   &zfcp_sysfs_diag_attr_group);
+       if (rc == 0)
+               adapter->diagnostics->sysfs_established = 1;
+
+       return rc;
+}
+
+/**
+ * zfcp_diag_sysfs_destroy() - Remove the sysfs-group for adapter-diagnostics.
+ * @adapter: target adapter from which the group should be removed.
+ */
+void zfcp_diag_sysfs_destroy(struct zfcp_adapter *const adapter)
+{
+       if (adapter->diagnostics == NULL ||
+           !adapter->diagnostics->sysfs_established)
+               return;
+
+       /*
+        * We need this state-handling so we can prevent warnings being printed
+        * on the kernel-console in case we have to abort a halfway done
+        * zfcp_adapter_enqueue(), in which the sysfs-group was not yet
+        * established. sysfs_remove_group() does this checking as well, but
+        * still prints a warning in case we try to remove a group that has not
+        * been established before
+        */
+       adapter->diagnostics->sysfs_established = 0;
+       sysfs_remove_group(&adapter->ccw_device->dev.kobj,
+                          &zfcp_sysfs_diag_attr_group);
+}
+
+
 /**
  * zfcp_diag_update_xdata() - Update a diagnostics buffer.
  * @hdr: the meta data to update.
 
 /**
  * struct zfcp_diag_adapter - central storage for all diagnostics concerning an
  *                           adapter.
+ * @sysfs_established: flag showing that the associated sysfs-group was created
+ *                    during run of zfcp_adapter_enqueue().
  * @port_data: data retrieved using exchange port data.
  * @port_data.header: header with metadata for the cache in @port_data.data.
  * @port_data.data: cached QTCB Bottom of command exchange port data.
  * @config_data.data: cached QTCB Bottom of command exchange config data.
  */
 struct zfcp_diag_adapter {
+       u64     sysfs_established       :1;
+
        struct {
                struct zfcp_diag_header         header;
                struct fsf_qtcb_bottom_port     data;
 int zfcp_diag_adapter_setup(struct zfcp_adapter *const adapter);
 void zfcp_diag_adapter_free(struct zfcp_adapter *const adapter);
 
+int zfcp_diag_sysfs_setup(struct zfcp_adapter *const adapter);
+void zfcp_diag_sysfs_destroy(struct zfcp_adapter *const adapter);
+
 void zfcp_diag_update_xdata(struct zfcp_diag_header *const hdr,
                            const void *const data, const bool incomplete);
 
+/**
+ * zfcp_diag_support_sfp() - Return %true if the @adapter supports reporting
+ *                          SFP Data.
+ * @adapter: adapter to test the availability of SFP Data reporting for.
+ */
+static inline bool
+zfcp_diag_support_sfp(const struct zfcp_adapter *const adapter)
+{
+       return !!(adapter->adapter_features & FSF_FEATURE_REPORT_SFP_DATA);
+}
+
 #endif /* ZFCP_DIAG_H */
 
 #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
 
 #include <linux/slab.h>
+#include "zfcp_diag.h"
 #include "zfcp_ext.h"
 
 #define ZFCP_DEV_ATTR(_feat, _name, _mode, _show, _store) \
        &dev_attr_queue_full,
        NULL
 };
+
+#define ZFCP_DEFINE_DIAG_SFP_ATTR(_name, _qtcb_member, _prtsize, _prtfmt)      \
+       static ssize_t zfcp_sysfs_adapter_diag_sfp_##_name##_show(             \
+               struct device *dev, struct device_attribute *attr, char *buf)  \
+       {                                                                      \
+               struct zfcp_adapter *const adapter =                           \
+                       zfcp_ccw_adapter_by_cdev(to_ccwdev(dev));              \
+               struct zfcp_diag_header *diag_hdr;                             \
+               ssize_t rc = -ENOLINK;                                         \
+               unsigned long flags;                                           \
+               unsigned int status;                                           \
+                                                                              \
+               if (!adapter)                                                  \
+                       return -ENODEV;                                        \
+                                                                              \
+               status = atomic_read(&adapter->status);                        \
+               if (0 == (status & ZFCP_STATUS_COMMON_OPEN) ||                 \
+                   0 == (status & ZFCP_STATUS_COMMON_UNBLOCKED) ||            \
+                   0 != (status & ZFCP_STATUS_COMMON_ERP_FAILED))             \
+                       goto out;                                              \
+                                                                              \
+               if (!zfcp_diag_support_sfp(adapter)) {                         \
+                       rc = -EOPNOTSUPP;                                      \
+                       goto out;                                              \
+               }                                                              \
+                                                                              \
+               diag_hdr = &adapter->diagnostics->port_data.header;            \
+                                                                              \
+               spin_lock_irqsave(&diag_hdr->access_lock, flags);              \
+               rc = scnprintf(                                                \
+                       buf, (_prtsize) + 2, _prtfmt "\n",                     \
+                       adapter->diagnostics->port_data.data._qtcb_member);    \
+               spin_unlock_irqrestore(&diag_hdr->access_lock, flags);         \
+                                                                              \
+       out:                                                                   \
+               zfcp_ccw_adapter_put(adapter);                                 \
+               return rc;                                                     \
+       }                                                                      \
+       static ZFCP_DEV_ATTR(adapter_diag_sfp, _name, 0400,                    \
+                            zfcp_sysfs_adapter_diag_sfp_##_name##_show, NULL)
+
+ZFCP_DEFINE_DIAG_SFP_ATTR(temperature, temperature, 5, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(vcc, vcc, 5, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(tx_bias, tx_bias, 5, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(tx_power, tx_power, 5, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(rx_power, rx_power, 5, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(port_tx_type, sfp_flags.port_tx_type, 2, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(optical_port, sfp_flags.optical_port, 1, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(sfp_invalid, sfp_flags.sfp_invalid, 1, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(connector_type, sfp_flags.connector_type, 1, "%hu");
+ZFCP_DEFINE_DIAG_SFP_ATTR(fec_active, sfp_flags.fec_active, 1, "%hu");
+
+static struct attribute *zfcp_sysfs_diag_attrs[] = {
+       &dev_attr_adapter_diag_sfp_temperature.attr,
+       &dev_attr_adapter_diag_sfp_vcc.attr,
+       &dev_attr_adapter_diag_sfp_tx_bias.attr,
+       &dev_attr_adapter_diag_sfp_tx_power.attr,
+       &dev_attr_adapter_diag_sfp_rx_power.attr,
+       &dev_attr_adapter_diag_sfp_port_tx_type.attr,
+       &dev_attr_adapter_diag_sfp_optical_port.attr,
+       &dev_attr_adapter_diag_sfp_sfp_invalid.attr,
+       &dev_attr_adapter_diag_sfp_connector_type.attr,
+       &dev_attr_adapter_diag_sfp_fec_active.attr,
+       NULL,
+};
+
+const struct attribute_group zfcp_sysfs_diag_attr_group = {
+       .name = "diagnostics",
+       .attrs = zfcp_sysfs_diag_attrs,
+};