]> pilppa.com Git - linux-2.6-omap-h63xx.git/commitdiff
[SCSI] zfcp: Automatically attach remote ports
authorSwen Schillig <swen@vnet.ibm.com>
Tue, 10 Jun 2008 16:21:00 +0000 (18:21 +0200)
committerJames Bottomley <James.Bottomley@HansenPartnership.com>
Sat, 12 Jul 2008 13:22:26 +0000 (08:22 -0500)
Automatically attach the remote ports in zfcp when the adapter is set
online. This is done by querying all available ports from the FC
namesever. The scan for remote ports is also triggered by RSCNs and
can be triggered manually with the sysfs attribute 'port_rescan'.

Signed-off-by: Swen Schillig <swen@vnet.ibm.com>
Signed-off-by: Christof Schmitt <christof.schmitt@de.ibm.com>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
drivers/s390/scsi/zfcp_aux.c
drivers/s390/scsi/zfcp_dbf.c
drivers/s390/scsi/zfcp_def.h
drivers/s390/scsi/zfcp_erp.c
drivers/s390/scsi/zfcp_ext.h
drivers/s390/scsi/zfcp_fc.c
drivers/s390/scsi/zfcp_fsf.c
drivers/s390/scsi/zfcp_sysfs_adapter.c

index 47739f4f6709a84e6be9c9f3e72a0fd4f259dc73..2bd80fdcceffd932e6c71514abf7d2aff7adbb4b 100644 (file)
@@ -568,6 +568,19 @@ static void _zfcp_status_read_scheduler(struct work_struct *work)
                                             stat_work));
 }
 
+static int zfcp_nameserver_enqueue(struct zfcp_adapter *adapter)
+{
+       struct zfcp_port *port;
+
+       port = zfcp_port_enqueue(adapter, 0, ZFCP_STATUS_PORT_WKA,
+                                ZFCP_DID_DIRECTORY_SERVICE);
+       if (!port)
+               return -ENXIO;
+       zfcp_port_put(port);
+
+       return 0;
+}
+
 /*
  * Enqueues an adapter at the end of the adapter list in the driver data.
  * All adapter internal structures are set up.
@@ -648,6 +661,7 @@ zfcp_adapter_enqueue(struct ccw_device *ccw_device)
        /* initialize lock of associated request queue */
        rwlock_init(&adapter->req_q.lock);
        INIT_WORK(&adapter->stat_work, _zfcp_status_read_scheduler);
+       INIT_WORK(&adapter->scan_work, _zfcp_scan_ports_later);
 
        /* mark adapter unusable as long as sysfs registration is not complete */
        atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status);
@@ -673,6 +687,8 @@ zfcp_adapter_enqueue(struct ccw_device *ccw_device)
 
        zfcp_data.adapters++;
 
+       zfcp_nameserver_enqueue(adapter);
+
        goto out;
 
  generic_services_failed:
@@ -704,6 +720,7 @@ zfcp_adapter_dequeue(struct zfcp_adapter *adapter)
        int retval = 0;
        unsigned long flags;
 
+       cancel_work_sync(&adapter->scan_work);
        cancel_work_sync(&adapter->stat_work);
        zfcp_adapter_scsi_unregister(adapter);
        device_unregister(&adapter->generic_services);
@@ -816,13 +833,15 @@ zfcp_port_enqueue(struct zfcp_adapter *adapter, wwn_t wwpn, u32 status,
                        kfree(port);
                        return NULL;
                }
-               port->d_id = d_id;
                port->sysfs_device.parent = &adapter->generic_services;
        } else {
                snprintf(port->sysfs_device.bus_id,
                         BUS_ID_SIZE, "0x%016llx", wwpn);
                port->sysfs_device.parent = &adapter->ccw_device->dev;
        }
+
+       port->d_id = d_id;
+
        port->sysfs_device.release = zfcp_sysfs_port_release;
        dev_set_drvdata(&port->sysfs_device, port);
 
@@ -873,21 +892,6 @@ zfcp_port_dequeue(struct zfcp_port *port)
        device_unregister(&port->sysfs_device);
 }
 
