aboutsummaryrefslogtreecommitdiff
path: root/drivers/s390/scsi/zfcp_fc.c
diff options
context:
space:
mode:
authorSwen Schillig <swen@vnet.ibm.com>2008-06-10 18:21:00 +0200
committerJames Bottomley <James.Bottomley@HansenPartnership.com>2008-07-12 08:22:26 -0500
commitcc8c282963bd258a5bf49d3aa52675a4ae6d31f6 (patch)
treed0f353c918158b203c71603d9afdf4b71c126f63 /drivers/s390/scsi/zfcp_fc.c
parent85a82392fe6fe7620d8fe0eb694f926cefe62e1f (diff)
[SCSI] zfcp: Automatically attach remote ports
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>
Diffstat (limited to 'drivers/s390/scsi/zfcp_fc.c')
-rw-r--r--drivers/s390/scsi/zfcp_fc.c248
1 files changed, 248 insertions, 0 deletions
diff --git a/drivers/s390/scsi/zfcp_fc.c b/drivers/s390/scsi/zfcp_fc.c
index bb07c3bf225..aa2d9a668d1 100644
--- a/drivers/s390/scsi/zfcp_fc.c
+++ b/drivers/s390/scsi/zfcp_fc.c
@@ -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));
+}