X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=drivers%2Fscsi%2Fscsi_transport_fc.c;h=9168883d0dfe09fb16a5123c6d6c0c46dafe3b5b;hb=a91a3a20e06621b9931793888583efe37db4e4e8;hp=35d1c1e8e345dd67efcb2afafdcee616b0c23f3d;hpb=1da177e4c3f41524e886b7f1b8a0c1fc7321cac2;p=safe%2Fjmp%2Flinux-2.6 diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index 35d1c1e..9168883 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -1,4 +1,4 @@ -/* +/* * FiberChannel transport specific attributes exported to sysfs. * * Copyright (c) 2003 Silicon Graphics, Inc. All rights reserved. @@ -19,9 +19,10 @@ * * ======== * - * Copyright (C) 2004-2005 James Smart, Emulex Corporation + * Copyright (C) 2004-2007 James Smart, Emulex Corporation * Rewrite for host, target, device, and remote port attributes, * statistics, and service functions... + * Add vports, etc * */ #include @@ -30,16 +31,49 @@ #include #include #include +#include +#include +#include +#include #include "scsi_priv.h" +#include "scsi_transport_fc_internal.h" + +static int fc_queue_work(struct Scsi_Host *, struct work_struct *); +static void fc_vport_sched_delete(struct work_struct *work); + +/* + * This is a temporary carrier for creating a vport. It will eventually + * be replaced by a real message definition for sgio or netlink. + * + * fc_vport_identifiers: This set of data contains all elements + * to uniquely identify and instantiate a FC virtual port. + * + * Notes: + * symbolic_name: The driver is to append the symbolic_name string data + * to the symbolic_node_name data that it generates by default. + * the resulting combination should then be registered with the switch. + * It is expected that things like Xen may stuff a VM title into + * this field. + */ +struct fc_vport_identifiers { + u64 node_name; + u64 port_name; + u32 roles; + bool disable; + enum fc_port_type vport_type; /* only FC_PORTTYPE_NPIV allowed */ + char symbolic_name[FC_VPORT_SYMBOLIC_NAMELEN]; +}; -#define FC_PRINTK(x, l, f, a...) printk(l "scsi(%d:%d:%d:%d): " f, (x)->host->host_no, (x)->channel, (x)->id, (x)->lun , ##a) +static int fc_vport_create(struct Scsi_Host *shost, int channel, + struct device *pdev, struct fc_vport_identifiers *ids, + struct fc_vport **vport); /* * Redefine so that we can have same named attributes in the * sdev/starget/host objects. */ -#define FC_CLASS_DEVICE_ATTR(_prefix,_name,_mode,_show,_store) \ -struct class_device_attribute class_device_attr_##_prefix##_##_name = \ +#define FC_DEVICE_ATTR(_prefix,_name,_mode,_show,_store) \ +struct device_attribute device_attr_##_prefix##_##_name = \ __ATTR(_name,_mode,_show,_store) #define fc_enum_name_search(title, table_type, table) \ @@ -48,7 +82,7 @@ static const char *get_fc_##title##_name(enum table_type table_key) \ int i; \ char *name = NULL; \ \ - for (i = 0; i < sizeof(table)/sizeof(table[0]); i++) { \ + for (i = 0; i < ARRAY_SIZE(table); i++) { \ if (table[i].value == table_key) { \ name = table[i].name; \ break; \ @@ -63,7 +97,7 @@ static int get_fc_##title##_match(const char *table_key, \ { \ int i; \ \ - for (i = 0; i < sizeof(table)/sizeof(table[0]); i++) { \ + for (i = 0; i < ARRAY_SIZE(table); i++) { \ if (strncmp(table_key, table[i].name, \ table[i].matchlen) == 0) { \ *value = table[i].value; \ @@ -86,10 +120,37 @@ static struct { { FC_PORTTYPE_NLPORT, "NLPort (fabric via loop)" }, { FC_PORTTYPE_LPORT, "LPort (private loop)" }, { FC_PORTTYPE_PTP, "Point-To-Point (direct nport connection" }, + { FC_PORTTYPE_NPIV, "NPIV VPORT" }, }; fc_enum_name_search(port_type, fc_port_type, fc_port_type_names) #define FC_PORTTYPE_MAX_NAMELEN 50 +/* Reuse fc_port_type enum function for vport_type */ +#define get_fc_vport_type_name get_fc_port_type_name + + +/* Convert fc_host_event_code values to ascii string name */ +static const struct { + enum fc_host_event_code value; + char *name; +} fc_host_event_code_names[] = { + { FCH_EVT_LIP, "lip" }, + { FCH_EVT_LINKUP, "link_up" }, + { FCH_EVT_LINKDOWN, "link_down" }, + { FCH_EVT_LIPRESET, "lip_reset" }, + { FCH_EVT_RSCN, "rscn" }, + { FCH_EVT_ADAPTER_CHANGE, "adapter_chg" }, + { FCH_EVT_PORT_UNKNOWN, "port_unknown" }, + { FCH_EVT_PORT_ONLINE, "port_online" }, + { FCH_EVT_PORT_OFFLINE, "port_offline" }, + { FCH_EVT_PORT_FABRIC, "port_fabric" }, + { FCH_EVT_LINK_UNKNOWN, "link_unknown" }, + { FCH_EVT_VENDOR_UNIQUE, "vendor_unique" }, +}; +fc_enum_name_search(host_event_code, fc_host_event_code, + fc_host_event_code_names) +#define FC_HOST_EVENT_CODE_MAX_NAMELEN 30 + /* Convert fc_port_state values to ascii string name */ static struct { @@ -106,13 +167,37 @@ static struct { { FC_PORTSTATE_LINKDOWN, "Linkdown" }, { FC_PORTSTATE_ERROR, "Error" }, { FC_PORTSTATE_LOOPBACK, "Loopback" }, + { FC_PORTSTATE_DELETED, "Deleted" }, }; fc_enum_name_search(port_state, fc_port_state, fc_port_state_names) #define FC_PORTSTATE_MAX_NAMELEN 20 -/* Convert fc_tgtid_binding_type values to ascii string name */ +/* Convert fc_vport_state values to ascii string name */ static struct { + enum fc_vport_state value; + char *name; +} fc_vport_state_names[] = { + { FC_VPORT_UNKNOWN, "Unknown" }, + { FC_VPORT_ACTIVE, "Active" }, + { FC_VPORT_DISABLED, "Disabled" }, + { FC_VPORT_LINKDOWN, "Linkdown" }, + { FC_VPORT_INITIALIZING, "Initializing" }, + { FC_VPORT_NO_FABRIC_SUPP, "No Fabric Support" }, + { FC_VPORT_NO_FABRIC_RSCS, "No Fabric Resources" }, + { FC_VPORT_FABRIC_LOGOUT, "Fabric Logout" }, + { FC_VPORT_FABRIC_REJ_WWN, "Fabric Rejected WWN" }, + { FC_VPORT_FAILED, "VPort Failed" }, +}; +fc_enum_name_search(vport_state, fc_vport_state, fc_vport_state_names) +#define FC_VPORTSTATE_MAX_NAMELEN 24 + +/* Reuse fc_vport_state enum function for vport_last_state */ +#define get_fc_vport_last_state_name get_fc_vport_state_name + + +/* Convert fc_tgtid_binding_type values to ascii string name */ +static const struct { enum fc_tgtid_binding_type value; char *name; int matchlen; @@ -137,7 +222,7 @@ get_fc_##title##_names(u32 table_key, char *buf) \ ssize_t len = 0; \ int i; \ \ - for (i = 0; i < sizeof(table)/sizeof(table[0]); i++) { \ + for (i = 0; i < ARRAY_SIZE(table); i++) { \ if (table[i].value & table_key) { \ len += sprintf(buf + len, "%s%s", \ prefix, table[i].name); \ @@ -150,7 +235,7 @@ get_fc_##title##_names(u32 table_key, char *buf) \ /* Convert FC_COS bit values to ascii string name */ -static struct { +static const struct { u32 value; char *name; } fc_cos_names[] = { @@ -164,7 +249,7 @@ fc_bitfield_name_search(cos, fc_cos_names) /* Convert FC_PORTSPEED bit values to ascii string name */ -static struct { +static const struct { u32 value; char *name; } fc_port_speed_names[] = { @@ -172,6 +257,8 @@ static struct { { FC_PORTSPEED_2GBIT, "2 Gbit" }, { FC_PORTSPEED_4GBIT, "4 Gbit" }, { FC_PORTSPEED_10GBIT, "10 Gbit" }, + { FC_PORTSPEED_8GBIT, "8 Gbit" }, + { FC_PORTSPEED_16GBIT, "16 Gbit" }, { FC_PORTSPEED_NOT_NEGOTIATED, "Not Negotiated" }, }; fc_bitfield_name_search(port_speed, fc_port_speed_names) @@ -189,16 +276,16 @@ show_fc_fc4s (char *buf, u8 *fc4_list) } -/* Convert FC_RPORT_ROLE bit values to ascii string name */ -static struct { +/* Convert FC_PORT_ROLE bit values to ascii string name */ +static const struct { u32 value; char *name; -} fc_remote_port_role_names[] = { - { FC_RPORT_ROLE_FCP_TARGET, "FCP Target" }, - { FC_RPORT_ROLE_FCP_INITIATOR, "FCP Initiator" }, - { FC_RPORT_ROLE_IP_PORT, "IP Port" }, +} fc_port_role_names[] = { + { FC_PORT_ROLE_FCP_TARGET, "FCP Target" }, + { FC_PORT_ROLE_FCP_INITIATOR, "FCP Initiator" }, + { FC_PORT_ROLE_IP_PORT, "IP Port" }, }; -fc_bitfield_name_search(remote_port_roles, fc_remote_port_role_names) +fc_bitfield_name_search(port_roles, fc_port_role_names) /* * Define roles that are specific to port_id. Values are relative to ROLE_MASK. @@ -212,17 +299,18 @@ fc_bitfield_name_search(remote_port_roles, fc_remote_port_role_names) #define FC_MGMTSRVR_PORTID 0x00000a -static void fc_timeout_blocked_rport(void *data); -static void fc_scsi_scan_rport(void *data); -static void fc_rport_terminate(struct fc_rport *rport); +static void fc_timeout_deleted_rport(struct work_struct *work); +static void fc_timeout_fail_rport_io(struct work_struct *work); +static void fc_scsi_scan_rport(struct work_struct *work); /* * Attribute counts pre object type... * Increase these values if you add attributes */ #define FC_STARGET_NUM_ATTRS 3 -#define FC_RPORT_NUM_ATTRS 9 -#define FC_HOST_NUM_ATTRS 15 +#define FC_RPORT_NUM_ATTRS 10 +#define FC_VPORT_NUM_ATTRS 9 +#define FC_HOST_NUM_ATTRS 21 struct fc_internal { struct scsi_transport_template t; @@ -238,21 +326,26 @@ struct fc_internal { * part of the midlayer. As the remote port is specific to the * fc transport, we must provide the attribute container. */ - struct class_device_attribute private_starget_attrs[ + struct device_attribute private_starget_attrs[ FC_STARGET_NUM_ATTRS]; - struct class_device_attribute *starget_attrs[FC_STARGET_NUM_ATTRS + 1]; + struct device_attribute *starget_attrs[FC_STARGET_NUM_ATTRS + 1]; - struct class_device_attribute private_host_attrs[FC_HOST_NUM_ATTRS]; - struct class_device_attribute *host_attrs[FC_HOST_NUM_ATTRS + 1]; + struct device_attribute private_host_attrs[FC_HOST_NUM_ATTRS]; + struct device_attribute *host_attrs[FC_HOST_NUM_ATTRS + 1]; struct transport_container rport_attr_cont; - struct class_device_attribute private_rport_attrs[FC_RPORT_NUM_ATTRS]; - struct class_device_attribute *rport_attrs[FC_RPORT_NUM_ATTRS + 1]; + struct device_attribute private_rport_attrs[FC_RPORT_NUM_ATTRS]; + struct device_attribute *rport_attrs[FC_RPORT_NUM_ATTRS + 1]; + + struct transport_container vport_attr_cont; + struct device_attribute private_vport_attrs[FC_VPORT_NUM_ATTRS]; + struct device_attribute *vport_attrs[FC_VPORT_NUM_ATTRS + 1]; }; #define to_fc_internal(tmpl) container_of(tmpl, struct fc_internal, t) -static int fc_target_setup(struct device *dev) +static int fc_target_setup(struct transport_container *tc, struct device *dev, + struct device *cdev) { struct scsi_target *starget = to_scsi_target(dev); struct fc_rport *rport = starget_to_rport(starget); @@ -281,41 +374,66 @@ static DECLARE_TRANSPORT_CLASS(fc_transport_class, NULL, NULL); -static int fc_host_setup(struct device *dev) +static int fc_host_setup(struct transport_container *tc, struct device *dev, + struct device *cdev) { struct Scsi_Host *shost = dev_to_shost(dev); + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); - /* + /* * Set default values easily detected by the midlayer as * failure cases. The scsi lldd is responsible for initializing * all transport attributes to valid values per host. */ - fc_host_node_name(shost) = -1; - fc_host_port_name(shost) = -1; - fc_host_supported_classes(shost) = FC_COS_UNSPECIFIED; - memset(fc_host_supported_fc4s(shost), 0, - sizeof(fc_host_supported_fc4s(shost))); - memset(fc_host_symbolic_name(shost), 0, - sizeof(fc_host_symbolic_name(shost))); - fc_host_supported_speeds(shost) = FC_PORTSPEED_UNKNOWN; - fc_host_maxframe_size(shost) = -1; - memset(fc_host_serial_number(shost), 0, - sizeof(fc_host_serial_number(shost))); - - fc_host_port_id(shost) = -1; - fc_host_port_type(shost) = FC_PORTTYPE_UNKNOWN; - fc_host_port_state(shost) = FC_PORTSTATE_UNKNOWN; - memset(fc_host_active_fc4s(shost), 0, - sizeof(fc_host_active_fc4s(shost))); - fc_host_speed(shost) = FC_PORTSPEED_UNKNOWN; - fc_host_fabric_name(shost) = -1; - - fc_host_tgtid_bind_type(shost) = FC_TGTID_BIND_BY_WWPN; - - INIT_LIST_HEAD(&fc_host_rports(shost)); - INIT_LIST_HEAD(&fc_host_rport_bindings(shost)); - fc_host_next_rport_number(shost) = 0; - fc_host_next_target_id(shost) = 0; + fc_host->node_name = -1; + fc_host->port_name = -1; + fc_host->permanent_port_name = -1; + fc_host->supported_classes = FC_COS_UNSPECIFIED; + memset(fc_host->supported_fc4s, 0, + sizeof(fc_host->supported_fc4s)); + fc_host->supported_speeds = FC_PORTSPEED_UNKNOWN; + fc_host->maxframe_size = -1; + fc_host->max_npiv_vports = 0; + memset(fc_host->serial_number, 0, + sizeof(fc_host->serial_number)); + + fc_host->port_id = -1; + fc_host->port_type = FC_PORTTYPE_UNKNOWN; + fc_host->port_state = FC_PORTSTATE_UNKNOWN; + memset(fc_host->active_fc4s, 0, + sizeof(fc_host->active_fc4s)); + fc_host->speed = FC_PORTSPEED_UNKNOWN; + fc_host->fabric_name = -1; + memset(fc_host->symbolic_name, 0, sizeof(fc_host->symbolic_name)); + memset(fc_host->system_hostname, 0, sizeof(fc_host->system_hostname)); + + fc_host->tgtid_bind_type = FC_TGTID_BIND_BY_WWPN; + + INIT_LIST_HEAD(&fc_host->rports); + INIT_LIST_HEAD(&fc_host->rport_bindings); + INIT_LIST_HEAD(&fc_host->vports); + fc_host->next_rport_number = 0; + fc_host->next_target_id = 0; + fc_host->next_vport_number = 0; + fc_host->npiv_vports_inuse = 0; + + snprintf(fc_host->work_q_name, sizeof(fc_host->work_q_name), + "fc_wq_%d", shost->host_no); + fc_host->work_q = create_singlethread_workqueue( + fc_host->work_q_name); + if (!fc_host->work_q) + return -ENOMEM; + + snprintf(fc_host->devloss_work_q_name, + sizeof(fc_host->devloss_work_q_name), + "fc_dl_%d", shost->host_no); + fc_host->devloss_work_q = create_singlethread_workqueue( + fc_host->devloss_work_q_name); + if (!fc_host->devloss_work_q) { + destroy_workqueue(fc_host->work_q); + fc_host->work_q = NULL; + return -ENOMEM; + } return 0; } @@ -337,6 +455,16 @@ static DECLARE_TRANSPORT_CLASS(fc_rport_class, NULL); /* + * Setup and Remove actions for virtual ports are handled + * in the service functions below. + */ +static DECLARE_TRANSPORT_CLASS(fc_vport_class, + "fc_vports", + NULL, + NULL, + NULL); + +/* * Module Parameters */ @@ -345,19 +473,194 @@ static DECLARE_TRANSPORT_CLASS(fc_rport_class, * should insulate the loss of a remote port. * The maximum will be capped by the value of SCSI_DEVICE_BLOCK_MAX_TIMEOUT. */ -static unsigned int fc_dev_loss_tmo = SCSI_DEVICE_BLOCK_MAX_TIMEOUT; +static unsigned int fc_dev_loss_tmo = 60; /* seconds */ -module_param_named(dev_loss_tmo, fc_dev_loss_tmo, int, S_IRUGO|S_IWUSR); +module_param_named(dev_loss_tmo, fc_dev_loss_tmo, uint, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(dev_loss_tmo, "Maximum number of seconds that the FC transport should" " insulate the loss of a remote port. Once this value is" " exceeded, the scsi target is removed. Value should be" " between 1 and SCSI_DEVICE_BLOCK_MAX_TIMEOUT."); +/* + * Netlink Infrastructure + */ + +static atomic_t fc_event_seq; + +/** + * fc_get_event_number - Obtain the next sequential FC event number + * + * Notes: + * We could have inlined this, but it would have required fc_event_seq to + * be exposed. For now, live with the subroutine call. + * Atomic used to avoid lock/unlock... + */ +u32 +fc_get_event_number(void) +{ + return atomic_add_return(1, &fc_event_seq); +} +EXPORT_SYMBOL(fc_get_event_number); + + +/** + * fc_host_post_event - called to post an even on an fc_host. + * @shost: host the event occurred on + * @event_number: fc event number obtained from get_fc_event_number() + * @event_code: fc_host event being posted + * @event_data: 32bits of data for the event being posted + * + * Notes: + * This routine assumes no locks are held on entry. + */ +void +fc_host_post_event(struct Scsi_Host *shost, u32 event_number, + enum fc_host_event_code event_code, u32 event_data) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct fc_nl_event *event; + const char *name; + u32 len, skblen; + int err; + + if (!scsi_nl_sock) { + err = -ENOENT; + goto send_fail; + } + + len = FC_NL_MSGALIGN(sizeof(*event)); + skblen = NLMSG_SPACE(len); + + skb = alloc_skb(skblen, GFP_KERNEL); + if (!skb) { + err = -ENOBUFS; + goto send_fail; + } + + nlh = nlmsg_put(skb, 0, 0, SCSI_TRANSPORT_MSG, + skblen - sizeof(*nlh), 0); + if (!nlh) { + err = -ENOBUFS; + goto send_fail_skb; + } + event = NLMSG_DATA(nlh); + + INIT_SCSI_NL_HDR(&event->snlh, SCSI_NL_TRANSPORT_FC, + FC_NL_ASYNC_EVENT, len); + event->seconds = get_seconds(); + event->vendor_id = 0; + event->host_no = shost->host_no; + event->event_datalen = sizeof(u32); /* bytes */ + event->event_num = event_number; + event->event_code = event_code; + event->event_data = event_data; + + err = nlmsg_multicast(scsi_nl_sock, skb, 0, SCSI_NL_GRP_FC_EVENTS, + GFP_KERNEL); + if (err && (err != -ESRCH)) /* filter no recipient errors */ + /* nlmsg_multicast already kfree_skb'd */ + goto send_fail; + + return; + +send_fail_skb: + kfree_skb(skb); +send_fail: + name = get_fc_host_event_code_name(event_code); + printk(KERN_WARNING + "%s: Dropped Event : host %d %s data 0x%08x - err %d\n", + __func__, shost->host_no, + (name) ? name : "", event_data, err); + return; +} +EXPORT_SYMBOL(fc_host_post_event); + + +/** + * fc_host_post_vendor_event - called to post a vendor unique event on an fc_host + * @shost: host the event occurred on + * @event_number: fc event number obtained from get_fc_event_number() + * @data_len: amount, in bytes, of vendor unique data + * @data_buf: pointer to vendor unique data + * @vendor_id: Vendor id + * + * Notes: + * This routine assumes no locks are held on entry. + */ +void +fc_host_post_vendor_event(struct Scsi_Host *shost, u32 event_number, + u32 data_len, char * data_buf, u64 vendor_id) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct fc_nl_event *event; + u32 len, skblen; + int err; + + if (!scsi_nl_sock) { + err = -ENOENT; + goto send_vendor_fail; + } + + len = FC_NL_MSGALIGN(sizeof(*event) + data_len); + skblen = NLMSG_SPACE(len); + + skb = alloc_skb(skblen, GFP_KERNEL); + if (!skb) { + err = -ENOBUFS; + goto send_vendor_fail; + } + + nlh = nlmsg_put(skb, 0, 0, SCSI_TRANSPORT_MSG, + skblen - sizeof(*nlh), 0); + if (!nlh) { + err = -ENOBUFS; + goto send_vendor_fail_skb; + } + event = NLMSG_DATA(nlh); + + INIT_SCSI_NL_HDR(&event->snlh, SCSI_NL_TRANSPORT_FC, + FC_NL_ASYNC_EVENT, len); + event->seconds = get_seconds(); + event->vendor_id = vendor_id; + event->host_no = shost->host_no; + event->event_datalen = data_len; /* bytes */ + event->event_num = event_number; + event->event_code = FCH_EVT_VENDOR_UNIQUE; + memcpy(&event->event_data, data_buf, data_len); + + err = nlmsg_multicast(scsi_nl_sock, skb, 0, SCSI_NL_GRP_FC_EVENTS, + GFP_KERNEL); + if (err && (err != -ESRCH)) /* filter no recipient errors */ + /* nlmsg_multicast already kfree_skb'd */ + goto send_vendor_fail; + + return; + +send_vendor_fail_skb: + kfree_skb(skb); +send_vendor_fail: + printk(KERN_WARNING + "%s: Dropped Event : host %d vendor_unique - err %d\n", + __func__, shost->host_no, err); + return; +} +EXPORT_SYMBOL(fc_host_post_vendor_event); + + static __init int fc_transport_init(void) { - int error = transport_class_register(&fc_host_class); + int error; + + atomic_set(&fc_event_seq, 0); + + error = transport_class_register(&fc_host_class); + if (error) + return error; + error = transport_class_register(&fc_vport_class); if (error) return error; error = transport_class_register(&fc_rport_class); @@ -371,6 +674,7 @@ static void __exit fc_transport_exit(void) transport_class_unregister(&fc_transport_class); transport_class_unregister(&fc_rport_class); transport_class_unregister(&fc_host_class); + transport_class_unregister(&fc_vport_class); } /* @@ -379,84 +683,98 @@ static void __exit fc_transport_exit(void) #define fc_rport_show_function(field, format_string, sz, cast) \ static ssize_t \ -show_fc_rport_##field (struct class_device *cdev, char *buf) \ +show_fc_rport_##field (struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ - struct fc_rport *rport = transport_class_to_rport(cdev); \ + struct fc_rport *rport = transport_class_to_rport(dev); \ struct Scsi_Host *shost = rport_to_shost(rport); \ struct fc_internal *i = to_fc_internal(shost->transportt); \ - if (i->f->get_rport_##field) \ + if ((i->f->get_rport_##field) && \ + !((rport->port_state == FC_PORTSTATE_BLOCKED) || \ + (rport->port_state == FC_PORTSTATE_DELETED) || \ + (rport->port_state == FC_PORTSTATE_NOTPRESENT))) \ i->f->get_rport_##field(rport); \ return snprintf(buf, sz, format_string, cast rport->field); \ } #define fc_rport_store_function(field) \ static ssize_t \ -store_fc_rport_##field(struct class_device *cdev, const char *buf, \ - size_t count) \ +store_fc_rport_##field(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ { \ int val; \ - struct fc_rport *rport = transport_class_to_rport(cdev); \ + struct fc_rport *rport = transport_class_to_rport(dev); \ struct Scsi_Host *shost = rport_to_shost(rport); \ struct fc_internal *i = to_fc_internal(shost->transportt); \ - val = simple_strtoul(buf, NULL, 0); \ + char *cp; \ + if ((rport->port_state == FC_PORTSTATE_BLOCKED) || \ + (rport->port_state == FC_PORTSTATE_DELETED) || \ + (rport->port_state == FC_PORTSTATE_NOTPRESENT)) \ + return -EBUSY; \ + val = simple_strtoul(buf, &cp, 0); \ + if (*cp && (*cp != '\n')) \ + return -EINVAL; \ i->f->set_rport_##field(rport, val); \ return count; \ } #define fc_rport_rd_attr(field, format_string, sz) \ fc_rport_show_function(field, format_string, sz, ) \ -static FC_CLASS_DEVICE_ATTR(rport, field, S_IRUGO, \ +static FC_DEVICE_ATTR(rport, field, S_IRUGO, \ show_fc_rport_##field, NULL) #define fc_rport_rd_attr_cast(field, format_string, sz, cast) \ fc_rport_show_function(field, format_string, sz, (cast)) \ -static FC_CLASS_DEVICE_ATTR(rport, field, S_IRUGO, \ +static FC_DEVICE_ATTR(rport, field, S_IRUGO, \ show_fc_rport_##field, NULL) #define fc_rport_rw_attr(field, format_string, sz) \ fc_rport_show_function(field, format_string, sz, ) \ fc_rport_store_function(field) \ -static FC_CLASS_DEVICE_ATTR(rport, field, S_IRUGO | S_IWUSR, \ +static FC_DEVICE_ATTR(rport, field, S_IRUGO | S_IWUSR, \ show_fc_rport_##field, \ store_fc_rport_##field) #define fc_private_rport_show_function(field, format_string, sz, cast) \ static ssize_t \ -show_fc_rport_##field (struct class_device *cdev, char *buf) \ +show_fc_rport_##field (struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ - struct fc_rport *rport = transport_class_to_rport(cdev); \ + struct fc_rport *rport = transport_class_to_rport(dev); \ return snprintf(buf, sz, format_string, cast rport->field); \ } #define fc_private_rport_rd_attr(field, format_string, sz) \ fc_private_rport_show_function(field, format_string, sz, ) \ -static FC_CLASS_DEVICE_ATTR(rport, field, S_IRUGO, \ +static FC_DEVICE_ATTR(rport, field, S_IRUGO, \ show_fc_rport_##field, NULL) #define fc_private_rport_rd_attr_cast(field, format_string, sz, cast) \ fc_private_rport_show_function(field, format_string, sz, (cast)) \ -static FC_CLASS_DEVICE_ATTR(rport, field, S_IRUGO, \ +static FC_DEVICE_ATTR(rport, field, S_IRUGO, \ show_fc_rport_##field, NULL) #define fc_private_rport_rd_enum_attr(title, maxlen) \ static ssize_t \ -show_fc_rport_##title (struct class_device *cdev, char *buf) \ +show_fc_rport_##title (struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ - struct fc_rport *rport = transport_class_to_rport(cdev); \ + struct fc_rport *rport = transport_class_to_rport(dev); \ const char *name; \ name = get_fc_##title##_name(rport->title); \ if (!name) \ return -EINVAL; \ return snprintf(buf, maxlen, "%s\n", name); \ } \ -static FC_CLASS_DEVICE_ATTR(rport, title, S_IRUGO, \ +static FC_DEVICE_ATTR(rport, title, S_IRUGO, \ show_fc_rport_##title, NULL) #define SETUP_RPORT_ATTRIBUTE_RD(field) \ - i->private_rport_attrs[count] = class_device_attr_rport_##field; \ + i->private_rport_attrs[count] = device_attr_rport_##field; \ i->private_rport_attrs[count].attr.mode = S_IRUGO; \ i->private_rport_attrs[count].store = NULL; \ i->rport_attrs[count] = &i->private_rport_attrs[count]; \ @@ -464,14 +782,14 @@ static FC_CLASS_DEVICE_ATTR(rport, title, S_IRUGO, \ count++ #define SETUP_PRIVATE_RPORT_ATTRIBUTE_RD(field) \ - i->private_rport_attrs[count] = class_device_attr_rport_##field; \ + i->private_rport_attrs[count] = device_attr_rport_##field; \ i->private_rport_attrs[count].attr.mode = S_IRUGO; \ i->private_rport_attrs[count].store = NULL; \ i->rport_attrs[count] = &i->private_rport_attrs[count]; \ count++ #define SETUP_RPORT_ATTRIBUTE_RW(field) \ - i->private_rport_attrs[count] = class_device_attr_rport_##field; \ + i->private_rport_attrs[count] = device_attr_rport_##field; \ if (!i->f->set_rport_##field) { \ i->private_rport_attrs[count].attr.mode = S_IRUGO; \ i->private_rport_attrs[count].store = NULL; \ @@ -480,6 +798,13 @@ static FC_CLASS_DEVICE_ATTR(rport, title, S_IRUGO, \ if (i->f->show_rport_##field) \ count++ +#define SETUP_PRIVATE_RPORT_ATTRIBUTE_RW(field) \ +{ \ + i->private_rport_attrs[count] = device_attr_rport_##field; \ + i->rport_attrs[count] = &i->private_rport_attrs[count]; \ + count++; \ +} + /* The FC Transport Remote Port Attributes: */ @@ -488,19 +813,45 @@ static FC_CLASS_DEVICE_ATTR(rport, title, S_IRUGO, \ fc_private_rport_rd_attr(maxframe_size, "%u bytes\n", 20); static ssize_t -show_fc_rport_supported_classes (struct class_device *cdev, char *buf) +show_fc_rport_supported_classes (struct device *dev, + struct device_attribute *attr, char *buf) { - struct fc_rport *rport = transport_class_to_rport(cdev); + struct fc_rport *rport = transport_class_to_rport(dev); if (rport->supported_classes == FC_COS_UNSPECIFIED) return snprintf(buf, 20, "unspecified\n"); return get_fc_cos_names(rport->supported_classes, buf); } -static FC_CLASS_DEVICE_ATTR(rport, supported_classes, S_IRUGO, +static FC_DEVICE_ATTR(rport, supported_classes, S_IRUGO, show_fc_rport_supported_classes, NULL); /* Dynamic Remote Port Attributes */ -fc_rport_rw_attr(dev_loss_tmo, "%d\n", 20); +/* + * dev_loss_tmo attribute + */ +fc_rport_show_function(dev_loss_tmo, "%d\n", 20, ) +static ssize_t +store_fc_rport_dev_loss_tmo(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int val; + struct fc_rport *rport = transport_class_to_rport(dev); + struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_internal *i = to_fc_internal(shost->transportt); + char *cp; + if ((rport->port_state == FC_PORTSTATE_BLOCKED) || + (rport->port_state == FC_PORTSTATE_DELETED) || + (rport->port_state == FC_PORTSTATE_NOTPRESENT)) + return -EBUSY; + val = simple_strtoul(buf, &cp, 0); + if ((*cp && (*cp != '\n')) || + (val < 0) || (val > SCSI_DEVICE_BLOCK_MAX_TIMEOUT)) + return -EINVAL; + i->f->set_rport_dev_loss_tmo(rport, val); + return count; +} +static FC_DEVICE_ATTR(rport, dev_loss_tmo, S_IRUGO | S_IWUSR, + show_fc_rport_dev_loss_tmo, store_fc_rport_dev_loss_tmo); /* Private Remote Port Attributes */ @@ -510,9 +861,10 @@ fc_private_rport_rd_attr_cast(port_name, "0x%llx\n", 20, unsigned long long); fc_private_rport_rd_attr(port_id, "0x%06x\n", 20); static ssize_t -show_fc_rport_roles (struct class_device *cdev, char *buf) +show_fc_rport_roles (struct device *dev, struct device_attribute *attr, + char *buf) { - struct fc_rport *rport = transport_class_to_rport(cdev); + struct fc_rport *rport = transport_class_to_rport(dev); /* identify any roles that are port_id specific */ if ((rport->port_id != -1) && @@ -533,17 +885,57 @@ show_fc_rport_roles (struct class_device *cdev, char *buf) return snprintf(buf, 30, "Unknown Fabric Entity\n"); } } else { - if (rport->roles == FC_RPORT_ROLE_UNKNOWN) + if (rport->roles == FC_PORT_ROLE_UNKNOWN) return snprintf(buf, 20, "unknown\n"); - return get_fc_remote_port_roles_names(rport->roles, buf); + return get_fc_port_roles_names(rport->roles, buf); } } -static FC_CLASS_DEVICE_ATTR(rport, roles, S_IRUGO, +static FC_DEVICE_ATTR(rport, roles, S_IRUGO, show_fc_rport_roles, NULL); fc_private_rport_rd_enum_attr(port_state, FC_PORTSTATE_MAX_NAMELEN); fc_private_rport_rd_attr(scsi_target_id, "%d\n", 20); +/* + * fast_io_fail_tmo attribute + */ +static ssize_t +show_fc_rport_fast_io_fail_tmo (struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fc_rport *rport = transport_class_to_rport(dev); + + if (rport->fast_io_fail_tmo == -1) + return snprintf(buf, 5, "off\n"); + return snprintf(buf, 20, "%d\n", rport->fast_io_fail_tmo); +} + +static ssize_t +store_fc_rport_fast_io_fail_tmo(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int val; + char *cp; + struct fc_rport *rport = transport_class_to_rport(dev); + + if ((rport->port_state == FC_PORTSTATE_BLOCKED) || + (rport->port_state == FC_PORTSTATE_DELETED) || + (rport->port_state == FC_PORTSTATE_NOTPRESENT)) + return -EBUSY; + if (strncmp(buf, "off", 3) == 0) + rport->fast_io_fail_tmo = -1; + else { + val = simple_strtoul(buf, &cp, 0); + if ((*cp && (*cp != '\n')) || + (val < 0) || (val >= rport->dev_loss_tmo)) + return -EINVAL; + rport->fast_io_fail_tmo = val; + } + return count; +} +static FC_DEVICE_ATTR(rport, fast_io_fail_tmo, S_IRUGO | S_IWUSR, + show_fc_rport_fast_io_fail_tmo, store_fc_rport_fast_io_fail_tmo); /* @@ -558,9 +950,10 @@ fc_private_rport_rd_attr(scsi_target_id, "%d\n", 20); */ #define fc_starget_show_function(field, format_string, sz, cast) \ static ssize_t \ -show_fc_starget_##field (struct class_device *cdev, char *buf) \ +show_fc_starget_##field (struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ - struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct scsi_target *starget = transport_class_to_starget(dev); \ struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); \ struct fc_internal *i = to_fc_internal(shost->transportt); \ struct fc_rport *rport = starget_to_rport(starget); \ @@ -574,16 +967,16 @@ show_fc_starget_##field (struct class_device *cdev, char *buf) \ #define fc_starget_rd_attr(field, format_string, sz) \ fc_starget_show_function(field, format_string, sz, ) \ -static FC_CLASS_DEVICE_ATTR(starget, field, S_IRUGO, \ +static FC_DEVICE_ATTR(starget, field, S_IRUGO, \ show_fc_starget_##field, NULL) #define fc_starget_rd_attr_cast(field, format_string, sz, cast) \ fc_starget_show_function(field, format_string, sz, (cast)) \ -static FC_CLASS_DEVICE_ATTR(starget, field, S_IRUGO, \ +static FC_DEVICE_ATTR(starget, field, S_IRUGO, \ show_fc_starget_##field, NULL) #define SETUP_STARGET_ATTRIBUTE_RD(field) \ - i->private_starget_attrs[count] = class_device_attr_starget_##field; \ + i->private_starget_attrs[count] = device_attr_starget_##field; \ i->private_starget_attrs[count].attr.mode = S_IRUGO; \ i->private_starget_attrs[count].store = NULL; \ i->starget_attrs[count] = &i->private_starget_attrs[count]; \ @@ -591,7 +984,7 @@ static FC_CLASS_DEVICE_ATTR(starget, field, S_IRUGO, \ count++ #define SETUP_STARGET_ATTRIBUTE_RW(field) \ - i->private_starget_attrs[count] = class_device_attr_starget_##field; \ + i->private_starget_attrs[count] = device_attr_starget_##field; \ if (!i->f->set_starget_##field) { \ i->private_starget_attrs[count].attr.mode = S_IRUGO; \ i->private_starget_attrs[count].store = NULL; \ @@ -607,14 +1000,275 @@ fc_starget_rd_attr(port_id, "0x%06x\n", 20); /* + * FC Virtual Port Attribute Management + */ + +#define fc_vport_show_function(field, format_string, sz, cast) \ +static ssize_t \ +show_fc_vport_##field (struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct fc_vport *vport = transport_class_to_vport(dev); \ + struct Scsi_Host *shost = vport_to_shost(vport); \ + struct fc_internal *i = to_fc_internal(shost->transportt); \ + if ((i->f->get_vport_##field) && \ + !(vport->flags & (FC_VPORT_DEL | FC_VPORT_CREATING))) \ + i->f->get_vport_##field(vport); \ + return snprintf(buf, sz, format_string, cast vport->field); \ +} + +#define fc_vport_store_function(field) \ +static ssize_t \ +store_fc_vport_##field(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + int val; \ + struct fc_vport *vport = transport_class_to_vport(dev); \ + struct Scsi_Host *shost = vport_to_shost(vport); \ + struct fc_internal *i = to_fc_internal(shost->transportt); \ + char *cp; \ + if (vport->flags & (FC_VPORT_DEL | FC_VPORT_CREATING)) \ + return -EBUSY; \ + val = simple_strtoul(buf, &cp, 0); \ + if (*cp && (*cp != '\n')) \ + return -EINVAL; \ + i->f->set_vport_##field(vport, val); \ + return count; \ +} + +#define fc_vport_store_str_function(field, slen) \ +static ssize_t \ +store_fc_vport_##field(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct fc_vport *vport = transport_class_to_vport(dev); \ + struct Scsi_Host *shost = vport_to_shost(vport); \ + struct fc_internal *i = to_fc_internal(shost->transportt); \ + unsigned int cnt=count; \ + \ + /* count may include a LF at end of string */ \ + if (buf[cnt-1] == '\n') \ + cnt--; \ + if (cnt > ((slen) - 1)) \ + return -EINVAL; \ + memcpy(vport->field, buf, cnt); \ + i->f->set_vport_##field(vport); \ + return count; \ +} + +#define fc_vport_rd_attr(field, format_string, sz) \ + fc_vport_show_function(field, format_string, sz, ) \ +static FC_DEVICE_ATTR(vport, field, S_IRUGO, \ + show_fc_vport_##field, NULL) + +#define fc_vport_rd_attr_cast(field, format_string, sz, cast) \ + fc_vport_show_function(field, format_string, sz, (cast)) \ +static FC_DEVICE_ATTR(vport, field, S_IRUGO, \ + show_fc_vport_##field, NULL) + +#define fc_vport_rw_attr(field, format_string, sz) \ + fc_vport_show_function(field, format_string, sz, ) \ + fc_vport_store_function(field) \ +static FC_DEVICE_ATTR(vport, field, S_IRUGO | S_IWUSR, \ + show_fc_vport_##field, \ + store_fc_vport_##field) + +#define fc_private_vport_show_function(field, format_string, sz, cast) \ +static ssize_t \ +show_fc_vport_##field (struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct fc_vport *vport = transport_class_to_vport(dev); \ + return snprintf(buf, sz, format_string, cast vport->field); \ +} + +#define fc_private_vport_store_u32_function(field) \ +static ssize_t \ +store_fc_vport_##field(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + u32 val; \ + struct fc_vport *vport = transport_class_to_vport(dev); \ + char *cp; \ + if (vport->flags & (FC_VPORT_DEL | FC_VPORT_CREATING)) \ + return -EBUSY; \ + val = simple_strtoul(buf, &cp, 0); \ + if (*cp && (*cp != '\n')) \ + return -EINVAL; \ + vport->field = val; \ + return count; \ +} + + +#define fc_private_vport_rd_attr(field, format_string, sz) \ + fc_private_vport_show_function(field, format_string, sz, ) \ +static FC_DEVICE_ATTR(vport, field, S_IRUGO, \ + show_fc_vport_##field, NULL) + +#define fc_private_vport_rd_attr_cast(field, format_string, sz, cast) \ + fc_private_vport_show_function(field, format_string, sz, (cast)) \ +static FC_DEVICE_ATTR(vport, field, S_IRUGO, \ + show_fc_vport_##field, NULL) + +#define fc_private_vport_rw_u32_attr(field, format_string, sz) \ + fc_private_vport_show_function(field, format_string, sz, ) \ + fc_private_vport_store_u32_function(field) \ +static FC_DEVICE_ATTR(vport, field, S_IRUGO | S_IWUSR, \ + show_fc_vport_##field, \ + store_fc_vport_##field) + + +#define fc_private_vport_rd_enum_attr(title, maxlen) \ +static ssize_t \ +show_fc_vport_##title (struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fc_vport *vport = transport_class_to_vport(dev); \ + const char *name; \ + name = get_fc_##title##_name(vport->title); \ + if (!name) \ + return -EINVAL; \ + return snprintf(buf, maxlen, "%s\n", name); \ +} \ +static FC_DEVICE_ATTR(vport, title, S_IRUGO, \ + show_fc_vport_##title, NULL) + + +#define SETUP_VPORT_ATTRIBUTE_RD(field) \ + i->private_vport_attrs[count] = device_attr_vport_##field; \ + i->private_vport_attrs[count].attr.mode = S_IRUGO; \ + i->private_vport_attrs[count].store = NULL; \ + i->vport_attrs[count] = &i->private_vport_attrs[count]; \ + if (i->f->get_##field) \ + count++ + /* NOTE: Above MACRO differs: checks function not show bit */ + +#define SETUP_PRIVATE_VPORT_ATTRIBUTE_RD(field) \ + i->private_vport_attrs[count] = device_attr_vport_##field; \ + i->private_vport_attrs[count].attr.mode = S_IRUGO; \ + i->private_vport_attrs[count].store = NULL; \ + i->vport_attrs[count] = &i->private_vport_attrs[count]; \ + count++ + +#define SETUP_VPORT_ATTRIBUTE_WR(field) \ + i->private_vport_attrs[count] = device_attr_vport_##field; \ + i->vport_attrs[count] = &i->private_vport_attrs[count]; \ + if (i->f->field) \ + count++ + /* NOTE: Above MACRO differs: checks function */ + +#define SETUP_VPORT_ATTRIBUTE_RW(field) \ + i->private_vport_attrs[count] = device_attr_vport_##field; \ + if (!i->f->set_vport_##field) { \ + i->private_vport_attrs[count].attr.mode = S_IRUGO; \ + i->private_vport_attrs[count].store = NULL; \ + } \ + i->vport_attrs[count] = &i->private_vport_attrs[count]; \ + count++ + /* NOTE: Above MACRO differs: does not check show bit */ + +#define SETUP_PRIVATE_VPORT_ATTRIBUTE_RW(field) \ +{ \ + i->private_vport_attrs[count] = device_attr_vport_##field; \ + i->vport_attrs[count] = &i->private_vport_attrs[count]; \ + count++; \ +} + + +/* The FC Transport Virtual Port Attributes: */ + +/* Fixed Virtual Port Attributes */ + +/* Dynamic Virtual Port Attributes */ + +/* Private Virtual Port Attributes */ + +fc_private_vport_rd_enum_attr(vport_state, FC_VPORTSTATE_MAX_NAMELEN); +fc_private_vport_rd_enum_attr(vport_last_state, FC_VPORTSTATE_MAX_NAMELEN); +fc_private_vport_rd_attr_cast(node_name, "0x%llx\n", 20, unsigned long long); +fc_private_vport_rd_attr_cast(port_name, "0x%llx\n", 20, unsigned long long); + +static ssize_t +show_fc_vport_roles (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fc_vport *vport = transport_class_to_vport(dev); + + if (vport->roles == FC_PORT_ROLE_UNKNOWN) + return snprintf(buf, 20, "unknown\n"); + return get_fc_port_roles_names(vport->roles, buf); +} +static FC_DEVICE_ATTR(vport, roles, S_IRUGO, show_fc_vport_roles, NULL); + +fc_private_vport_rd_enum_attr(vport_type, FC_PORTTYPE_MAX_NAMELEN); + +fc_private_vport_show_function(symbolic_name, "%s\n", + FC_VPORT_SYMBOLIC_NAMELEN + 1, ) +fc_vport_store_str_function(symbolic_name, FC_VPORT_SYMBOLIC_NAMELEN) +static FC_DEVICE_ATTR(vport, symbolic_name, S_IRUGO | S_IWUSR, + show_fc_vport_symbolic_name, store_fc_vport_symbolic_name); + +static ssize_t +store_fc_vport_delete(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fc_vport *vport = transport_class_to_vport(dev); + struct Scsi_Host *shost = vport_to_shost(vport); + + fc_queue_work(shost, &vport->vport_delete_work); + return count; +} +static FC_DEVICE_ATTR(vport, vport_delete, S_IWUSR, + NULL, store_fc_vport_delete); + + +/* + * Enable/Disable vport + * Write "1" to disable, write "0" to enable + */ +static ssize_t +store_fc_vport_disable(struct device *dev, struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct fc_vport *vport = transport_class_to_vport(dev); + struct Scsi_Host *shost = vport_to_shost(vport); + struct fc_internal *i = to_fc_internal(shost->transportt); + int stat; + + if (vport->flags & (FC_VPORT_DEL | FC_VPORT_CREATING)) + return -EBUSY; + + if (*buf == '0') { + if (vport->vport_state != FC_VPORT_DISABLED) + return -EALREADY; + } else if (*buf == '1') { + if (vport->vport_state == FC_VPORT_DISABLED) + return -EALREADY; + } else + return -EINVAL; + + stat = i->f->vport_disable(vport, ((*buf == '0') ? false : true)); + return stat ? stat : count; +} +static FC_DEVICE_ATTR(vport, vport_disable, S_IWUSR, + NULL, store_fc_vport_disable); + + +/* * Host Attribute Management */ #define fc_host_show_function(field, format_string, sz, cast) \ static ssize_t \ -show_fc_host_##field (struct class_device *cdev, char *buf) \ +show_fc_host_##field (struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ - struct Scsi_Host *shost = transport_class_to_shost(cdev); \ + struct Scsi_Host *shost = transport_class_to_shost(dev); \ struct fc_internal *i = to_fc_internal(shost->transportt); \ if (i->f->get_host_##field) \ i->f->get_host_##field(shost); \ @@ -623,40 +1277,65 @@ show_fc_host_##field (struct class_device *cdev, char *buf) \ #define fc_host_store_function(field) \ static ssize_t \ -store_fc_host_##field(struct class_device *cdev, const char *buf, \ - size_t count) \ +store_fc_host_##field(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ { \ int val; \ - struct Scsi_Host *shost = transport_class_to_shost(cdev); \ + struct Scsi_Host *shost = transport_class_to_shost(dev); \ struct fc_internal *i = to_fc_internal(shost->transportt); \ + char *cp; \ \ - val = simple_strtoul(buf, NULL, 0); \ + val = simple_strtoul(buf, &cp, 0); \ + if (*cp && (*cp != '\n')) \ + return -EINVAL; \ i->f->set_host_##field(shost, val); \ return count; \ } +#define fc_host_store_str_function(field, slen) \ +static ssize_t \ +store_fc_host_##field(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct Scsi_Host *shost = transport_class_to_shost(dev); \ + struct fc_internal *i = to_fc_internal(shost->transportt); \ + unsigned int cnt=count; \ + \ + /* count may include a LF at end of string */ \ + if (buf[cnt-1] == '\n') \ + cnt--; \ + if (cnt > ((slen) - 1)) \ + return -EINVAL; \ + memcpy(fc_host_##field(shost), buf, cnt); \ + i->f->set_host_##field(shost); \ + return count; \ +} + #define fc_host_rd_attr(field, format_string, sz) \ fc_host_show_function(field, format_string, sz, ) \ -static FC_CLASS_DEVICE_ATTR(host, field, S_IRUGO, \ +static FC_DEVICE_ATTR(host, field, S_IRUGO, \ show_fc_host_##field, NULL) #define fc_host_rd_attr_cast(field, format_string, sz, cast) \ fc_host_show_function(field, format_string, sz, (cast)) \ -static FC_CLASS_DEVICE_ATTR(host, field, S_IRUGO, \ +static FC_DEVICE_ATTR(host, field, S_IRUGO, \ show_fc_host_##field, NULL) #define fc_host_rw_attr(field, format_string, sz) \ fc_host_show_function(field, format_string, sz, ) \ fc_host_store_function(field) \ -static FC_CLASS_DEVICE_ATTR(host, field, S_IRUGO | S_IWUSR, \ +static FC_DEVICE_ATTR(host, field, S_IRUGO | S_IWUSR, \ show_fc_host_##field, \ store_fc_host_##field) #define fc_host_rd_enum_attr(title, maxlen) \ static ssize_t \ -show_fc_host_##title (struct class_device *cdev, char *buf) \ +show_fc_host_##title (struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ - struct Scsi_Host *shost = transport_class_to_shost(cdev); \ + struct Scsi_Host *shost = transport_class_to_shost(dev); \ struct fc_internal *i = to_fc_internal(shost->transportt); \ const char *name; \ if (i->f->get_host_##title) \ @@ -666,18 +1345,25 @@ show_fc_host_##title (struct class_device *cdev, char *buf) \ return -EINVAL; \ return snprintf(buf, maxlen, "%s\n", name); \ } \ -static FC_CLASS_DEVICE_ATTR(host, title, S_IRUGO, show_fc_host_##title, NULL) +static FC_DEVICE_ATTR(host, title, S_IRUGO, show_fc_host_##title, NULL) #define SETUP_HOST_ATTRIBUTE_RD(field) \ - i->private_host_attrs[count] = class_device_attr_host_##field; \ + i->private_host_attrs[count] = device_attr_host_##field; \ i->private_host_attrs[count].attr.mode = S_IRUGO; \ i->private_host_attrs[count].store = NULL; \ i->host_attrs[count] = &i->private_host_attrs[count]; \ if (i->f->show_host_##field) \ count++ +#define SETUP_HOST_ATTRIBUTE_RD_NS(field) \ + i->private_host_attrs[count] = device_attr_host_##field; \ + i->private_host_attrs[count].attr.mode = S_IRUGO; \ + i->private_host_attrs[count].store = NULL; \ + i->host_attrs[count] = &i->private_host_attrs[count]; \ + count++ + #define SETUP_HOST_ATTRIBUTE_RW(field) \ - i->private_host_attrs[count] = class_device_attr_host_##field; \ + i->private_host_attrs[count] = device_attr_host_##field; \ if (!i->f->set_host_##field) { \ i->private_host_attrs[count].attr.mode = S_IRUGO; \ i->private_host_attrs[count].store = NULL; \ @@ -689,86 +1375,95 @@ static FC_CLASS_DEVICE_ATTR(host, title, S_IRUGO, show_fc_host_##title, NULL) #define fc_private_host_show_function(field, format_string, sz, cast) \ static ssize_t \ -show_fc_host_##field (struct class_device *cdev, char *buf) \ +show_fc_host_##field (struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ - struct Scsi_Host *shost = transport_class_to_shost(cdev); \ + struct Scsi_Host *shost = transport_class_to_shost(dev); \ return snprintf(buf, sz, format_string, cast fc_host_##field(shost)); \ } #define fc_private_host_rd_attr(field, format_string, sz) \ fc_private_host_show_function(field, format_string, sz, ) \ -static FC_CLASS_DEVICE_ATTR(host, field, S_IRUGO, \ +static FC_DEVICE_ATTR(host, field, S_IRUGO, \ show_fc_host_##field, NULL) #define fc_private_host_rd_attr_cast(field, format_string, sz, cast) \ fc_private_host_show_function(field, format_string, sz, (cast)) \ -static FC_CLASS_DEVICE_ATTR(host, field, S_IRUGO, \ +static FC_DEVICE_ATTR(host, field, S_IRUGO, \ show_fc_host_##field, NULL) #define SETUP_PRIVATE_HOST_ATTRIBUTE_RD(field) \ - i->private_host_attrs[count] = class_device_attr_host_##field; \ + i->private_host_attrs[count] = device_attr_host_##field; \ i->private_host_attrs[count].attr.mode = S_IRUGO; \ i->private_host_attrs[count].store = NULL; \ i->host_attrs[count] = &i->private_host_attrs[count]; \ count++ #define SETUP_PRIVATE_HOST_ATTRIBUTE_RW(field) \ - i->private_host_attrs[count] = class_device_attr_host_##field; \ +{ \ + i->private_host_attrs[count] = device_attr_host_##field; \ i->host_attrs[count] = &i->private_host_attrs[count]; \ - count++ + count++; \ +} /* Fixed Host Attributes */ static ssize_t -show_fc_host_supported_classes (struct class_device *cdev, char *buf) +show_fc_host_supported_classes (struct device *dev, + struct device_attribute *attr, char *buf) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct Scsi_Host *shost = transport_class_to_shost(dev); if (fc_host_supported_classes(shost) == FC_COS_UNSPECIFIED) return snprintf(buf, 20, "unspecified\n"); return get_fc_cos_names(fc_host_supported_classes(shost), buf); } -static FC_CLASS_DEVICE_ATTR(host, supported_classes, S_IRUGO, +static FC_DEVICE_ATTR(host, supported_classes, S_IRUGO, show_fc_host_supported_classes, NULL); static ssize_t -show_fc_host_supported_fc4s (struct class_device *cdev, char *buf) +show_fc_host_supported_fc4s (struct device *dev, + struct device_attribute *attr, char *buf) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct Scsi_Host *shost = transport_class_to_shost(dev); return (ssize_t)show_fc_fc4s(buf, fc_host_supported_fc4s(shost)); } -static FC_CLASS_DEVICE_ATTR(host, supported_fc4s, S_IRUGO, +static FC_DEVICE_ATTR(host, supported_fc4s, S_IRUGO, show_fc_host_supported_fc4s, NULL); static ssize_t -show_fc_host_supported_speeds (struct class_device *cdev, char *buf) +show_fc_host_supported_speeds (struct device *dev, + struct device_attribute *attr, char *buf) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct Scsi_Host *shost = transport_class_to_shost(dev); if (fc_host_supported_speeds(shost) == FC_PORTSPEED_UNKNOWN) return snprintf(buf, 20, "unknown\n"); return get_fc_port_speed_names(fc_host_supported_speeds(shost), buf); } -static FC_CLASS_DEVICE_ATTR(host, supported_speeds, S_IRUGO, +static FC_DEVICE_ATTR(host, supported_speeds, S_IRUGO, show_fc_host_supported_speeds, NULL); fc_private_host_rd_attr_cast(node_name, "0x%llx\n", 20, unsigned long long); fc_private_host_rd_attr_cast(port_name, "0x%llx\n", 20, unsigned long long); -fc_private_host_rd_attr(symbolic_name, "%s\n", (FC_SYMBOLIC_NAME_SIZE +1)); +fc_private_host_rd_attr_cast(permanent_port_name, "0x%llx\n", 20, + unsigned long long); fc_private_host_rd_attr(maxframe_size, "%u bytes\n", 20); +fc_private_host_rd_attr(max_npiv_vports, "%u\n", 20); fc_private_host_rd_attr(serial_number, "%s\n", (FC_SERIAL_NUMBER_SIZE +1)); /* Dynamic Host Attributes */ static ssize_t -show_fc_host_active_fc4s (struct class_device *cdev, char *buf) +show_fc_host_active_fc4s (struct device *dev, + struct device_attribute *attr, char *buf) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct Scsi_Host *shost = transport_class_to_shost(dev); struct fc_internal *i = to_fc_internal(shost->transportt); if (i->f->get_host_active_fc4s) @@ -776,13 +1471,14 @@ show_fc_host_active_fc4s (struct class_device *cdev, char *buf) return (ssize_t)show_fc_fc4s(buf, fc_host_active_fc4s(shost)); } -static FC_CLASS_DEVICE_ATTR(host, active_fc4s, S_IRUGO, +static FC_DEVICE_ATTR(host, active_fc4s, S_IRUGO, show_fc_host_active_fc4s, NULL); static ssize_t -show_fc_host_speed (struct class_device *cdev, char *buf) +show_fc_host_speed (struct device *dev, + struct device_attribute *attr, char *buf) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct Scsi_Host *shost = transport_class_to_shost(dev); struct fc_internal *i = to_fc_internal(shost->transportt); if (i->f->get_host_speed) @@ -793,7 +1489,7 @@ show_fc_host_speed (struct class_device *cdev, char *buf) return get_fc_port_speed_names(fc_host_speed(shost), buf); } -static FC_CLASS_DEVICE_ATTR(host, speed, S_IRUGO, +static FC_DEVICE_ATTR(host, speed, S_IRUGO, show_fc_host_speed, NULL); @@ -801,14 +1497,22 @@ fc_host_rd_attr(port_id, "0x%06x\n", 20); fc_host_rd_enum_attr(port_type, FC_PORTTYPE_MAX_NAMELEN); fc_host_rd_enum_attr(port_state, FC_PORTSTATE_MAX_NAMELEN); fc_host_rd_attr_cast(fabric_name, "0x%llx\n", 20, unsigned long long); +fc_host_rd_attr(symbolic_name, "%s\n", FC_SYMBOLIC_NAME_SIZE + 1); + +fc_private_host_show_function(system_hostname, "%s\n", + FC_SYMBOLIC_NAME_SIZE + 1, ) +fc_host_store_str_function(system_hostname, FC_SYMBOLIC_NAME_SIZE) +static FC_DEVICE_ATTR(host, system_hostname, S_IRUGO | S_IWUSR, + show_fc_host_system_hostname, store_fc_host_system_hostname); /* Private Host Attributes */ static ssize_t -show_fc_private_host_tgtid_bind_type(struct class_device *cdev, char *buf) +show_fc_private_host_tgtid_bind_type(struct device *dev, + struct device_attribute *attr, char *buf) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct Scsi_Host *shost = transport_class_to_shost(dev); const char *name; name = get_fc_tgtid_bind_type_name(fc_host_tgtid_bind_type(shost)); @@ -817,12 +1521,15 @@ show_fc_private_host_tgtid_bind_type(struct class_device *cdev, char *buf) return snprintf(buf, FC_BINDTYPE_MAX_NAMELEN, "%s\n", name); } +#define get_list_head_entry(pos, head, member) \ + pos = list_entry((head)->next, typeof(*pos), member) + static ssize_t -store_fc_private_host_tgtid_bind_type(struct class_device *cdev, - const char *buf, size_t count) +store_fc_private_host_tgtid_bind_type(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); - struct fc_rport *rport, *next_rport; + struct Scsi_Host *shost = transport_class_to_shost(dev); + struct fc_rport *rport; enum fc_tgtid_binding_type val; unsigned long flags; @@ -832,9 +1539,13 @@ store_fc_private_host_tgtid_bind_type(struct class_device *cdev, /* if changing bind type, purge all unused consistent bindings */ if (val != fc_host_tgtid_bind_type(shost)) { spin_lock_irqsave(shost->host_lock, flags); - list_for_each_entry_safe(rport, next_rport, - &fc_host_rport_bindings(shost), peers) - fc_rport_terminate(rport); + while (!list_empty(&fc_host_rport_bindings(shost))) { + get_list_head_entry(rport, + &fc_host_rport_bindings(shost), peers); + list_del(&rport->peers); + rport->port_state = FC_PORTSTATE_DELETED; + fc_queue_work(shost, &rport->rport_delete_work); + } spin_unlock_irqrestore(shost->host_lock, flags); } @@ -842,19 +1553,42 @@ store_fc_private_host_tgtid_bind_type(struct class_device *cdev, return count; } -static FC_CLASS_DEVICE_ATTR(host, tgtid_bind_type, S_IRUGO | S_IWUSR, +static FC_DEVICE_ATTR(host, tgtid_bind_type, S_IRUGO | S_IWUSR, show_fc_private_host_tgtid_bind_type, store_fc_private_host_tgtid_bind_type); +static ssize_t +store_fc_private_host_issue_lip(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct Scsi_Host *shost = transport_class_to_shost(dev); + struct fc_internal *i = to_fc_internal(shost->transportt); + int ret; + + /* ignore any data value written to the attribute */ + if (i->f->issue_fc_host_lip) { + ret = i->f->issue_fc_host_lip(shost); + return ret ? ret: count; + } + + return -ENOENT; +} + +static FC_DEVICE_ATTR(host, issue_lip, S_IWUSR, NULL, + store_fc_private_host_issue_lip); + +fc_private_host_rd_attr(npiv_vports_inuse, "%u\n", 20); + + /* * Host Statistics Management */ /* Show a given an attribute in the statistics group */ static ssize_t -fc_stat_show(const struct class_device *cdev, char *buf, unsigned long offset) +fc_stat_show(const struct device *dev, char *buf, unsigned long offset) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct Scsi_Host *shost = transport_class_to_shost(dev); struct fc_internal *i = to_fc_internal(shost->transportt); struct fc_host_statistics *stats; ssize_t ret = -ENOENT; @@ -875,12 +1609,14 @@ fc_stat_show(const struct class_device *cdev, char *buf, unsigned long offset) /* generate a read-only statistics attribute */ #define fc_host_statistic(name) \ -static ssize_t show_fcstat_##name(struct class_device *cd, char *buf) \ +static ssize_t show_fcstat_##name(struct device *cd, \ + struct device_attribute *attr, \ + char *buf) \ { \ return fc_stat_show(cd, buf, \ offsetof(struct fc_host_statistics, name)); \ } \ -static FC_CLASS_DEVICE_ATTR(host, name, S_IRUGO, show_fcstat_##name, NULL) +static FC_DEVICE_ATTR(host, name, S_IRUGO, show_fcstat_##name, NULL) fc_host_statistic(seconds_since_last_reset); fc_host_statistic(tx_frames); @@ -904,10 +1640,10 @@ fc_host_statistic(fcp_input_megabytes); fc_host_statistic(fcp_output_megabytes); static ssize_t -fc_reset_statistics(struct class_device *cdev, const char *buf, - size_t count) +fc_reset_statistics(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { - struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct Scsi_Host *shost = transport_class_to_shost(dev); struct fc_internal *i = to_fc_internal(shost->transportt); /* ignore any data value written to the attribute */ @@ -918,32 +1654,31 @@ fc_reset_statistics(struct class_device *cdev, const char *buf, return -ENOENT; } -static FC_CLASS_DEVICE_ATTR(host, reset_statistics, S_IWUSR, NULL, +static FC_DEVICE_ATTR(host, reset_statistics, S_IWUSR, NULL, fc_reset_statistics); - static struct attribute *fc_statistics_attrs[] = { - &class_device_attr_host_seconds_since_last_reset.attr, - &class_device_attr_host_tx_frames.attr, - &class_device_attr_host_tx_words.attr, - &class_device_attr_host_rx_frames.attr, - &class_device_attr_host_rx_words.attr, - &class_device_attr_host_lip_count.attr, - &class_device_attr_host_nos_count.attr, - &class_device_attr_host_error_frames.attr, - &class_device_attr_host_dumped_frames.attr, - &class_device_attr_host_link_failure_count.attr, - &class_device_attr_host_loss_of_sync_count.attr, - &class_device_attr_host_loss_of_signal_count.attr, - &class_device_attr_host_prim_seq_protocol_err_count.attr, - &class_device_attr_host_invalid_tx_word_count.attr, - &class_device_attr_host_invalid_crc_count.attr, - &class_device_attr_host_fcp_input_requests.attr, - &class_device_attr_host_fcp_output_requests.attr, - &class_device_attr_host_fcp_control_requests.attr, - &class_device_attr_host_fcp_input_megabytes.attr, - &class_device_attr_host_fcp_output_megabytes.attr, - &class_device_attr_host_reset_statistics.attr, + &device_attr_host_seconds_since_last_reset.attr, + &device_attr_host_tx_frames.attr, + &device_attr_host_tx_words.attr, + &device_attr_host_rx_frames.attr, + &device_attr_host_rx_words.attr, + &device_attr_host_lip_count.attr, + &device_attr_host_nos_count.attr, + &device_attr_host_error_frames.attr, + &device_attr_host_dumped_frames.attr, + &device_attr_host_link_failure_count.attr, + &device_attr_host_loss_of_sync_count.attr, + &device_attr_host_loss_of_signal_count.attr, + &device_attr_host_prim_seq_protocol_err_count.attr, + &device_attr_host_invalid_tx_word_count.attr, + &device_attr_host_invalid_crc_count.attr, + &device_attr_host_fcp_input_requests.attr, + &device_attr_host_fcp_output_requests.attr, + &device_attr_host_fcp_control_requests.attr, + &device_attr_host_fcp_input_megabytes.attr, + &device_attr_host_fcp_output_megabytes.attr, + &device_attr_host_reset_statistics.attr, NULL }; @@ -952,18 +1687,154 @@ static struct attribute_group fc_statistics_group = { .attrs = fc_statistics_attrs, }; -static int fc_host_match(struct attribute_container *cont, - struct device *dev) + +/* Host Vport Attributes */ + +static int +fc_parse_wwn(const char *ns, u64 *nm) { - struct Scsi_Host *shost; - struct fc_internal *i; + unsigned int i, j; + u8 wwn[8]; + + memset(wwn, 0, sizeof(wwn)); + + /* Validate and store the new name */ + for (i=0, j=0; i < 16; i++) { + if ((*ns >= 'a') && (*ns <= 'f')) + j = ((j << 4) | ((*ns++ -'a') + 10)); + else if ((*ns >= 'A') && (*ns <= 'F')) + j = ((j << 4) | ((*ns++ -'A') + 10)); + else if ((*ns >= '0') && (*ns <= '9')) + j = ((j << 4) | (*ns++ -'0')); + else + return -EINVAL; + if (i % 2) { + wwn[i/2] = j & 0xff; + j = 0; + } + } - if (!scsi_is_host_device(dev)) - return 0; + *nm = wwn_to_u64(wwn); - shost = dev_to_shost(dev); - if (!shost->transportt || shost->transportt->host_attrs.ac.class - != &fc_host_class.class) + return 0; +} + + +/* + * "Short-cut" sysfs variable to create a new vport on a FC Host. + * Input is a string of the form ":". Other attributes + * will default to a NPIV-based FCP_Initiator; The WWNs are specified + * as hex characters, and may *not* contain any prefixes (e.g. 0x, x, etc) + */ +static ssize_t +store_fc_host_vport_create(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct Scsi_Host *shost = transport_class_to_shost(dev); + struct fc_vport_identifiers vid; + struct fc_vport *vport; + unsigned int cnt=count; + int stat; + + memset(&vid, 0, sizeof(vid)); + + /* count may include a LF at end of string */ + if (buf[cnt-1] == '\n') + cnt--; + + /* validate we have enough characters for WWPN */ + if ((cnt != (16+1+16)) || (buf[16] != ':')) + return -EINVAL; + + stat = fc_parse_wwn(&buf[0], &vid.port_name); + if (stat) + return stat; + + stat = fc_parse_wwn(&buf[17], &vid.node_name); + if (stat) + return stat; + + vid.roles = FC_PORT_ROLE_FCP_INITIATOR; + vid.vport_type = FC_PORTTYPE_NPIV; + /* vid.symbolic_name is already zero/NULL's */ + vid.disable = false; /* always enabled */ + + /* we only allow support on Channel 0 !!! */ + stat = fc_vport_create(shost, 0, &shost->shost_gendev, &vid, &vport); + return stat ? stat : count; +} +static FC_DEVICE_ATTR(host, vport_create, S_IWUSR, NULL, + store_fc_host_vport_create); + + +/* + * "Short-cut" sysfs variable to delete a vport on a FC Host. + * Vport is identified by a string containing ":". + * The WWNs are specified as hex characters, and may *not* contain + * any prefixes (e.g. 0x, x, etc) + */ +static ssize_t +store_fc_host_vport_delete(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct Scsi_Host *shost = transport_class_to_shost(dev); + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); + struct fc_vport *vport; + u64 wwpn, wwnn; + unsigned long flags; + unsigned int cnt=count; + int stat, match; + + /* count may include a LF at end of string */ + if (buf[cnt-1] == '\n') + cnt--; + + /* validate we have enough characters for WWPN */ + if ((cnt != (16+1+16)) || (buf[16] != ':')) + return -EINVAL; + + stat = fc_parse_wwn(&buf[0], &wwpn); + if (stat) + return stat; + + stat = fc_parse_wwn(&buf[17], &wwnn); + if (stat) + return stat; + + spin_lock_irqsave(shost->host_lock, flags); + match = 0; + /* we only allow support on Channel 0 !!! */ + list_for_each_entry(vport, &fc_host->vports, peers) { + if ((vport->channel == 0) && + (vport->port_name == wwpn) && (vport->node_name == wwnn)) { + match = 1; + break; + } + } + spin_unlock_irqrestore(shost->host_lock, flags); + + if (!match) + return -ENODEV; + + stat = fc_vport_terminate(vport); + return stat ? stat : count; +} +static FC_DEVICE_ATTR(host, vport_delete, S_IWUSR, NULL, + store_fc_host_vport_delete); + + +static int fc_host_match(struct attribute_container *cont, + struct device *dev) +{ + struct Scsi_Host *shost; + struct fc_internal *i; + + if (!scsi_is_host_device(dev)) + return 0; + + shost = dev_to_shost(dev); + if (!shost->transportt || shost->transportt->host_attrs.ac.class + != &fc_host_class.class) return 0; i = to_fc_internal(shost->transportt); @@ -1022,18 +1893,168 @@ static int fc_rport_match(struct attribute_container *cont, return &i->rport_attr_cont.ac == cont; } + +static void fc_vport_dev_release(struct device *dev) +{ + struct fc_vport *vport = dev_to_vport(dev); + put_device(dev->parent); /* release kobj parent */ + kfree(vport); +} + +int scsi_is_fc_vport(const struct device *dev) +{ + return dev->release == fc_vport_dev_release; +} +EXPORT_SYMBOL(scsi_is_fc_vport); + +static int fc_vport_match(struct attribute_container *cont, + struct device *dev) +{ + struct fc_vport *vport; + struct Scsi_Host *shost; + struct fc_internal *i; + + if (!scsi_is_fc_vport(dev)) + return 0; + vport = dev_to_vport(dev); + + shost = vport_to_shost(vport); + if (!shost->transportt || shost->transportt->host_attrs.ac.class + != &fc_host_class.class) + return 0; + + i = to_fc_internal(shost->transportt); + return &i->vport_attr_cont.ac == cont; +} + + +/** + * fc_timed_out - FC Transport I/O timeout intercept handler + * @scmd: The SCSI command which timed out + * + * This routine protects against error handlers getting invoked while a + * rport is in a blocked state, typically due to a temporarily loss of + * connectivity. If the error handlers are allowed to proceed, requests + * to abort i/o, reset the target, etc will likely fail as there is no way + * to communicate with the device to perform the requested function. These + * failures may result in the midlayer taking the device offline, requiring + * manual intervention to restore operation. + * + * This routine, called whenever an i/o times out, validates the state of + * the underlying rport. If the rport is blocked, it returns + * EH_RESET_TIMER, which will continue to reschedule the timeout. + * Eventually, either the device will return, or devloss_tmo will fire, + * and when the timeout then fires, it will be handled normally. + * If the rport is not blocked, normal error handling continues. + * + * Notes: + * This routine assumes no locks are held on entry. + */ +static enum blk_eh_timer_return +fc_timed_out(struct scsi_cmnd *scmd) +{ + struct fc_rport *rport = starget_to_rport(scsi_target(scmd->device)); + + if (rport->port_state == FC_PORTSTATE_BLOCKED) + return BLK_EH_RESET_TIMER; + + return BLK_EH_NOT_HANDLED; +} + +/* + * Called by fc_user_scan to locate an rport on the shost that + * matches the channel and target id, and invoke scsi_scan_target() + * on the rport. + */ +static void +fc_user_scan_tgt(struct Scsi_Host *shost, uint channel, uint id, uint lun) +{ + struct fc_rport *rport; + unsigned long flags; + + spin_lock_irqsave(shost->host_lock, flags); + + list_for_each_entry(rport, &fc_host_rports(shost), peers) { + if (rport->scsi_target_id == -1) + continue; + + if (rport->port_state != FC_PORTSTATE_ONLINE) + continue; + + if ((channel == rport->channel) && + (id == rport->scsi_target_id)) { + spin_unlock_irqrestore(shost->host_lock, flags); + scsi_scan_target(&rport->dev, channel, id, lun, 1); + return; + } + } + + spin_unlock_irqrestore(shost->host_lock, flags); +} + +/* + * Called via sysfs scan routines. Necessary, as the FC transport + * wants to place all target objects below the rport object. So this + * routine must invoke the scsi_scan_target() routine with the rport + * object as the parent. + */ +static int +fc_user_scan(struct Scsi_Host *shost, uint channel, uint id, uint lun) +{ + uint chlo, chhi; + uint tgtlo, tgthi; + + if (((channel != SCAN_WILD_CARD) && (channel > shost->max_channel)) || + ((id != SCAN_WILD_CARD) && (id >= shost->max_id)) || + ((lun != SCAN_WILD_CARD) && (lun > shost->max_lun))) + return -EINVAL; + + if (channel == SCAN_WILD_CARD) { + chlo = 0; + chhi = shost->max_channel + 1; + } else { + chlo = channel; + chhi = channel + 1; + } + + if (id == SCAN_WILD_CARD) { + tgtlo = 0; + tgthi = shost->max_id; + } else { + tgtlo = id; + tgthi = id + 1; + } + + for ( ; chlo < chhi; chlo++) + for ( ; tgtlo < tgthi; tgtlo++) + fc_user_scan_tgt(shost, chlo, tgtlo, lun); + + return 0; +} + +static int fc_tsk_mgmt_response(struct Scsi_Host *shost, u64 nexus, u64 tm_id, + int result) +{ + struct fc_internal *i = to_fc_internal(shost->transportt); + return i->f->tsk_mgmt_response(shost, nexus, tm_id, result); +} + +static int fc_it_nexus_response(struct Scsi_Host *shost, u64 nexus, int result) +{ + struct fc_internal *i = to_fc_internal(shost->transportt); + return i->f->it_nexus_response(shost, nexus, result); +} + struct scsi_transport_template * fc_attach_transport(struct fc_function_template *ft) { - struct fc_internal *i = kmalloc(sizeof(struct fc_internal), - GFP_KERNEL); int count; + struct fc_internal *i = kzalloc(sizeof(struct fc_internal), + GFP_KERNEL); if (unlikely(!i)) return NULL; - memset(i, 0, sizeof(struct fc_internal)); - i->t.target_attrs.ac.attrs = &i->starget_attrs[0]; i->t.target_attrs.ac.class = &fc_transport_class.class; i->t.target_attrs.ac.match = fc_target_match; @@ -1053,11 +2074,24 @@ fc_attach_transport(struct fc_function_template *ft) i->rport_attr_cont.ac.match = fc_rport_match; transport_container_register(&i->rport_attr_cont); + i->vport_attr_cont.ac.attrs = &i->vport_attrs[0]; + i->vport_attr_cont.ac.class = &fc_vport_class.class; + i->vport_attr_cont.ac.match = fc_vport_match; + transport_container_register(&i->vport_attr_cont); + i->f = ft; /* Transport uses the shost workq for scsi scanning */ i->t.create_work_queue = 1; - + + i->t.eh_timed_out = fc_timed_out; + + i->t.user_scan = fc_user_scan; + + /* target-mode drivers' functions */ + i->t.tsk_mgmt_response = fc_tsk_mgmt_response; + i->t.it_nexus_response = fc_it_nexus_response; + /* * Setup SCSI Target Attributes. */ @@ -1077,11 +2111,15 @@ fc_attach_transport(struct fc_function_template *ft) count=0; SETUP_HOST_ATTRIBUTE_RD(node_name); SETUP_HOST_ATTRIBUTE_RD(port_name); + SETUP_HOST_ATTRIBUTE_RD(permanent_port_name); SETUP_HOST_ATTRIBUTE_RD(supported_classes); SETUP_HOST_ATTRIBUTE_RD(supported_fc4s); - SETUP_HOST_ATTRIBUTE_RD(symbolic_name); SETUP_HOST_ATTRIBUTE_RD(supported_speeds); SETUP_HOST_ATTRIBUTE_RD(maxframe_size); + if (ft->vport_create) { + SETUP_HOST_ATTRIBUTE_RD_NS(max_npiv_vports); + SETUP_HOST_ATTRIBUTE_RD_NS(npiv_vports_inuse); + } SETUP_HOST_ATTRIBUTE_RD(serial_number); SETUP_HOST_ATTRIBUTE_RD(port_id); @@ -1090,9 +2128,17 @@ fc_attach_transport(struct fc_function_template *ft) SETUP_HOST_ATTRIBUTE_RD(active_fc4s); SETUP_HOST_ATTRIBUTE_RD(speed); SETUP_HOST_ATTRIBUTE_RD(fabric_name); + SETUP_HOST_ATTRIBUTE_RD(symbolic_name); + SETUP_HOST_ATTRIBUTE_RW(system_hostname); /* Transport-managed attributes */ SETUP_PRIVATE_HOST_ATTRIBUTE_RW(tgtid_bind_type); + if (ft->issue_fc_host_lip) + SETUP_PRIVATE_HOST_ATTRIBUTE_RW(issue_lip); + if (ft->vport_create) + SETUP_PRIVATE_HOST_ATTRIBUTE_RW(vport_create); + if (ft->vport_delete) + SETUP_PRIVATE_HOST_ATTRIBUTE_RW(vport_delete); BUG_ON(count > FC_HOST_NUM_ATTRS); @@ -1111,11 +2157,31 @@ fc_attach_transport(struct fc_function_template *ft) SETUP_PRIVATE_RPORT_ATTRIBUTE_RD(roles); SETUP_PRIVATE_RPORT_ATTRIBUTE_RD(port_state); SETUP_PRIVATE_RPORT_ATTRIBUTE_RD(scsi_target_id); + if (ft->terminate_rport_io) + SETUP_PRIVATE_RPORT_ATTRIBUTE_RW(fast_io_fail_tmo); BUG_ON(count > FC_RPORT_NUM_ATTRS); i->rport_attrs[count] = NULL; + /* + * Setup Virtual Port Attributes. + */ + count=0; + SETUP_PRIVATE_VPORT_ATTRIBUTE_RD(vport_state); + SETUP_PRIVATE_VPORT_ATTRIBUTE_RD(vport_last_state); + SETUP_PRIVATE_VPORT_ATTRIBUTE_RD(node_name); + SETUP_PRIVATE_VPORT_ATTRIBUTE_RD(port_name); + SETUP_PRIVATE_VPORT_ATTRIBUTE_RD(roles); + SETUP_PRIVATE_VPORT_ATTRIBUTE_RD(vport_type); + SETUP_VPORT_ATTRIBUTE_RW(symbolic_name); + SETUP_VPORT_ATTRIBUTE_WR(vport_delete); + SETUP_VPORT_ATTRIBUTE_WR(vport_disable); + + BUG_ON(count > FC_VPORT_NUM_ATTRS); + + i->vport_attrs[count] = NULL; + return &i->t; } EXPORT_SYMBOL(fc_attach_transport); @@ -1127,43 +2193,249 @@ void fc_release_transport(struct scsi_transport_template *t) transport_container_unregister(&i->t.target_attrs); transport_container_unregister(&i->t.host_attrs); transport_container_unregister(&i->rport_attr_cont); + transport_container_unregister(&i->vport_attr_cont); kfree(i); } EXPORT_SYMBOL(fc_release_transport); +/** + * fc_queue_work - Queue work to the fc_host workqueue. + * @shost: Pointer to Scsi_Host bound to fc_host. + * @work: Work to queue for execution. + * + * Return value: + * 1 - work queued for execution + * 0 - work is already queued + * -EINVAL - work queue doesn't exist + */ +static int +fc_queue_work(struct Scsi_Host *shost, struct work_struct *work) +{ + if (unlikely(!fc_host_work_q(shost))) { + printk(KERN_ERR + "ERROR: FC host '%s' attempted to queue work, " + "when no workqueue created.\n", shost->hostt->name); + dump_stack(); + + return -EINVAL; + } + + return queue_work(fc_host_work_q(shost), work); +} + +/** + * fc_flush_work - Flush a fc_host's workqueue. + * @shost: Pointer to Scsi_Host bound to fc_host. + */ +static void +fc_flush_work(struct Scsi_Host *shost) +{ + if (!fc_host_work_q(shost)) { + printk(KERN_ERR + "ERROR: FC host '%s' attempted to flush work, " + "when no workqueue created.\n", shost->hostt->name); + dump_stack(); + return; + } + + flush_workqueue(fc_host_work_q(shost)); +} + +/** + * fc_queue_devloss_work - Schedule work for the fc_host devloss workqueue. + * @shost: Pointer to Scsi_Host bound to fc_host. + * @work: Work to queue for execution. + * @delay: jiffies to delay the work queuing + * + * Return value: + * 1 on success / 0 already queued / < 0 for error + */ +static int +fc_queue_devloss_work(struct Scsi_Host *shost, struct delayed_work *work, + unsigned long delay) +{ + if (unlikely(!fc_host_devloss_work_q(shost))) { + printk(KERN_ERR + "ERROR: FC host '%s' attempted to queue work, " + "when no workqueue created.\n", shost->hostt->name); + dump_stack(); + + return -EINVAL; + } + + return queue_delayed_work(fc_host_devloss_work_q(shost), work, delay); +} + +/** + * fc_flush_devloss - Flush a fc_host's devloss workqueue. + * @shost: Pointer to Scsi_Host bound to fc_host. + */ +static void +fc_flush_devloss(struct Scsi_Host *shost) +{ + if (!fc_host_devloss_work_q(shost)) { + printk(KERN_ERR + "ERROR: FC host '%s' attempted to flush work, " + "when no workqueue created.\n", shost->hostt->name); + dump_stack(); + return; + } + + flush_workqueue(fc_host_devloss_work_q(shost)); +} + /** - * fc_remove_host - called to terminate any fc_transport-related elements - * for a scsi host. - * @rport: remote port to be unblocked. + * fc_remove_host - called to terminate any fc_transport-related elements for a scsi host. + * @shost: Which &Scsi_Host * * This routine is expected to be called immediately preceeding the * a driver's call to scsi_remove_host(). * * WARNING: A driver utilizing the fc_transport, which fails to call - * this routine prior to scsi_remote_host(), will leave dangling + * this routine prior to scsi_remove_host(), will leave dangling * objects in /sys/class/fc_remote_ports. Access to any of these * objects can result in a system crash !!! * * Notes: * This routine assumes no locks are held on entry. - **/ + */ void fc_remove_host(struct Scsi_Host *shost) { - struct fc_rport *rport, *next_rport; + struct fc_vport *vport = NULL, *next_vport = NULL; + struct fc_rport *rport = NULL, *next_rport = NULL; + struct workqueue_struct *work_q; + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); + unsigned long flags; + + spin_lock_irqsave(shost->host_lock, flags); + + /* Remove any vports */ + list_for_each_entry_safe(vport, next_vport, &fc_host->vports, peers) + fc_queue_work(shost, &vport->vport_delete_work); /* Remove any remote ports */ list_for_each_entry_safe(rport, next_rport, - &fc_host_rports(shost), peers) - fc_rport_terminate(rport); + &fc_host->rports, peers) { + list_del(&rport->peers); + rport->port_state = FC_PORTSTATE_DELETED; + fc_queue_work(shost, &rport->rport_delete_work); + } + list_for_each_entry_safe(rport, next_rport, - &fc_host_rport_bindings(shost), peers) - fc_rport_terminate(rport); + &fc_host->rport_bindings, peers) { + list_del(&rport->peers); + rport->port_state = FC_PORTSTATE_DELETED; + fc_queue_work(shost, &rport->rport_delete_work); + } + + spin_unlock_irqrestore(shost->host_lock, flags); + + /* flush all scan work items */ + scsi_flush_work(shost); + + /* flush all stgt delete, and rport delete work items, then kill it */ + if (fc_host->work_q) { + work_q = fc_host->work_q; + fc_host->work_q = NULL; + destroy_workqueue(work_q); + } + + /* flush all devloss work items, then kill it */ + if (fc_host->devloss_work_q) { + work_q = fc_host->devloss_work_q; + fc_host->devloss_work_q = NULL; + destroy_workqueue(work_q); + } } EXPORT_SYMBOL(fc_remove_host); + +/** + * fc_starget_delete - called to delete the scsi decendents of an rport + * @work: remote port to be operated on. + * + * Deletes target and all sdevs. + */ +static void +fc_starget_delete(struct work_struct *work) +{ + struct fc_rport *rport = + container_of(work, struct fc_rport, stgt_delete_work); + struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_internal *i = to_fc_internal(shost->transportt); + + /* Involve the LLDD if possible to terminate all io on the rport. */ + if (i->f->terminate_rport_io) + i->f->terminate_rport_io(rport); + + scsi_remove_target(&rport->dev); +} + + +/** + * fc_rport_final_delete - finish rport termination and delete it. + * @work: remote port to be deleted. + */ +static void +fc_rport_final_delete(struct work_struct *work) +{ + struct fc_rport *rport = + container_of(work, struct fc_rport, rport_delete_work); + struct device *dev = &rport->dev; + struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_internal *i = to_fc_internal(shost->transportt); + unsigned long flags; + + /* + * if a scan is pending, flush the SCSI Host work_q so that + * that we can reclaim the rport scan work element. + */ + if (rport->flags & FC_RPORT_SCAN_PENDING) + scsi_flush_work(shost); + + /* involve the LLDD to terminate all pending i/o */ + if (i->f->terminate_rport_io) + i->f->terminate_rport_io(rport); + + /* + * Cancel any outstanding timers. These should really exist + * only when rmmod'ing the LLDD and we're asking for + * immediate termination of the rports + */ + spin_lock_irqsave(shost->host_lock, flags); + if (rport->flags & FC_RPORT_DEVLOSS_PENDING) { + spin_unlock_irqrestore(shost->host_lock, flags); + if (!cancel_delayed_work(&rport->fail_io_work)) + fc_flush_devloss(shost); + if (!cancel_delayed_work(&rport->dev_loss_work)) + fc_flush_devloss(shost); + spin_lock_irqsave(shost->host_lock, flags); + rport->flags &= ~FC_RPORT_DEVLOSS_PENDING; + } + spin_unlock_irqrestore(shost->host_lock, flags); + + /* Delete SCSI target and sdevs */ + if (rport->scsi_target_id != -1) + fc_starget_delete(&rport->stgt_delete_work); + + /* + * Notify the driver that the rport is now dead. The LLDD will + * also guarantee that any communication to the rport is terminated + */ + if (i->f->dev_loss_tmo_callbk) + i->f->dev_loss_tmo_callbk(rport); + + transport_remove_device(dev); + device_del(dev); + transport_destroy_device(dev); + put_device(&shost->shost_gendev); /* for fc_host->rport list */ + put_device(dev); /* for self-reference */ +} + + /** * fc_rport_create - allocates and creates a remote FC port. * @shost: scsi host the remote port is connected to. @@ -1176,13 +2448,12 @@ EXPORT_SYMBOL(fc_remove_host); * * Notes: * This routine assumes no locks are held on entry. - **/ -struct fc_rport * + */ +static struct fc_rport * fc_rport_create(struct Scsi_Host *shost, int channel, struct fc_rport_identifiers *ids) { - struct fc_host_attrs *fc_host = - (struct fc_host_attrs *)shost->shost_data; + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); struct fc_internal *fci = to_fc_internal(shost->transportt); struct fc_rport *rport; struct device *dev; @@ -1191,12 +2462,11 @@ fc_rport_create(struct Scsi_Host *shost, int channel, size_t size; size = (sizeof(struct fc_rport) + fci->f->dd_fcrport_size); - rport = kmalloc(size, GFP_KERNEL); + rport = kzalloc(size, GFP_KERNEL); if (unlikely(!rport)) { - printk(KERN_ERR "%s: allocation failure\n", __FUNCTION__); + printk(KERN_ERR "%s: allocation failure\n", __func__); return NULL; } - memset(rport, 0, size); rport->maxframe_size = -1; rport->supported_classes = FC_COS_UNSPECIFIED; @@ -1209,25 +2479,29 @@ fc_rport_create(struct Scsi_Host *shost, int channel, if (fci->f->dd_fcrport_size) rport->dd_data = &rport[1]; rport->channel = channel; + rport->fast_io_fail_tmo = -1; - INIT_WORK(&rport->dev_loss_work, fc_timeout_blocked_rport, rport); - INIT_WORK(&rport->scan_work, fc_scsi_scan_rport, rport); + INIT_DELAYED_WORK(&rport->dev_loss_work, fc_timeout_deleted_rport); + INIT_DELAYED_WORK(&rport->fail_io_work, fc_timeout_fail_rport_io); + INIT_WORK(&rport->scan_work, fc_scsi_scan_rport); + INIT_WORK(&rport->stgt_delete_work, fc_starget_delete); + INIT_WORK(&rport->rport_delete_work, fc_rport_final_delete); spin_lock_irqsave(shost->host_lock, flags); rport->number = fc_host->next_rport_number++; - if (rport->roles & FC_RPORT_ROLE_FCP_TARGET) + if (rport->roles & FC_PORT_ROLE_FCP_TARGET) rport->scsi_target_id = fc_host->next_target_id++; else rport->scsi_target_id = -1; - list_add_tail(&rport->peers, &fc_host_rports(shost)); - get_device(&shost->shost_gendev); + list_add_tail(&rport->peers, &fc_host->rports); + get_device(&shost->shost_gendev); /* for fc_host->rport list */ spin_unlock_irqrestore(shost->host_lock, flags); dev = &rport->dev; - device_initialize(dev); - dev->parent = get_device(&shost->shost_gendev); + device_initialize(dev); /* takes self reference */ + dev->parent = get_device(&shost->shost_gendev); /* parent reference */ dev->release = fc_rport_dev_release; sprintf(dev->bus_id, "rport-%d:%d-%d", shost->host_no, channel, rport->number); @@ -1241,18 +2515,19 @@ fc_rport_create(struct Scsi_Host *shost, int channel, transport_add_device(dev); transport_configure_device(dev); - if (rport->roles & FC_RPORT_ROLE_FCP_TARGET) + if (rport->roles & FC_PORT_ROLE_FCP_TARGET) { /* initiate a scan of the target */ + rport->flags |= FC_RPORT_SCAN_PENDING; scsi_queue_work(shost, &rport->scan_work); + } return rport; delete_rport: transport_destroy_device(dev); - put_device(dev->parent); spin_lock_irqsave(shost->host_lock, flags); list_del(&rport->peers); - put_device(&shost->shost_gendev); + put_device(&shost->shost_gendev); /* for fc_host->rport list */ spin_unlock_irqrestore(shost->host_lock, flags); put_device(dev->parent); kfree(rport); @@ -1260,8 +2535,7 @@ delete_rport: } /** - * fc_remote_port_add - notifies the fc transport of the existence - * of a remote FC port. + * fc_remote_port_add - notify fc transport of the existence of a remote FC port. * @shost: scsi host the remote port is connected to. * @channel: Channel on shost port connected to. * @ids: The world wide names, fc address, and FC4 port @@ -1297,28 +2571,131 @@ delete_rport: * * Notes: * This routine assumes no locks are held on entry. - **/ + */ struct fc_rport * fc_remote_port_add(struct Scsi_Host *shost, int channel, struct fc_rport_identifiers *ids) { + struct fc_internal *fci = to_fc_internal(shost->transportt); + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); struct fc_rport *rport; unsigned long flags; int match = 0; - if (likely((ids->roles & FC_RPORT_ROLE_FCP_TARGET) && - (fc_host_tgtid_bind_type(shost) != FC_TGTID_BIND_NONE))) { + /* ensure any stgt delete functions are done */ + fc_flush_work(shost); - /* search for a matching consistent binding */ + /* + * Search the list of "active" rports, for an rport that has been + * deleted, but we've held off the real delete while the target + * is in a "blocked" state. + */ + spin_lock_irqsave(shost->host_lock, flags); - spin_lock_irqsave(shost->host_lock, flags); + list_for_each_entry(rport, &fc_host->rports, peers) { - list_for_each_entry(rport, &fc_host_rport_bindings(shost), + if ((rport->port_state == FC_PORTSTATE_BLOCKED) && + (rport->channel == channel)) { + + switch (fc_host->tgtid_bind_type) { + case FC_TGTID_BIND_BY_WWPN: + case FC_TGTID_BIND_NONE: + if (rport->port_name == ids->port_name) + match = 1; + break; + case FC_TGTID_BIND_BY_WWNN: + if (rport->node_name == ids->node_name) + match = 1; + break; + case FC_TGTID_BIND_BY_ID: + if (rport->port_id == ids->port_id) + match = 1; + break; + } + + if (match) { + + memcpy(&rport->node_name, &ids->node_name, + sizeof(rport->node_name)); + memcpy(&rport->port_name, &ids->port_name, + sizeof(rport->port_name)); + rport->port_id = ids->port_id; + + rport->port_state = FC_PORTSTATE_ONLINE; + rport->roles = ids->roles; + + spin_unlock_irqrestore(shost->host_lock, flags); + + if (fci->f->dd_fcrport_size) + memset(rport->dd_data, 0, + fci->f->dd_fcrport_size); + + /* + * If we were not a target, cancel the + * io terminate and rport timers, and + * we're done. + * + * If we were a target, but our new role + * doesn't indicate a target, leave the + * timers running expecting the role to + * change as the target fully logs in. If + * it doesn't, the target will be torn down. + * + * If we were a target, and our role shows + * we're still a target, cancel the timers + * and kick off a scan. + */ + + /* was a target, not in roles */ + if ((rport->scsi_target_id != -1) && + (!(ids->roles & FC_PORT_ROLE_FCP_TARGET))) + return rport; + + /* + * Stop the fail io and dev_loss timers. + * If they flush, the port_state will + * be checked and will NOOP the function. + */ + if (!cancel_delayed_work(&rport->fail_io_work)) + fc_flush_devloss(shost); + if (!cancel_delayed_work(&rport->dev_loss_work)) + fc_flush_devloss(shost); + + spin_lock_irqsave(shost->host_lock, flags); + + rport->flags &= ~FC_RPORT_DEVLOSS_PENDING; + + /* if target, initiate a scan */ + if (rport->scsi_target_id != -1) { + rport->flags |= FC_RPORT_SCAN_PENDING; + scsi_queue_work(shost, + &rport->scan_work); + spin_unlock_irqrestore(shost->host_lock, + flags); + scsi_target_unblock(&rport->dev); + } else + spin_unlock_irqrestore(shost->host_lock, + flags); + + return rport; + } + } + } + + /* + * Search the bindings array + * Note: if never a FCP target, you won't be on this list + */ + if (fc_host->tgtid_bind_type != FC_TGTID_BIND_NONE) { + + /* search for a matching consistent binding */ + + list_for_each_entry(rport, &fc_host->rport_bindings, peers) { if (rport->channel != channel) continue; - switch (fc_host_tgtid_bind_type(shost)) { + switch (fc_host->tgtid_bind_type) { case FC_TGTID_BIND_BY_WWPN: if (rport->port_name == ids->port_name) match = 1; @@ -1336,14 +2713,11 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel, } if (match) { - list_move_tail(&rport->peers, - &fc_host_rports(shost)); + list_move_tail(&rport->peers, &fc_host->rports); break; } } - spin_unlock_irqrestore(shost->host_lock, flags); - if (match) { memcpy(&rport->node_name, &ids->node_name, sizeof(rport->node_name)); @@ -1353,14 +2727,25 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel, rport->roles = ids->roles; rport->port_state = FC_PORTSTATE_ONLINE; - if (rport->roles & FC_RPORT_ROLE_FCP_TARGET) + if (fci->f->dd_fcrport_size) + memset(rport->dd_data, 0, + fci->f->dd_fcrport_size); + + if (rport->roles & FC_PORT_ROLE_FCP_TARGET) { /* initiate a scan of the target */ + rport->flags |= FC_RPORT_SCAN_PENDING; scsi_queue_work(shost, &rport->scan_work); + spin_unlock_irqrestore(shost->host_lock, flags); + scsi_target_unblock(&rport->dev); + } else + spin_unlock_irqrestore(shost->host_lock, flags); return rport; } } + spin_unlock_irqrestore(shost->host_lock, flags); + /* No consistent binding found - create new remote port entry */ rport = fc_rport_create(shost, channel, ids); @@ -1368,140 +2753,123 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel, } EXPORT_SYMBOL(fc_remote_port_add); -/* - * fc_rport_tgt_remove - Removes the scsi target on the remote port - * @rport: The remote port to be operated on - */ -static void -fc_rport_tgt_remove(struct fc_rport *rport) -{ - struct Scsi_Host *shost = rport_to_shost(rport); - - scsi_target_unblock(&rport->dev); - - /* Stop anything on the workq */ - if (!cancel_delayed_work(&rport->dev_loss_work)) - flush_scheduled_work(); - scsi_flush_work(shost); - - scsi_remove_target(&rport->dev); -} - -/* - * fc_rport_terminate - this routine tears down and deallocates a remote port. - * @rport: The remote port to be terminated - * - * Notes: - * This routine assumes no locks are held on entry. - */ -static void -fc_rport_terminate(struct fc_rport *rport) -{ - struct Scsi_Host *shost = rport_to_shost(rport); - struct device *dev = &rport->dev; - unsigned long flags; - - fc_rport_tgt_remove(rport); - - transport_remove_device(dev); - device_del(dev); - transport_destroy_device(dev); - spin_lock_irqsave(shost->host_lock, flags); - list_del(&rport->peers); - spin_unlock_irqrestore(shost->host_lock, flags); - put_device(&shost->shost_gendev); -} /** - * fc_remote_port_delete - notifies the fc transport that a remote - * port is no longer in existence. + * fc_remote_port_delete - notifies the fc transport that a remote port is no longer in existence. * @rport: The remote port that no longer exists * * The LLDD calls this routine to notify the transport that a remote * port is no longer part of the topology. Note: Although a port * may no longer be part of the topology, it may persist in the remote - * ports displayed by the fc_host. This is done so that target id - * mappings (managed via the remote port structures), are always visible - * as long as the mapping is valid, regardless of port state, + * ports displayed by the fc_host. We do this under 2 conditions: + * 1) If the port was a scsi target, we delay its deletion by "blocking" it. + * This allows the port to temporarily disappear, then reappear without + * disrupting the SCSI device tree attached to it. During the "blocked" + * period the port will still exist. + * 2) If the port was a scsi target and disappears for longer than we + * expect, we'll delete the port and the tear down the SCSI device tree + * attached to it. However, we want to semi-persist the target id assigned + * to that port if it eventually does exist. The port structure will + * remain (although with minimal information) so that the target id + * bindings remails. * * If the remote port is not an FCP Target, it will be fully torn down * and deallocated, including the fc_remote_port class device. * - * If the remote port is an FCP Target, the port structure will be - * marked as Not Present, but will remain as long as there is a valid - * SCSI target id mapping associated with the port structure. Validity - * is determined by the binding type. If binding by wwpn, then the port - * structure is always valid and will not be deallocated until the host - * is removed. If binding by wwnn, then the port structure is valid - * until another port with the same node name is found in the topology. - * If binding by port id (fc address), then the port structure is valid - * valid until another port with the same address is identified. + * If the remote port is an FCP Target, the port will be placed in a + * temporary blocked state. From the LLDD's perspective, the rport no + * longer exists. From the SCSI midlayer's perspective, the SCSI target + * exists, but all sdevs on it are blocked from further I/O. The following + * is then expected. + * + * If the remote port does not return (signaled by a LLDD call to + * fc_remote_port_add()) within the dev_loss_tmo timeout, then the + * scsi target is removed - killing all outstanding i/o and removing the + * scsi devices attached ot it. The port structure will be marked Not + * Present and be partially cleared, leaving only enough information to + * recognize the remote port relative to the scsi target id binding if + * it later appears. The port will remain as long as there is a valid + * binding (e.g. until the user changes the binding type or unloads the + * scsi host with the binding). + * + * If the remote port returns within the dev_loss_tmo value (and matches + * according to the target id binding type), the port structure will be + * reused. If it is no longer a SCSI target, the target will be torn + * down. If it continues to be a SCSI target, then the target will be + * unblocked (allowing i/o to be resumed), and a scan will be activated + * to ensure that all luns are detected. * - * Called from interrupt or normal process context. + * Called from normal process context only - cannot be called from interrupt. * * Notes: * This routine assumes no locks are held on entry. - **/ + */ void fc_remote_port_delete(struct fc_rport *rport) { struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_internal *i = to_fc_internal(shost->transportt); + int timeout = rport->dev_loss_tmo; unsigned long flags; - /* If no scsi target id mapping or consistent binding type, delete it */ - if ((rport->scsi_target_id == -1) || - (fc_host_tgtid_bind_type(shost) == FC_TGTID_BIND_NONE)) { - fc_rport_terminate(rport); + /* + * No need to flush the fc_host work_q's, as all adds are synchronous. + * + * We do need to reclaim the rport scan work element, so eventually + * (in fc_rport_final_delete()) we'll flush the scsi host work_q if + * there's still a scan pending. + */ + + spin_lock_irqsave(shost->host_lock, flags); + + if (rport->port_state != FC_PORTSTATE_ONLINE) { + spin_unlock_irqrestore(shost->host_lock, flags); return; } - fc_rport_tgt_remove(rport); + /* + * In the past, we if this was not an FCP-Target, we would + * unconditionally just jump to deleting the rport. + * However, rports can be used as node containers by the LLDD, + * and its not appropriate to just terminate the rport at the + * first sign of a loss in connectivity. The LLDD may want to + * send ELS traffic to re-validate the login. If the rport is + * immediately deleted, it makes it inappropriate for a node + * container. + * So... we now unconditionally wait dev_loss_tmo before + * destroying an rport. + */ + + rport->port_state = FC_PORTSTATE_BLOCKED; + + rport->flags |= FC_RPORT_DEVLOSS_PENDING; - spin_lock_irqsave(shost->host_lock, flags); - list_move_tail(&rport->peers, &fc_host_rport_bindings(shost)); spin_unlock_irqrestore(shost->host_lock, flags); - /* - * Note: We do not remove or clear the hostdata area. This allows - * host-specific target data to persist along with the - * scsi_target_id. It's up to the host to manage it's hostdata area. - */ + if (rport->roles & FC_PORT_ROLE_FCP_INITIATOR && + shost->active_mode & MODE_TARGET) + fc_tgt_it_nexus_destroy(shost, (unsigned long)rport); - /* - * Reinitialize port attributes that may change if the port comes back. - */ - rport->maxframe_size = -1; - rport->supported_classes = FC_COS_UNSPECIFIED; - rport->roles = FC_RPORT_ROLE_UNKNOWN; - rport->port_state = FC_PORTSTATE_NOTPRESENT; + scsi_target_block(&rport->dev); - /* remove the identifiers that aren't used in the consisting binding */ - switch (fc_host_tgtid_bind_type(shost)) { - case FC_TGTID_BIND_BY_WWPN: - rport->node_name = -1; - rport->port_id = -1; - break; - case FC_TGTID_BIND_BY_WWNN: - rport->port_name = -1; - rport->port_id = -1; - break; - case FC_TGTID_BIND_BY_ID: - rport->node_name = -1; - rport->port_name = -1; - break; - case FC_TGTID_BIND_NONE: /* to keep compiler happy */ - break; - } + /* see if we need to kill io faster than waiting for device loss */ + if ((rport->fast_io_fail_tmo != -1) && + (rport->fast_io_fail_tmo < timeout) && (i->f->terminate_rport_io)) + fc_queue_devloss_work(shost, &rport->fail_io_work, + rport->fast_io_fail_tmo * HZ); + + /* cap the length the devices can be blocked until they are deleted */ + fc_queue_devloss_work(shost, &rport->dev_loss_work, timeout * HZ); } EXPORT_SYMBOL(fc_remote_port_delete); /** - * fc_remote_port_rolechg - notifies the fc transport that the roles - * on a remote may have changed. + * fc_remote_port_rolechg - notifies the fc transport that the roles on a remote may have changed. * @rport: The remote port that changed. + * @roles: New roles for this port. * - * The LLDD calls this routine to notify the transport that the roles - * on a remote port may have changed. The largest effect of this is + * Description: The LLDD calls this routine to notify the transport that the + * roles on a remote port may have changed. The largest effect of this is * if a port now becomes a FCP Target, it must be allocated a * scsi target id. If the port is no longer a FCP target, any * scsi target id value assigned to it will persist in case the @@ -1514,150 +2882,446 @@ EXPORT_SYMBOL(fc_remote_port_delete); * * Notes: * This routine assumes no locks are held on entry. - **/ + */ void fc_remote_port_rolechg(struct fc_rport *rport, u32 roles) { struct Scsi_Host *shost = rport_to_shost(rport); - struct fc_host_attrs *fc_host = - (struct fc_host_attrs *)shost->shost_data; + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); unsigned long flags; int create = 0; - - rport->roles = roles; + int ret; spin_lock_irqsave(shost->host_lock, flags); - if ((rport->scsi_target_id == -1) && - (rport->roles & FC_RPORT_ROLE_FCP_TARGET)) { - rport->scsi_target_id = fc_host->next_target_id++; - create = 1; + if (roles & FC_PORT_ROLE_FCP_TARGET) { + if (rport->scsi_target_id == -1) { + rport->scsi_target_id = fc_host->next_target_id++; + create = 1; + } else if (!(rport->roles & FC_PORT_ROLE_FCP_TARGET)) + create = 1; + } else if (shost->active_mode & MODE_TARGET) { + ret = fc_tgt_it_nexus_create(shost, (unsigned long)rport, + (char *)&rport->node_name); + if (ret) + printk(KERN_ERR "FC Remore Port tgt nexus failed %d\n", + ret); } + + rport->roles = roles; + spin_unlock_irqrestore(shost->host_lock, flags); - if (create) + if (create) { + /* + * There may have been a delete timer running on the + * port. Ensure that it is cancelled as we now know + * the port is an FCP Target. + * Note: we know the rport is exists and in an online + * state as the LLDD would not have had an rport + * reference to pass us. + * + * Take no action on the del_timer failure as the state + * machine state change will validate the + * transaction. + */ + if (!cancel_delayed_work(&rport->fail_io_work)) + fc_flush_devloss(shost); + if (!cancel_delayed_work(&rport->dev_loss_work)) + fc_flush_devloss(shost); + + spin_lock_irqsave(shost->host_lock, flags); + rport->flags &= ~FC_RPORT_DEVLOSS_PENDING; + spin_unlock_irqrestore(shost->host_lock, flags); + + /* ensure any stgt delete functions are done */ + fc_flush_work(shost); + /* initiate a scan of the target */ + spin_lock_irqsave(shost->host_lock, flags); + rport->flags |= FC_RPORT_SCAN_PENDING; scsi_queue_work(shost, &rport->scan_work); + spin_unlock_irqrestore(shost->host_lock, flags); + scsi_target_unblock(&rport->dev); + } } EXPORT_SYMBOL(fc_remote_port_rolechg); /** - * fc_timeout_blocked_rport - Timeout handler for blocked remote port - * that fails to return in the alloted time. - * @data: scsi target that failed to reappear in the alloted time. - **/ + * fc_timeout_deleted_rport - Timeout handler for a deleted remote port. + * @work: rport target that failed to reappear in the allotted time. + * + * Description: An attempt to delete a remote port blocks, and if it fails + * to return in the allotted time this gets called. + */ static void -fc_timeout_blocked_rport(void *data) +fc_timeout_deleted_rport(struct work_struct *work) { - struct fc_rport *rport = (struct fc_rport *)data; + struct fc_rport *rport = + container_of(work, struct fc_rport, dev_loss_work.work); + struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); + unsigned long flags; + + spin_lock_irqsave(shost->host_lock, flags); + + rport->flags &= ~FC_RPORT_DEVLOSS_PENDING; + + /* + * If the port is ONLINE, then it came back. If it was a SCSI + * target, validate it still is. If not, tear down the + * scsi_target on it. + */ + if ((rport->port_state == FC_PORTSTATE_ONLINE) && + (rport->scsi_target_id != -1) && + !(rport->roles & FC_PORT_ROLE_FCP_TARGET)) { + dev_printk(KERN_ERR, &rport->dev, + "blocked FC remote port time out: no longer" + " a FCP target, removing starget\n"); + spin_unlock_irqrestore(shost->host_lock, flags); + scsi_target_unblock(&rport->dev); + fc_queue_work(shost, &rport->stgt_delete_work); + return; + } - rport->port_state = FC_PORTSTATE_OFFLINE; + /* NOOP state - we're flushing workq's */ + if (rport->port_state != FC_PORTSTATE_BLOCKED) { + spin_unlock_irqrestore(shost->host_lock, flags); + dev_printk(KERN_ERR, &rport->dev, + "blocked FC remote port time out: leaving" + " rport%s alone\n", + (rport->scsi_target_id != -1) ? " and starget" : ""); + return; + } + + if ((fc_host->tgtid_bind_type == FC_TGTID_BIND_NONE) || + (rport->scsi_target_id == -1)) { + list_del(&rport->peers); + rport->port_state = FC_PORTSTATE_DELETED; + dev_printk(KERN_ERR, &rport->dev, + "blocked FC remote port time out: removing" + " rport%s\n", + (rport->scsi_target_id != -1) ? " and starget" : ""); + fc_queue_work(shost, &rport->rport_delete_work); + spin_unlock_irqrestore(shost->host_lock, flags); + return; + } dev_printk(KERN_ERR, &rport->dev, - "blocked FC remote port time out: removing target\n"); + "blocked FC remote port time out: removing target and " + "saving binding\n"); + + list_move_tail(&rport->peers, &fc_host->rport_bindings); + + /* + * Note: We do not remove or clear the hostdata area. This allows + * host-specific target data to persist along with the + * scsi_target_id. It's up to the host to manage it's hostdata area. + */ + + /* + * Reinitialize port attributes that may change if the port comes back. + */ + rport->maxframe_size = -1; + rport->supported_classes = FC_COS_UNSPECIFIED; + rport->roles = FC_PORT_ROLE_UNKNOWN; + rport->port_state = FC_PORTSTATE_NOTPRESENT; + + /* remove the identifiers that aren't used in the consisting binding */ + switch (fc_host->tgtid_bind_type) { + case FC_TGTID_BIND_BY_WWPN: + rport->node_name = -1; + rport->port_id = -1; + break; + case FC_TGTID_BIND_BY_WWNN: + rport->port_name = -1; + rport->port_id = -1; + break; + case FC_TGTID_BIND_BY_ID: + rport->node_name = -1; + rport->port_name = -1; + break; + case FC_TGTID_BIND_NONE: /* to keep compiler happy */ + break; + } /* * As this only occurs if the remote port (scsi target) * went away and didn't come back - we'll remove * all attached scsi devices. */ + spin_unlock_irqrestore(shost->host_lock, flags); + scsi_target_unblock(&rport->dev); - scsi_remove_target(&rport->dev); + fc_queue_work(shost, &rport->stgt_delete_work); } /** - * fc_remote_port_block - temporarily block any scsi traffic to a remote port. - * @rport: remote port to be blocked. + * fc_timeout_fail_rport_io - Timeout handler for a fast io failing on a disconnected SCSI target. + * @work: rport to terminate io on. * - * scsi lldd's with a FC transport call this routine to temporarily stop - * all scsi traffic to a remote port. If the port is not a SCSI target, - * no action is taken. If the port is a SCSI target, all attached devices - * are placed into a SDEV_BLOCK state and a timer is started. The timer is - * represents the maximum amount of time the port may be blocked. If the - * timer expires, the port is considered non-existent and the attached - * scsi devices will be removed. - * - * Called from interrupt or normal process context. + * Notes: Only requests the failure of the io, not that all are flushed + * prior to returning. + */ +static void +fc_timeout_fail_rport_io(struct work_struct *work) +{ + struct fc_rport *rport = + container_of(work, struct fc_rport, fail_io_work.work); + struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_internal *i = to_fc_internal(shost->transportt); + + if (rport->port_state != FC_PORTSTATE_BLOCKED) + return; + + i->f->terminate_rport_io(rport); +} + +/** + * fc_scsi_scan_rport - called to perform a scsi scan on a remote port. + * @work: remote port to be scanned. + */ +static void +fc_scsi_scan_rport(struct work_struct *work) +{ + struct fc_rport *rport = + container_of(work, struct fc_rport, scan_work); + struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_internal *i = to_fc_internal(shost->transportt); + unsigned long flags; + + if ((rport->port_state == FC_PORTSTATE_ONLINE) && + (rport->roles & FC_PORT_ROLE_FCP_TARGET) && + !(i->f->disable_target_scan)) { + scsi_scan_target(&rport->dev, rport->channel, + rport->scsi_target_id, SCAN_WILD_CARD, 1); + } + + spin_lock_irqsave(shost->host_lock, flags); + rport->flags &= ~FC_RPORT_SCAN_PENDING; + spin_unlock_irqrestore(shost->host_lock, flags); +} + + +/** + * fc_vport_create - allocates and creates a FC virtual port. + * @shost: scsi host the virtual port is connected to. + * @channel: Channel on shost port connected to. + * @pdev: parent device for vport + * @ids: The world wide names, FC4 port roles, etc for + * the virtual port. + * @ret_vport: The pointer to the created vport. * - * Returns zero if successful or error if not + * Allocates and creates the vport structure, calls the parent host + * to instantiate the vport, the completes w/ class and sysfs creation. * * Notes: - * This routine assumes no locks are held on entry. - * - * The timeout and timer types are extracted from the fc transport - * attributes from the caller's rport pointer. - **/ -int -fc_remote_port_block(struct fc_rport *rport) + * This routine assumes no locks are held on entry. + */ +static int +fc_vport_create(struct Scsi_Host *shost, int channel, struct device *pdev, + struct fc_vport_identifiers *ids, struct fc_vport **ret_vport) { - int timeout = rport->dev_loss_tmo; - struct work_struct *work = &rport->dev_loss_work; + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); + struct fc_internal *fci = to_fc_internal(shost->transportt); + struct fc_vport *vport; + struct device *dev; + unsigned long flags; + size_t size; + int error; - if (timeout < 0 || timeout > SCSI_DEVICE_BLOCK_MAX_TIMEOUT) - return -EINVAL; + *ret_vport = NULL; - scsi_target_block(&rport->dev); + if ( ! fci->f->vport_create) + return -ENOENT; - /* cap the length the devices can be blocked */ - schedule_delayed_work(work, timeout * HZ); + size = (sizeof(struct fc_vport) + fci->f->dd_fcvport_size); + vport = kzalloc(size, GFP_KERNEL); + if (unlikely(!vport)) { + printk(KERN_ERR "%s: allocation failure\n", __func__); + return -ENOMEM; + } + + vport->vport_state = FC_VPORT_UNKNOWN; + vport->vport_last_state = FC_VPORT_UNKNOWN; + vport->node_name = ids->node_name; + vport->port_name = ids->port_name; + vport->roles = ids->roles; + vport->vport_type = ids->vport_type; + if (fci->f->dd_fcvport_size) + vport->dd_data = &vport[1]; + vport->shost = shost; + vport->channel = channel; + vport->flags = FC_VPORT_CREATING; + INIT_WORK(&vport->vport_delete_work, fc_vport_sched_delete); + + spin_lock_irqsave(shost->host_lock, flags); + + if (fc_host->npiv_vports_inuse >= fc_host->max_npiv_vports) { + spin_unlock_irqrestore(shost->host_lock, flags); + kfree(vport); + return -ENOSPC; + } + fc_host->npiv_vports_inuse++; + vport->number = fc_host->next_vport_number++; + list_add_tail(&vport->peers, &fc_host->vports); + get_device(&shost->shost_gendev); /* for fc_host->vport list */ + + spin_unlock_irqrestore(shost->host_lock, flags); + + dev = &vport->dev; + device_initialize(dev); /* takes self reference */ + dev->parent = get_device(pdev); /* takes parent reference */ + dev->release = fc_vport_dev_release; + sprintf(dev->bus_id, "vport-%d:%d-%d", + shost->host_no, channel, vport->number); + transport_setup_device(dev); + + error = device_add(dev); + if (error) { + printk(KERN_ERR "FC Virtual Port device_add failed\n"); + goto delete_vport; + } + transport_add_device(dev); + transport_configure_device(dev); + + error = fci->f->vport_create(vport, ids->disable); + if (error) { + printk(KERN_ERR "FC Virtual Port LLDD Create failed\n"); + goto delete_vport_all; + } + + /* + * if the parent isn't the physical adapter's Scsi_Host, ensure + * the Scsi_Host at least contains ia symlink to the vport. + */ + if (pdev != &shost->shost_gendev) { + error = sysfs_create_link(&shost->shost_gendev.kobj, + &dev->kobj, dev->bus_id); + if (error) + printk(KERN_ERR + "%s: Cannot create vport symlinks for " + "%s, err=%d\n", + __func__, dev->bus_id, error); + } + spin_lock_irqsave(shost->host_lock, flags); + vport->flags &= ~FC_VPORT_CREATING; + spin_unlock_irqrestore(shost->host_lock, flags); + + dev_printk(KERN_NOTICE, pdev, + "%s created via shost%d channel %d\n", dev->bus_id, + shost->host_no, channel); + + *ret_vport = vport; - rport->port_state = FC_PORTSTATE_BLOCKED; return 0; + +delete_vport_all: + transport_remove_device(dev); + device_del(dev); +delete_vport: + transport_destroy_device(dev); + spin_lock_irqsave(shost->host_lock, flags); + list_del(&vport->peers); + put_device(&shost->shost_gendev); /* for fc_host->vport list */ + fc_host->npiv_vports_inuse--; + spin_unlock_irqrestore(shost->host_lock, flags); + put_device(dev->parent); + kfree(vport); + + return error; } -EXPORT_SYMBOL(fc_remote_port_block); + /** - * fc_remote_port_unblock - restart any blocked scsi traffic to a remote port. - * @rport: remote port to be unblocked. + * fc_vport_terminate - Admin App or LLDD requests termination of a vport + * @vport: fc_vport to be terminated * - * scsi lld's with a FC transport call this routine to restart IO to all - * devices associated with the caller's scsi target following a fc_target_block - * request. Called from interrupt or normal process context. + * Calls the LLDD vport_delete() function, then deallocates and removes + * the vport from the shost and object tree. * * Notes: * This routine assumes no locks are held on entry. - **/ - void -fc_remote_port_unblock(struct fc_rport *rport) + */ +int +fc_vport_terminate(struct fc_vport *vport) { - struct work_struct *work = &rport->dev_loss_work; - struct Scsi_Host *shost = rport_to_shost(rport); + struct Scsi_Host *shost = vport_to_shost(vport); + struct fc_host_attrs *fc_host = shost_to_fc_host(shost); + struct fc_internal *i = to_fc_internal(shost->transportt); + struct device *dev = &vport->dev; + unsigned long flags; + int stat; - /* - * Stop the target timer first. Take no action on the del_timer - * failure as the state machine state change will validate the - * transaction. - */ - if (!cancel_delayed_work(work)) - flush_scheduled_work(); + spin_lock_irqsave(shost->host_lock, flags); + if (vport->flags & FC_VPORT_CREATING) { + spin_unlock_irqrestore(shost->host_lock, flags); + return -EBUSY; + } + if (vport->flags & (FC_VPORT_DEL)) { + spin_unlock_irqrestore(shost->host_lock, flags); + return -EALREADY; + } + vport->flags |= FC_VPORT_DELETING; + spin_unlock_irqrestore(shost->host_lock, flags); - if (rport->port_state == FC_PORTSTATE_OFFLINE) - /* - * initiate a scan of the target as the target has - * been torn down. - */ - scsi_queue_work(shost, &rport->scan_work); + if (i->f->vport_delete) + stat = i->f->vport_delete(vport); else - scsi_target_unblock(&rport->dev); + stat = -ENOENT; - rport->port_state = FC_PORTSTATE_ONLINE; + spin_lock_irqsave(shost->host_lock, flags); + vport->flags &= ~FC_VPORT_DELETING; + if (!stat) { + vport->flags |= FC_VPORT_DELETED; + list_del(&vport->peers); + fc_host->npiv_vports_inuse--; + put_device(&shost->shost_gendev); /* for fc_host->vport list */ + } + spin_unlock_irqrestore(shost->host_lock, flags); + + if (stat) + return stat; + + if (dev->parent != &shost->shost_gendev) + sysfs_remove_link(&shost->shost_gendev.kobj, dev->bus_id); + transport_remove_device(dev); + device_del(dev); + transport_destroy_device(dev); + + /* + * Removing our self-reference should mean our + * release function gets called, which will drop the remaining + * parent reference and free the data structure. + */ + put_device(dev); /* for self-reference */ + + return 0; /* SUCCESS */ } -EXPORT_SYMBOL(fc_remote_port_unblock); +EXPORT_SYMBOL(fc_vport_terminate); /** - * fc_scsi_scan_rport - called to perform a scsi scan on a remote port. - * @data: remote port to be scanned. - **/ + * fc_vport_sched_delete - workq-based delete request for a vport + * @work: vport to be deleted. + */ static void -fc_scsi_scan_rport(void *data) +fc_vport_sched_delete(struct work_struct *work) { - struct fc_rport *rport = (struct fc_rport *)data; - - scsi_scan_target(&rport->dev, rport->channel, rport->scsi_target_id, - SCAN_WILD_CARD, 1); + struct fc_vport *vport = + container_of(work, struct fc_vport, vport_delete_work); + int stat; + + stat = fc_vport_terminate(vport); + if (stat) + dev_printk(KERN_ERR, vport->dev.parent, + "%s: %s could not be deleted created via " + "shost%d channel %d - error %d\n", __func__, + vport->dev.bus_id, vport->shost->host_no, + vport->channel, stat); } -MODULE_AUTHOR("Martin Hicks"); +/* Original Author: Martin Hicks */ +MODULE_AUTHOR("James Smart"); MODULE_DESCRIPTION("FC Transport Attributes"); MODULE_LICENSE("GPL");