-/* Enqueues a nameserver port */
-int
-zfcp_nameserver_enqueue(struct zfcp_adapter *adapter)
-{
-       struct zfcp_port *port;
-
-       port = zfcp_port_enqueue(adapter, 0, ZFCP_STATUS_PORT_WKA,
-                                ZFCP_DID_DIRECTORY_SERVICE);
-       if (!port)
-               return -ENXIO;
-       zfcp_port_put(port);
-
-       return 0;
-}
-
 void zfcp_sg_free_table(struct scatterlist *sg, int count)
 {
        int i;
index 7c72f502eb0f5593373f0e3dd1fe750583dcf59a..3e9f0abb22f9bd5d0fd4f138ca56cf8ca02a7013 100644 (file)
@@ -596,6 +596,10 @@ static const char *zfcp_rec_dbf_ids[] = {
        [145]   = "recovery action being processed",
        [146]   = "recovery action ready for next step",
        [147]   = "qdio error inbound",
+       [148]   = "nameserver needed for port scan",
+       [149]   = "port scan",
+       [150]   = "ptp attach",
+       [151]   = "port validation failed",
 };
 
 static int zfcp_rec_dbf_view_format(debug_info_t *id, struct debug_view *view,
index 73425dbb2be801fd970561c79a2cf79216f08b8c..1e837d46ea749bf1cf9a1d4c23d41d77a45b5a09 100644 (file)
@@ -282,7 +282,10 @@ struct zfcp_rc_entry {
 #define ZFCP_CT_DIRECTORY_SERVICE      0xFC
 #define ZFCP_CT_NAME_SERVER            0x02
 #define ZFCP_CT_SYNCHRONOUS            0x00
+#define ZFCP_CT_SCSI_FCP               0x08
+#define ZFCP_CT_UNABLE_TO_PERFORM_CMD  0x09
 #define ZFCP_CT_GID_PN                 0x0121
+#define ZFCP_CT_GPN_FT                 0x0172
 #define ZFCP_CT_MAX_SIZE               0x1020
 #define ZFCP_CT_ACCEPT                 0x8002
 #define ZFCP_CT_REJECT                 0x8001
@@ -311,6 +314,7 @@ struct zfcp_rc_entry {
 #define ZFCP_STATUS_COMMON_ERP_INUSE           0x01000000
 #define ZFCP_STATUS_COMMON_ACCESS_DENIED       0x00800000
 #define ZFCP_STATUS_COMMON_ACCESS_BOXED                0x00400000
+#define ZFCP_STATUS_COMMON_NOESC               0x00200000
 
 /* adapter status */
 #define ZFCP_STATUS_ADAPTER_QDIOUP             0x00000002
@@ -629,6 +633,7 @@ struct zfcp_adapter {
        struct fc_host_statistics *fc_stats;
        struct fsf_qtcb_bottom_port *stats_reset_data;
        unsigned long           stats_reset;
+       struct work_struct      scan_work;
 };
 
 /*
index c06156b288ea395ae533a60c9bde1ba3da42a9f7..9b9c999cf39f568896c453bf65256eedf2a795a5 100644 (file)
@@ -1199,6 +1199,10 @@ zfcp_erp_strategy_check_port(struct zfcp_port *port, int result)
                zfcp_erp_port_unblock(port);
                break;
        case ZFCP_ERP_FAILED :
+               if (atomic_test_mask(ZFCP_STATUS_COMMON_NOESC, &port->status)) {
+                       zfcp_erp_port_block(port, 0);
+                       result = ZFCP_ERP_EXIT;
+               }
                atomic_inc(&port->erp_counter);
                if (atomic_read(&port->erp_counter) > ZFCP_MAX_ERPS)
                        zfcp_erp_port_failed(port, 22, NULL);
@@ -1607,6 +1611,7 @@ zfcp_erp_adapter_strategy_generic(struct zfcp_erp_action *erp_action, int close)
                goto failed_openfcp;
 
        atomic_set_mask(ZFCP_STATUS_COMMON_OPEN, &erp_action->adapter->status);
+       schedule_work(&erp_action->adapter->scan_work);
        goto out;
 
  close_only:
@@ -1665,10 +1670,19 @@ zfcp_erp_adapter_strategy_open_fsf(struct zfcp_erp_action *erp_action)
        return zfcp_erp_adapter_strategy_open_fsf_statusread(erp_action);
 }
 
+static void zfcp_erp_open_ptp_port(struct zfcp_adapter *adapter)
+{
+       struct zfcp_port *port;
+       port = zfcp_port_enqueue(adapter, adapter->peer_wwpn, 0,
+                                adapter->peer_d_id);
+       if (!port) /* error or port already attached */
+               return;
+       zfcp_erp_port_reopen_internal(port, 0, 150, NULL);
+}
+
 static int
 zfcp_erp_adapter_strategy_open_fsf_xconfig(struct zfcp_erp_action *erp_action)
 {
-       int retval = ZFCP_ERP_SUCCEEDED;
        int retries;
        int sleep = ZFCP_EXCHANGE_CONFIG_DATA_FIRST_SLEEP;
        struct zfcp_adapter *adapter = erp_action->adapter;
@@ -1682,8 +1696,9 @@ zfcp_erp_adapter_strategy_open_fsf_xconfig(struct zfcp_erp_action *erp_action)
                zfcp_erp_action_to_running(erp_action);
                write_unlock_irq(&adapter->erp_lock);
                if (zfcp_fsf_exchange_config_data(erp_action)) {
-                       retval = ZFCP_ERP_FAILED;
-                       break;
+                       atomic_clear_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
+                                         &adapter->status);
+                       return ZFCP_ERP_FAILED;
                }
 
                /*
@@ -1719,9 +1734,12 @@ zfcp_erp_adapter_strategy_open_fsf_xconfig(struct zfcp_erp_action *erp_action)
 
        if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK,
                              &adapter->status))
-               retval = ZFCP_ERP_FAILED;
+               return ZFCP_ERP_FAILED;
 
-       return retval;
+       if (fc_host_port_type(adapter->scsi_host) == FC_PORTTYPE_PTP)
+               zfcp_erp_open_ptp_port(adapter);
+
+       return ZFCP_ERP_SUCCEEDED;
 }
 
 static int
@@ -1899,14 +1917,12 @@ zfcp_erp_port_strategy_open_common(struct zfcp_erp_action *erp_action)
                        retval = zfcp_erp_port_strategy_open_port(erp_action);
                        break;
                }
-               if (!(adapter->nameserver_port)) {
-                       retval = zfcp_nameserver_enqueue(adapter);
-                       if (retval != 0) {
-                               dev_err(&adapter->ccw_device->dev,
-                                       "Nameserver port unavailable.\n");
-                               retval = ZFCP_ERP_FAILED;
-                               break;
-                       }
+
+               if (!adapter->nameserver_port) {
+                       dev_err(&adapter->ccw_device->dev,
+                               "Nameserver port unavailable.\n");
+                       retval = ZFCP_ERP_FAILED;
+                       break;
                }
                if (!atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED,
                                      &adapter->nameserver_port->status)) {
index 9aa412bd66372d40bb68607d3917140ac1956a87..c3b51338abfa4b92a5a8c8eff25d86669022d25c 100644 (file)
@@ -37,6 +37,8 @@ extern struct zfcp_port *zfcp_port_enqueue(struct zfcp_adapter *, wwn_t,
 extern void   zfcp_port_dequeue(struct zfcp_port *);
 extern struct zfcp_unit *zfcp_unit_enqueue(struct zfcp_port *, fcp_lun_t);
 extern void   zfcp_unit_dequeue(struct zfcp_unit *);
+extern int zfcp_scan_ports(struct zfcp_adapter *);
+extern void _zfcp_scan_ports_later(struct work_struct *work);
 
 /******************************* S/390 IO ************************************/
 extern int  zfcp_ccw_register(void);
@@ -97,8 +99,6 @@ extern int  zfcp_fc_ns_gid_pn_request(struct zfcp_erp_action *);
 extern void zfcp_fc_plogi_evaluate(struct zfcp_port *, struct fsf_plogi *);
 extern void  zfcp_test_link(struct zfcp_port *);
 
-extern int  zfcp_nameserver_enqueue(struct zfcp_adapter *);
-
 /******************************* SCSI ****************************************/
 extern int  zfcp_adapter_scsi_register(struct zfcp_adapter *);
 extern void zfcp_adapter_scsi_unregister(struct zfcp_adapter *);
index bb07c3bf2258af13555e6c48d933cf9580e38228..aa2d9a668d170f74a1e672eaa0fd4e9dd1bd52aa 100644 (file)
@@ -8,6 +8,37 @@
 
 #include "zfcp_ext.h"
 
+struct ct_iu_gpn_ft_req {
+       struct ct_hdr header;
+       u8 flags;
+       u8 domain_id_scope;
+       u8 area_id_scope;
+       u8 fc4_type;
+} __attribute__ ((packed));
+
+struct gpn_ft_resp_acc {
+       u8 control;
+       u8 port_id[3];
+       u8 reserved[4];
+       u64 wwpn;
+} __attribute__ ((packed));
+
+#define ZFCP_GPN_FT_ENTRIES ((PAGE_SIZE - sizeof(struct ct_hdr)) \
+                               / sizeof(struct gpn_ft_resp_acc))
+#define ZFCP_GPN_FT_BUFFERS 4
+#define ZFCP_GPN_FT_MAX_ENTRIES ZFCP_GPN_FT_BUFFERS * (ZFCP_GPN_FT_ENTRIES + 1)
+
+struct ct_iu_gpn_ft_resp {
+       struct ct_hdr header;
+       struct gpn_ft_resp_acc accept[ZFCP_GPN_FT_ENTRIES];
+} __attribute__ ((packed));
+
+struct zfcp_gpn_ft {
+       struct zfcp_send_ct ct;
+       struct scatterlist sg_req;
+       struct scatterlist sg_resp[ZFCP_GPN_FT_BUFFERS];
+};
+
 static void _zfcp_fc_incoming_rscn(struct zfcp_fsf_req *fsf_req, u32 range,
                                   struct fcp_rscn_element *elem)
 {
@@ -68,6 +99,7 @@ static void zfcp_fc_incoming_rscn(struct zfcp_fsf_req *fsf_req)
                }
                _zfcp_fc_incoming_rscn(fsf_req, range_mask, fcp_rscn_element);
        }
+       schedule_work(&fsf_req->adapter->scan_work);
 }
 
 static void zfcp_fc_incoming_wwpn(struct zfcp_fsf_req *req, wwn_t wwpn)
@@ -303,3 +335,219 @@ void zfcp_test_link(struct zfcp_port *port)
        zfcp_port_put(port);
        zfcp_erp_port_forced_reopen(port, 0, 65, NULL);
 }
+
+static int zfcp_scan_get_nameserver(struct zfcp_adapter *adapter)
+{
+       int ret;
+
+       if (!adapter->nameserver_port)
+               return -EINTR;
+
+       if (!atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED,
+                              &adapter->nameserver_port->status)) {
+               ret = zfcp_erp_port_reopen(adapter->nameserver_port, 0, 148,
+                                          NULL);
+               if (ret)
+                       return ret;
+               zfcp_erp_wait(adapter);
+               zfcp_port_put(adapter->nameserver_port);
+       }
+       return !atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED,
+                                 &adapter->nameserver_port->status);
+}
+
+static void zfcp_gpn_ft_handler(unsigned long _done)
+{
+       complete((struct completion *)_done);
+}
+
+static void zfcp_free_sg_env(struct zfcp_gpn_ft *gpn_ft)
+{
+       struct scatterlist *sg = &gpn_ft->sg_req;
+
+       kfree(sg_virt(sg)); /* free request buffer */
+       zfcp_sg_free_table(gpn_ft->sg_resp, ZFCP_GPN_FT_BUFFERS);
+
+       kfree(gpn_ft);
+}
+
+static struct zfcp_gpn_ft *zfcp_alloc_sg_env(void)
+{
+       struct zfcp_gpn_ft *gpn_ft;
+       struct ct_iu_gpn_ft_req *req;
+
+       gpn_ft = kzalloc(sizeof(*gpn_ft), GFP_KERNEL);
+       if (!gpn_ft)
+               return NULL;
+
+       req = kzalloc(sizeof(struct ct_iu_gpn_ft_req), GFP_KERNEL);
+       if (!req) {
+               kfree(gpn_ft);
+               gpn_ft = NULL;
+               goto out;
+       }
+       sg_init_one(&gpn_ft->sg_req, req, sizeof(*req));
+
+       if (zfcp_sg_setup_table(gpn_ft->sg_resp, ZFCP_GPN_FT_BUFFERS)) {
+               zfcp_free_sg_env(gpn_ft);
+               gpn_ft = NULL;
+       }
+out:
+       return gpn_ft;
+}
+
+
+static int zfcp_scan_issue_gpn_ft(struct zfcp_gpn_ft *gpn_ft,
+                                 struct zfcp_adapter *adapter)
+{
+       struct zfcp_send_ct *ct = &gpn_ft->ct;
+       struct ct_iu_gpn_ft_req *req = sg_virt(&gpn_ft->sg_req);
+       struct completion done;
+       int ret;
+
+       /* prepare CT IU for GPN_FT */
+       req->header.revision = ZFCP_CT_REVISION;
+       req->header.gs_type = ZFCP_CT_DIRECTORY_SERVICE;
+       req->header.gs_subtype = ZFCP_CT_NAME_SERVER;
+       req->header.options = ZFCP_CT_SYNCHRONOUS;
+       req->header.cmd_rsp_code = ZFCP_CT_GPN_FT;
+       req->header.max_res_size = (sizeof(struct gpn_ft_resp_acc) *
+                                       (ZFCP_GPN_FT_MAX_ENTRIES - 1)) >> 2;
+       req->flags = 0;
+       req->domain_id_scope = 0;
+       req->area_id_scope = 0;
+       req->fc4_type = ZFCP_CT_SCSI_FCP;
+
+       /* prepare zfcp_send_ct */
+       ct->port = adapter->nameserver_port;
+       ct->handler = zfcp_gpn_ft_handler;
+       ct->handler_data = (unsigned long)&done;
+       ct->timeout = 10;
+       ct->req = &gpn_ft->sg_req;
+       ct->resp = gpn_ft->sg_resp;
+       ct->req_count = 1;
+       ct->resp_count = ZFCP_GPN_FT_BUFFERS;
+
+       init_completion(&done);
+       ret = zfcp_fsf_send_ct(ct, NULL, NULL);
+       if (!ret)
+               wait_for_completion(&done);
+       return ret;
+}
+
+static void zfcp_validate_port(struct zfcp_port *port)
+{
+       struct zfcp_adapter *adapter = port->adapter;
+
+       atomic_clear_mask(ZFCP_STATUS_COMMON_NOESC, &port->status);
+
+       if (port == adapter->nameserver_port)
+               return;
+       if ((port->supported_classes != 0) || (port->units != 0)) {
+               zfcp_port_put(port);
+               return;
+       }
+       zfcp_erp_port_shutdown(port, 0, 151, NULL);
+       zfcp_erp_wait(adapter);
+       zfcp_port_put(port);
+       zfcp_port_dequeue(port);
+}
+
+static int zfcp_scan_eval_gpn_ft(struct zfcp_gpn_ft *gpn_ft)
+{
+       struct zfcp_send_ct *ct = &gpn_ft->ct;
+       struct scatterlist *sg = gpn_ft->sg_resp;
+       struct ct_hdr *hdr = sg_virt(sg);
+       struct gpn_ft_resp_acc *acc = sg_virt(sg);
+       struct zfcp_adapter *adapter = ct->port->adapter;
+       struct zfcp_port *port, *tmp;
+       u32 d_id;
+       int ret = 0, x;
+
+       if (ct->status)
+               return -EIO;
+
+       if (hdr->cmd_rsp_code != ZFCP_CT_ACCEPT) {
+               if (hdr->reason_code == ZFCP_CT_UNABLE_TO_PERFORM_CMD)
+                       return -EAGAIN; /* might be a temporary condition */
+               return -EIO;
+       }
+
+       if (hdr->max_res_size)
+               return -E2BIG;
+
+       down(&zfcp_data.config_sema);
+
+       /* first entry is the header */
+       for (x = 1; x < ZFCP_GPN_FT_MAX_ENTRIES; x++) {
+               if (x % (ZFCP_GPN_FT_ENTRIES + 1))
+                       acc++;
+               else
+                       acc = sg_virt(++sg);
+
+               d_id = acc->port_id[0] << 16 | acc->port_id[1] << 8 |
+                      acc->port_id[2];
+
+               /* skip the adapter's port and known remote ports */
+               if (acc->wwpn == fc_host_port_name(adapter->scsi_host) ||
+                    zfcp_get_port_by_did(adapter, d_id))
+                       continue;
+
+               port = zfcp_port_enqueue(adapter, acc->wwpn,
+                                        ZFCP_STATUS_PORT_DID_DID |
+                                        ZFCP_STATUS_COMMON_NOESC, d_id);
+               if (port)
+                       zfcp_erp_port_reopen(port, 0, 149, NULL);
+               else
+                       ret = -ENOMEM;
+               if (acc->control & 0x80) /* last entry */
+                       break;
+       }
+
+       zfcp_erp_wait(adapter);
+       list_for_each_entry_safe(port, tmp, &adapter->port_list_head, list)
+               zfcp_validate_port(port);
+       up(&zfcp_data.config_sema);
+       return ret;
+}
+
+/**
+ * zfcp_scan_ports - scan remote ports and attach new ports
+ * @adapter: pointer to struct zfcp_adapter
+ */
+int zfcp_scan_ports(struct zfcp_adapter *adapter)
+{
+       int ret, i;
+       struct zfcp_gpn_ft *gpn_ft;
+
+       if (fc_host_port_type(adapter->scsi_host) != FC_PORTTYPE_NPORT)
+               return 0;
+
+       ret = zfcp_scan_get_nameserver(adapter);
+       if (ret)
+               return ret;
+
+       gpn_ft = zfcp_alloc_sg_env();
+       if (!gpn_ft)
+               return -ENOMEM;
+
+       for (i = 0; i < 3; i++) {
+               ret = zfcp_scan_issue_gpn_ft(gpn_ft, adapter);
+               if (!ret) {
+                       ret = zfcp_scan_eval_gpn_ft(gpn_ft);
+                       if (ret == -EAGAIN)
+                               ssleep(1);
+                       else
+                               break;
+               }
+       }
+       zfcp_free_sg_env(gpn_ft);
+
+       return ret;
+}
+
+
+void _zfcp_scan_ports_later(struct work_struct *work)
+{
+       zfcp_scan_ports(container_of(work, struct zfcp_adapter, scan_work));
+}
index 01ed5fb46c441d9dd88abade746b7847b7f8a704..243e792f2407267985c5fab99442d8fefa9630ef 100644 (file)
@@ -874,6 +874,9 @@ zfcp_fsf_status_read_handler(struct zfcp_fsf_req *fsf_req)
                if (status_buffer->status_subtype &
                    FSF_STATUS_READ_SUB_ACT_UPDATED)
                        zfcp_erp_adapter_access_changed(adapter, 135, fsf_req);
+               if (status_buffer->status_subtype &
+                   FSF_STATUS_READ_SUB_INCOMING_ELS)
+                       schedule_work(&adapter->scan_work);
                break;
 
        case FSF_STATUS_READ_CFDC_UPDATED:
index 1f2a8c21b731461895a21e619ec0a434b05fd4e5..a4cae60f69d44eba325c68e22e7d35febbb2d454 100644 (file)
@@ -84,6 +84,30 @@ zfcp_sysfs_port_add_store(struct device *dev, struct device_attribute *attr, con
 
 static DEVICE_ATTR(port_add, S_IWUSR, NULL, zfcp_sysfs_port_add_store);
 
+/**
+ * zfcp_sysfs_port_rescan - trigger manual port rescan
+ * @dev: pointer to belonging device
+ * @attr: pointer to struct device_attribute
+ * @buf: pointer to input buffer
+ * @count: number of bytes in buffer
+ */
+static ssize_t zfcp_sysfs_port_rescan_store(struct device *dev,
+                                           struct device_attribute *attr,
+                                           const char *buf, size_t count)
+{
+       struct zfcp_adapter *adapter;
+       int ret;
+
+       adapter = dev_get_drvdata(dev);
+       if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status))
+               return -EBUSY;
+
+       ret = zfcp_scan_ports(adapter);
+
+       return ret ? ret : (ssize_t) count;
+}
+static DEVICE_ATTR(port_rescan, S_IWUSR, NULL, zfcp_sysfs_port_rescan_store);
+
 /**
  * zfcp_sysfs_port_remove_store - remove a port from sysfs tree
  * @dev: pointer to belonging device
@@ -214,6 +238,7 @@ static struct attribute *zfcp_adapter_attrs[] = {
        &dev_attr_in_recovery.attr,
        &dev_attr_port_remove.attr,
        &dev_attr_port_add.attr,
+       &dev_attr_port_rescan.attr,
        &dev_attr_peer_wwnn.attr,
        &dev_attr_peer_wwpn.attr,
        &dev_attr_peer_d_id.attr,