This patch adds SCSI (initiator) as FC4 type that registers with the FC layer. This layer bridges the FC subsystem and the SCSI subsystem. When a fcvport is created in the FC layer a callback is made to scsi_transport_fcp and a fcpinit and Scsi_Host are allocated and added to sysfs. When a fcrport is created in the FC layer a callback is made to scsi_transport_fcp and a fcptarg is allocated, added to sysfs and SCSI is notified to scan. The devices created by scsi_transport_fcp are: fcpinit: Object that bridges fcvport and Scsi_Host Attributes: permanent_port_name (misplaced?) port_state (misplaced?) system_hostname (misplaced?) I'm not sure these are in the right place. These attributes seem like they should be moved to the fcport within the FC subsystem. fcptarg: Object that bridges fcrports and stargets Attributes: scsi_target_id The fcptarg is not linked to the scsi targets in sysfs right now. Signed-off-by: Robert Love <robert.w.love@xxxxxxxxx> --- drivers/scsi/Kconfig | 10 drivers/scsi/Makefile | 1 drivers/scsi/scsi_transport_fcp.c | 1598 +++++++++++++++++++++++++++++++++++++ include/scsi/scsi_transport_fcp.h | 325 ++++++++ 4 files changed, 1934 insertions(+), 0 deletions(-) create mode 100644 drivers/scsi/scsi_transport_fcp.c create mode 100644 include/scsi/scsi_transport_fcp.h diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 75f2336..f0e56ce 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -299,6 +299,16 @@ config SCSI_FC_ATTRS each attached FiberChannel device to sysfs, say Y. Otherwise, say N. +config SCSI_FCP_ATTRS + tristate "Experimental Fiber Channel Protocol Transport Attributes" + depends on SCSI + depends on FC + select SCSI_NETLINK + help + If you wish to export transport-specific information about + each attached FiberChannel device to sysfs, say Y. + Otherwise, say N. + config SCSI_FC_TGT_ATTRS bool "SCSI target support for FiberChannel Transport Attributes" depends on SCSI_FC_ATTRS diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 1c7ac49..c68fcfa 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_RAID_ATTRS) += raid_class.o # -------------------------- obj-$(CONFIG_SCSI_SPI_ATTRS) += scsi_transport_spi.o obj-$(CONFIG_SCSI_FC_ATTRS) += scsi_transport_fc.o +obj-$(CONFIG_SCSI_FCP_ATTRS) += scsi_transport_fcp.o obj-$(CONFIG_SCSI_ISCSI_ATTRS) += scsi_transport_iscsi.o obj-$(CONFIG_SCSI_SAS_ATTRS) += scsi_transport_sas.o obj-$(CONFIG_SCSI_SAS_LIBSAS) += libsas/ diff --git a/drivers/scsi/scsi_transport_fcp.c b/drivers/scsi/scsi_transport_fcp.c new file mode 100644 index 0000000..f4a0d3f --- /dev/null +++ b/drivers/scsi/scsi_transport_fcp.c @@ -0,0 +1,1598 @@ +/* + * Copyright(c) 2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_fcp.h> +#include <scsi/scsi_cmnd.h> +#include <linux/netlink.h> +#include <net/netlink.h> +#include <scsi/scsi_netlink_fc.h> +#include <scsi/scsi_bsg_fc.h> +#include "scsi_priv.h" +#include "scsi_transport_fc_internal.h" + +/* + * Original Author: Martin Hicks + * Revised by: James Smart +*/ +MODULE_AUTHOR("Robert Love"); +MODULE_DESCRIPTION("Fibre Channel to SCSI Bridge"); +MODULE_LICENSE("GPL"); + +static int fc_bsg_hostadd(struct Scsi_Host *); +static void fc_bsg_remove(struct request_queue *q); +static int fc_bsg_fcptargadd(struct fc_fcpinit *fcpinit, + struct fc_fcptarg *fcptarg); +static void fc_bsg_request_handler(struct request_queue *q, + struct Scsi_Host *shost, + struct fc_fcptarg *fcptarg, + struct device *dev); +static void fc_bsg_softirq_done(struct request *rq); +static enum blk_eh_timer_return fc_bsg_job_timeout(struct request *req); + + +static void fcp_starget_delete(struct fc_rport *rport); +static void fcp_starget_delete_work(struct work_struct *work); +static void fcp_scsi_scan_rport(struct work_struct *work); + +/* Convert fcpinit_event_code values to ascii string name */ +static const struct { + enum fcpinit_event_code value; + char *name; +} fcpinit_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(fcpinit_event_code, fcpinit_event_code, + fcpinit_event_code_names) +#define FCPINIT_EVENT_CODE_MAX_NAMELEN 30 + +#define fcpinit_store_function(field) \ +static ssize_t \ +store_fcpinit_##field(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct fc_fcpinit *fcpinit = dev_to_fcpinit(dev); \ + int val; \ + char *cp; \ + \ + val = simple_strtoul(buf, &cp, 0); \ + if (*cp && (*cp != '\n')) \ + return -EINVAL; \ + fcpinit->f->set_fcpinit_##field(fcpinit, val); \ + return count; \ +} + +#define fcpinit_store_str_function(field, slen) \ +static ssize_t \ +store_fcpinit_##field(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct fc_fcpinit *fcpinit = dev_to_fcpinit(dev); \ + 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(fcpinit_##field(fcpinit), buf, cnt); \ + fcpinit->f->set_fcpinit_##field(fcpinit); \ + return count; \ +} + +#define fcpinit_rd_attr(field, format_string, sz) \ + fc_always_show_function(fcpinit, field, format_string, sz, ) \ + static FC_DEVICE_ATTR(fcpinit, field, S_IRUGO, \ + show_fcpinit_##field, NULL) + +#define fcpinit_rd_attr_cast(field, format_string, sz, cast) \ + fc_always_show_function(fcpinit, field, format_string, sz, (cast)) \ + static FC_DEVICE_ATTR(fcpinit, field, S_IRUGO, \ + show_fcpinit_##field, NULL) + +#define fcpinit_rw_attr(field, format_string, sz) \ + fc_always_show_function(fcpinit, field, format_string, sz, ) \ + fcpinit_store_function(field) \ + static FC_DEVICE_ATTR(fcpinit, field, S_IRUGO | S_IWUSR, \ + show_fcpinit_##field, \ + store_fcpinit_##field) + +#define fcpinit_rd_enum_attr(title, maxlen) \ +static ssize_t \ +show_fcpinit_##title (struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct fc_fcpinit *fcpinit = dev_to_fcpinit(dev); \ + const char *name; \ + if (fcpinit->f->get_fcpinit_##title) \ + fcpinit->f->get_fcpinit_##title(fcpinit); \ + name = get_fc_##title##_name(fcpinit_##title(fcpinit)); \ + if (!name) \ + return -EINVAL; \ + return snprintf(buf, maxlen, "%s\n", name); \ +} \ +static FC_DEVICE_ATTR(fcpinit, title, S_IRUGO, show_fcpinit_##title, NULL) + +/* Fixed Host Attributes */ + +fcpinit_rd_attr_cast(permanent_port_name, "0x%llx\n", 20, + unsigned long long); + +/* Dynamic Host Attributes */ + +fcpinit_rd_enum_attr(port_state, FC_PORTSTATE_MAX_NAMELEN); + +fc_always_show_function(fcpinit, system_hostname, "%s\n", + FC_SYMBOLIC_NAME_SIZE + 1, ) + +fcpinit_store_str_function(system_hostname, FC_SYMBOLIC_NAME_SIZE) +static FC_DEVICE_ATTR(fcpinit, system_hostname, S_IRUGO | S_IWUSR, + show_fcpinit_system_hostname, + store_fcpinit_system_hostname); + +#define fc_fcpinit_rd_attr_cast(field, format_string, sz, cast) \ + fc_always_show_function(fcpinit, field, format_string, sz, (cast)) \ + static FC_DEVICE_ATTR(fcpinit, field, S_IRUGO, \ + show_fcpinit_##field, NULL) + +/* Show a given an attribute in the statistics group */ +static ssize_t +fc_stat_show(const struct device *dev, char *buf, unsigned long offset) +{ + struct fc_fcpinit *fcpinit = dev_to_fcpinit(dev); + struct fcpinit_statistics *stats; + ssize_t ret = -ENOENT; + + if (offset > sizeof(struct fcpinit_statistics) || + offset % sizeof(u64) != 0) + WARN_ON(1); + + if (fcpinit->f->get_fcpinit_stats) { + stats = (fcpinit->f->get_fcpinit_stats)(fcpinit); + if (stats) + ret = snprintf(buf, 20, "0x%llx\n", + (unsigned long long)*(u64 *)(((u8 *) stats) + offset)); + } + return ret; +} + + +/* generate a read-only statistics attribute */ +#define fcpinit_statistic(name) \ +static ssize_t show_fcstat_##name(struct device *cd, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + return fc_stat_show(cd, buf, \ + offsetof(struct fcpinit_statistics, name)); \ +} \ +static FC_DEVICE_ATTR(host, name, S_IRUGO, show_fcstat_##name, NULL) + +fcpinit_statistic(seconds_since_last_reset); +fcpinit_statistic(tx_frames); +fcpinit_statistic(tx_words); +fcpinit_statistic(rx_frames); +fcpinit_statistic(rx_words); +fcpinit_statistic(lip_count); +fcpinit_statistic(nos_count); +fcpinit_statistic(error_frames); +fcpinit_statistic(dumped_frames); +fcpinit_statistic(link_failure_count); +fcpinit_statistic(loss_of_sync_count); +fcpinit_statistic(loss_of_signal_count); +fcpinit_statistic(prim_seq_protocol_err_count); +fcpinit_statistic(invalid_tx_word_count); +fcpinit_statistic(invalid_crc_count); +fcpinit_statistic(fcp_input_requests); +fcpinit_statistic(fcp_output_requests); +fcpinit_statistic(fcp_control_requests); +fcpinit_statistic(fcp_input_megabytes); +fcpinit_statistic(fcp_output_megabytes); + +static ssize_t +fc_reset_statistics(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fc_fcpinit *fcpinit = dev_to_fcpinit(dev); + + /* ignore any data value written to the attribute */ + if (fcpinit->f->reset_fcpinit_stats) { + fcpinit->f->reset_fcpinit_stats(fcpinit); + return count; + } + + return -ENOENT; +} +static FC_DEVICE_ATTR(host, reset_statistics, S_IWUSR, NULL, + fc_reset_statistics); + +static struct attribute *fc_statistics_attrs[] = { + &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 +}; + +static struct attribute_group fc_statistics_group = { + .name = "statistics", + .attrs = fc_statistics_attrs, +}; + + +#define fc_fcptarg_rd_attr(field, format_string, sz) \ + fc_always_show_function(fcptarg, field, format_string, sz, ) \ + static FC_DEVICE_ATTR(fcptarg, field, S_IRUGO, \ + show_fcptarg_##field, NULL) + +fc_fcptarg_rd_attr(scsi_target_id, "%d\n", 20); + + +/** + * 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 scsi_target *starget = scsi_target(scmd->device); + struct fc_fcptarg *fcptarg = starget_to_fcptarg(starget); + struct fc_rport *rport = fcptarg->rport; + + 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_fcpinit *fcpinit = shost_priv(shost); + struct fc_fcvport *fcvport = fcpinit_to_fcvport(fcpinit); + struct fc_rport *rport; + struct fc_fcptarg *fcptarg; + unsigned long flags; + + spin_lock_irqsave(&fcvport->lock, flags); + + list_for_each_entry(rport, &fcvport_rports(fcvport), peers) { + fcptarg = rport_to_fcptarg(rport); + + if (fcptarg->scsi_target_id == -1) + continue; + + if (rport->port_state != FC_PORTSTATE_ONLINE) + continue; + + if ((channel == rport->channel) && + (id == fcptarg->scsi_target_id)) { + spin_unlock_irqrestore(&fcvport->lock, flags); + scsi_scan_target(&rport->dev, channel, id, lun, 1); + return; + } + } + + spin_unlock_irqrestore(&fcvport->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) +{ + /* + * TODO: This seems far too simple, what am I missing? + */ + return shost->transportt->tsk_mgmt_response(shost, nexus, + tm_id, result); +} + +static int fc_it_nexus_response(struct Scsi_Host *shost, u64 nexus, int result) +{ + /* + * TODO: This seems far too simple, what am I missing? + */ + return shost->transportt->it_nexus_response(shost, nexus, result); +} + +static void fc_fcpinit_release(struct device *dev) +{ + struct fc_fcpinit *fcpinit = dev_to_fcpinit(dev); + struct Scsi_Host *shost = fcpinit_to_shost(fcpinit); + + scsi_host_put(shost); +} + +static void fcp_init_del(struct fc_fcvport *fcvport) +{ + struct fc_fcpinit *fcpinit = fcvport_to_fcpinit(fcvport); + struct Scsi_Host *shost = fcpinit_to_shost(fcpinit); + + fc_bsg_remove(fcpinit->rqst_q); + + /* flush all scan work items */ + scsi_flush_work(shost); + + device_del(&fcpinit->dev); + put_device(fcpinit->dev.parent); + + /* + * Detach from the scsi-ml. This frees the shost, + * fcpinit and any LLD private data. + */ + scsi_remove_host(shost); + + put_device(&fcpinit->dev); +} + +static struct scsi_transport_template fcpinit_scsi_transport_template = { + /* Use the shost workq for scsi scanning */ + .create_work_queue = 1, + + .eh_timed_out = fc_timed_out, + + .user_scan = fc_user_scan, + + /* target-mode drivers' functions */ + .tsk_mgmt_response = fc_tsk_mgmt_response, + .it_nexus_response = fc_it_nexus_response, +}; + +static int fcp_init_add(struct fc_fcvport *fcvport, void *v) +{ + struct fcp_template *f = v; + struct Scsi_Host *shost; + struct fc_fcpinit *fcpinit; + int error = 0; + int count = 0; + + int priv_size = sizeof(struct fc_fcpinit) + f->fcpinit_priv_size; + shost = scsi_host_alloc(&f->shost_template, priv_size); + if (!shost) + return -ENOMEM; + + shost->transportt = &fcpinit_scsi_transport_template; + + fcpinit = shost_priv(shost); + fcpinit->f = f; + + fcvport->fc4_priv = fcpinit; + fcpinit->fcvport = fcvport; + fcpinit->shost = shost; + fcpinit->next_target_id = 0; + + device_initialize(&fcpinit->dev); + fcpinit->dev.release = fc_fcpinit_release; + dev_set_name(&fcpinit->dev, "fcpinit_%d", shost->host_no); + + /* + * 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. + */ + + /* + * TODO: Most of this stuff needs to move to fcvport because + * rports will be attached to the vport not fcpinit + */ + fcpinit->permanent_port_name = -1; + fcpinit->port_state = FC_PORTSTATE_UNKNOWN; + memset(fcpinit->system_hostname, 0, sizeof(fcpinit->system_hostname)); + + /* + * Need to call back to the LLD with priv memory + */ + f->fcp_fcpinit_callback(fcpinit); + + fcpinit->dev.parent = get_device(&fcvport->dev); + + error = device_add(&fcpinit->dev); + if (error) + return error; + + /* add the new host to the SCSI-ml */ + error = scsi_add_host(shost, &fcpinit->dev); + if (error) + return error; + + fc_bsg_hostadd(shost); + /* ignore any bsg add error - we just can't do sgio */ + + /* + * Setup SCSI Host Attributes. + */ + FC_SETUP_ALWAYS_ATTRIBUTE_RD_NS(fcpinit, permanent_port_name); + FC_SETUP_ALWAYS_ATTRIBUTE_RD_NS(fcpinit, port_state); + FC_SETUP_CONDITIONAL_ATTRIBUTE_RW(fcpinit, system_hostname); + + BUG_ON(count > FCPINIT_NUM_ATTRS); + FC_CREATE_ATTRIBUTES(fcpinit, count, 0); + + if (error || count != 0) { + /* + * TODO: This isn't right, should I be freeing the + * fcpinit if the device attributes couldn't be added? + */ + return -EINVAL; + } + + return 0; + +/* + * TODO: Where's the error handling for this routine + */ +} + +/* + * TODO: I'm not sure how to handle these events at the moment. + * They're scsi-ml interactions so they belong in fcpinit. + */ + +/** + * fcpinit_post_event - called to post an even on an fcpinit. + * @shost: host the event occurred on + * @event_number: fc event number obtained from get_fc_event_number() + * @event_code: fcpinit event being posted + * @event_data: 32bits of data for the event being posted + * + * Notes: + * This routine assumes no locks are held on entry. + */ +void fcpinit_post_event(struct Scsi_Host *shost, u32 event_number, + enum fcpinit_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; + + nlmsg_multicast(scsi_nl_sock, skb, 0, SCSI_NL_GRP_FC_EVENTS, + GFP_KERNEL); + return; + +send_fail_skb: + kfree_skb(skb); +send_fail: + name = get_fc_fcpinit_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 : "<unknown>", event_data, err); + return; +} +EXPORT_SYMBOL(fcpinit_post_event); + +/** + * fcpinit_post_vendor_event - called to post a vendor unique event on an fcpinit + * @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 fcpinit_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); + + nlmsg_multicast(scsi_nl_sock, skb, 0, SCSI_NL_GRP_FC_EVENTS, + GFP_KERNEL); + 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(fcpinit_post_vendor_event); + +/** + * fc_bsg_host_handler - handler for bsg requests for a fc host + * @q: fc host request queue + */ +static void fc_bsg_host_handler(struct request_queue *q) +{ + struct Scsi_Host *shost = q->queuedata; + + fc_bsg_request_handler(q, shost, NULL, &shost->shost_gendev); +} + +/** + * fc_bsg_hostadd - Create and add the bsg hooks so we can receive requests + * @shost: shost for fcpinit + */ +static int fc_bsg_hostadd(struct Scsi_Host *shost) +{ + struct fc_fcpinit *fcpinit = shost_priv(shost); + struct device *dev = &shost->shost_gendev; + struct request_queue *q; + int err; + char bsg_name[20]; + + fcpinit->rqst_q = NULL; + + if (!fcpinit->f->bsg_request) + return -ENOTSUPP; + + snprintf(bsg_name, sizeof(bsg_name), + "fcpinit%d", shost->host_no); + + q = __scsi_alloc_queue(shost, fc_bsg_host_handler); + if (!q) { + printk(KERN_ERR "fcpinit%d: bsg interface failed to " + "initialize - no request queue\n", + shost->host_no); + return -ENOMEM; + } + + q->queuedata = shost; + queue_flag_set_unlocked(QUEUE_FLAG_BIDI, q); + blk_queue_softirq_done(q, fc_bsg_softirq_done); + blk_queue_rq_timed_out(q, fc_bsg_job_timeout); + blk_queue_rq_timeout(q, FC_DEFAULT_BSG_TIMEOUT); + + err = bsg_register_queue(q, dev, bsg_name, NULL); + if (err) { + printk(KERN_ERR "fcpinit%d: bsg interface failed to " + "initialize - register queue\n", + shost->host_no); + blk_cleanup_queue(q); + return err; + } + + fcpinit->rqst_q = q; + return 0; +} + +/** + * fc_bsg_remove - Deletes the bsg hooks on fchosts/fcptargs + * @q: the request_queue that is to be torn down. + */ +void fc_bsg_remove(struct request_queue *q) +{ + if (q) { + bsg_unregister_queue(q); + blk_cleanup_queue(q); + } +} + +/* TARGET CODE */ + +static void fc_fcptarg_dev_release(struct device *dev) +{ + struct fc_fcptarg *fcptarg = dev_to_fcptarg(dev); + kfree(fcptarg); +} + +int scsi_is_fcptarg(const struct device *dev) +{ + return dev->release == fc_fcptarg_dev_release; +} +EXPORT_SYMBOL(scsi_is_fcptarg); + +static int fcp_targ_add(struct fc_rport *rport, + struct fc_fcvport *fcvport, + int channel) +{ + struct fc_fcpinit *fcpinit = fcvport_to_fcpinit(fcvport); + struct Scsi_Host *shost = fcpinit_to_shost(fcpinit); + struct fc_fcptarg *fcptarg; + int error = 0; + int count = 0; + + fcptarg = kzalloc(sizeof(struct fc_fcptarg), GFP_KERNEL); + if (!fcptarg) + return -ENOMEM; + + rport->fc4_priv = fcptarg; + fcptarg->rport = rport; + fcptarg->fcpinit = fcpinit; + + INIT_WORK(&fcptarg->stgt_delete_work, fcp_starget_delete_work); + INIT_WORK(&fcptarg->scan_work, fcp_scsi_scan_rport); + + if (rport->roles & FC_PORT_ROLE_FCP_TARGET) + fcptarg->scsi_target_id = fcpinit->next_target_id++; + else + fcptarg->scsi_target_id = -1; + + /* + * TODO: dev is a workaround device since scsi_scan_target + * wants a device whose parent is a shost. Setting the parent here + * is incorrect since any rports created before fcpinit is allocated + * will have invalid pointers. This workaround only works becuase + * the only rports that will call scsi_scan_target will be created + * after discovery, which is after fcp has been initialized, thus + * ensuring that fcpvport/fcpinit/shost will all exist. + */ + device_initialize(&fcptarg->dev); /* takes self reference */ + fcptarg->dev.parent = get_device(&shost->shost_gendev); + + /* + * TODO: This is terrible. Becuase we're passing the dev up + * into the scsi-ml it is using that device to setup scsi_target and + * scsi_device relationships. The problem is that when the lld (libfc) + * gets the slave_alloc (fc_slave_alloc) callback it tries to use the + * starget_to_rport macro which checks scsi_is_fc_rport. + * scsi_is_fc_rport looks at the device's release function. Since + * we're using this dummy device we need to setup the release + * routine as well. + * + * This is really a mess though. The scsi APIs (scsi_scan_target) + * requires that a device be passed up that has a relationship to the + * shost. + * + * We need to create a fcptarg that has that relationship to scsi + * or we need to change the scsi api to allow us to pass up an + * unrelated device and a shost. + */ + fcptarg->dev.release = fc_fcptarg_dev_release; + dev_set_name(&fcptarg->dev, "fcptarg-%d:%d-%d", + shost->host_no, channel, rport->number); + + /* + * TODO: Do I need to do this since we're not using the + * transport right now? + */ + transport_setup_device(&fcptarg->dev); + + error = device_add(&fcptarg->dev); + if (error) { + printk(KERN_ERR "FC Remote Port device_add failed\n"); + kfree(fcptarg); + return error; + } + transport_add_device(&fcptarg->dev); + transport_configure_device(&fcptarg->dev); + + fc_bsg_fcptargadd(fcpinit, fcptarg); + /* ignore any bsg add error - we just can't do sgio */ + + FC_SETUP_ALWAYS_ATTRIBUTE_RD_NS(fcptarg, scsi_target_id); + + BUG_ON(count > FCPTARG_NUM_ATTRS); + FC_CREATE_ATTRIBUTES(fcptarg, count, 0); + + if (error || count != 0) { + /* + * TODO: This isn't right, should I be freeing the + * fcptarg if the device attributes couldn't be added? + */ + return -EINVAL; + } + + return 0; + + /* +delete_fcptarg: + + * TODO: Fix error handling. + + put_device(&fcptarg->dev); + put_device(&fcpinit->dev); for fcpinit->rport list + put_device(&shost->shost_dev); + */ +} + +static void fcp_targ_del(struct fc_rport *rport) +{ + struct fc_fcptarg *fcptarg = rport_to_fcptarg(rport); + struct fc_fcpinit *fcpinit = fcptarg_to_fcpinit(fcptarg); + struct Scsi_Host *shost = fcpinit_to_shost(fcpinit); + fc_bsg_remove(fcptarg->rqst_q); + + /* Delete SCSI target and sdevs */ + if (fcptarg->scsi_target_id != -1) + fcp_starget_delete(rport); + + transport_remove_device(&fcptarg->dev); + device_del(&fcptarg->dev); + transport_destroy_device(&fcptarg->dev); + put_device(&shost->shost_gendev); + put_device(&fcptarg->dev); +} + +/** + * fc_block_scsi_eh - Block SCSI eh thread for blocked fc_rport + * @cmnd: SCSI command that scsi_eh is trying to recover + * + * This routine can be called from a FC LLD scsi_eh callback. It + * blocks the scsi_eh thread until the fc_rport leaves the + * FC_PORTSTATE_BLOCKED. This is necessary to avoid the scsi_eh + * failing recovery actions for blocked rports which would lead to + * offlined SCSI devices. + */ +static void fc_block_scsi_eh(struct scsi_cmnd *cmnd) +{ + struct fc_fcptarg *fcptarg = starget_to_fcptarg( + scsi_target(cmnd->device)); + struct fc_rport *rport = fcptarg->rport; + struct fc_fcpinit *fcpinit = fcptarg->fcpinit; + struct fc_fcvport *fcvport = fcpinit_to_fcvport(fcpinit); + unsigned long flags; + + spin_lock_irqsave(&fcvport->lock, flags); + while (rport->port_state == FC_PORTSTATE_BLOCKED) { + spin_unlock_irqrestore(&fcvport->lock, flags); + msleep(1000); + spin_lock_irqsave(&fcvport->lock, flags); + } + spin_unlock_irqrestore(&fcvport->lock, flags); +} + +/* + * BSG support + */ + +/** + * fc_destroy_bsgjob - routine to teardown/delete a fc bsg job + * @job: fc_bsg_job that is to be torn down + */ +static void fc_destroy_bsgjob(struct fc_bsg_job *job) +{ + unsigned long flags; + + spin_lock_irqsave(&job->job_lock, flags); + if (job->ref_cnt) { + spin_unlock_irqrestore(&job->job_lock, flags); + return; + } + spin_unlock_irqrestore(&job->job_lock, flags); + + put_device(job->dev); /* release reference for the request */ + + kfree(job->request_payload.sg_list); + kfree(job->reply_payload.sg_list); + kfree(job); +} + +/** + * fc_bsg_jobdone - completion routine for bsg requests that the LLD has + * completed + * @job: fc_bsg_job that is complete + */ +static void fc_bsg_jobdone(struct fc_bsg_job *job) +{ + struct request *req = job->req; + struct request *rsp = req->next_rq; + int err; + + err = job->req->errors = job->reply->result; + + if (err < 0) + /* we're only returning the result field in the reply */ + job->req->sense_len = sizeof(uint32_t); + else + job->req->sense_len = job->reply_len; + + /* we assume all request payload was transferred, residual == 0 */ + req->resid_len = 0; + + if (rsp) { + WARN_ON(job->reply->reply_payload_rcv_len > rsp->resid_len); + + /* set reply (bidi) residual */ + rsp->resid_len -= min(job->reply->reply_payload_rcv_len, + rsp->resid_len); + } + blk_complete_request(req); +} + +/** + * fc_bsg_softirq_done - softirq done routine for destroying the bsg requests + * @rq: BSG request that holds the job to be destroyed + */ +static void fc_bsg_softirq_done(struct request *rq) +{ + struct fc_bsg_job *job = rq->special; + unsigned long flags; + + spin_lock_irqsave(&job->job_lock, flags); + job->state_flags |= FC_RQST_STATE_DONE; + job->ref_cnt--; + spin_unlock_irqrestore(&job->job_lock, flags); + + blk_end_request_all(rq, rq->errors); + fc_destroy_bsgjob(job); +} + +/** + * fc_bsg_job_timeout - handler for when a bsg request timesout + * @req: request that timed out + */ +static enum blk_eh_timer_return fc_bsg_job_timeout(struct request *req) +{ + struct fc_bsg_job *job = (void *) req->special; + struct Scsi_Host *shost = job->shost; + struct fc_fcpinit *fcpinit = shost_priv(shost); + unsigned long flags; + int err = 0, done = 0; + + if (job->rport && job->rport->port_state == FC_PORTSTATE_BLOCKED) + return BLK_EH_RESET_TIMER; + + spin_lock_irqsave(&job->job_lock, flags); + if (job->state_flags & FC_RQST_STATE_DONE) + done = 1; + else + job->ref_cnt++; + spin_unlock_irqrestore(&job->job_lock, flags); + + if (!done && fcpinit->f->bsg_timeout) { + /* call LLDD to abort the i/o as it has timed out */ + err = fcpinit->f->bsg_timeout(job); + if (err == -EAGAIN) { + job->ref_cnt--; + return BLK_EH_RESET_TIMER; + } else if (err) + printk(KERN_ERR "ERROR: FC BSG request timeout - LLD " + "abort failed with status %d\n", err); + } + + /* the blk_end_sync_io() doesn't check the error */ + if (done) + return BLK_EH_NOT_HANDLED; + else + return BLK_EH_HANDLED; +} + +static int fc_bsg_map_buffer(struct fc_bsg_buffer *buf, struct request *req) +{ + size_t sz = (sizeof(struct scatterlist) * req->nr_phys_segments); + + BUG_ON(!req->nr_phys_segments); + + buf->sg_list = kzalloc(sz, GFP_KERNEL); + if (!buf->sg_list) + return -ENOMEM; + sg_init_table(buf->sg_list, req->nr_phys_segments); + buf->sg_cnt = blk_rq_map_sg(req->q, req, buf->sg_list); + buf->payload_len = blk_rq_bytes(req); + return 0; +} + + +/** + * fc_req_to_bsgjob - Allocate/create the fc_bsg_job structure for the + * bsg request + * @shost: SCSI Host corresponding to the bsg object + * @rport: (optional) FC Remote Port corresponding to the bsg object + * @req: BSG request that needs a job structure + */ +static int fc_req_to_bsgjob(struct Scsi_Host *shost, struct fc_rport *rport, + struct request *req) +{ + struct fc_fcpinit *fcpinit = shost_priv(shost); + struct request *rsp = req->next_rq; + struct fc_bsg_job *job; + int ret; + + BUG_ON(req->special); + + job = kzalloc(sizeof(struct fc_bsg_job) + fcpinit->f->dd_bsg_size, + GFP_KERNEL); + if (!job) + return -ENOMEM; + + /* + * Note: this is a bit silly. + * The request gets formatted as a SGIO v4 ioctl request, which + * then gets reformatted as a blk request, which then gets + * reformatted as a fc bsg request. And on completion, we have + * to wrap return results such that SGIO v4 thinks it was a scsi + * status. I hope this was all worth it. + */ + + req->special = job; + job->shost = shost; + job->rport = rport; + job->req = req; + if (fcpinit->f->dd_bsg_size) + job->dd_data = (void *)&job[1]; + spin_lock_init(&job->job_lock); + job->request = (struct fc_bsg_request *)req->cmd; + job->request_len = req->cmd_len; + job->reply = req->sense; + job->reply_len = SCSI_SENSE_BUFFERSIZE; /* Size of sense buffer + * allocated */ + if (req->bio) { + ret = fc_bsg_map_buffer(&job->request_payload, req); + if (ret) + goto failjob_rls_job; + } + if (rsp && rsp->bio) { + ret = fc_bsg_map_buffer(&job->reply_payload, rsp); + if (ret) + goto failjob_rls_rqst_payload; + } + job->job_done = fc_bsg_jobdone; + if (rport) + job->dev = &rport->dev; + else + job->dev = &shost->shost_gendev; + get_device(job->dev); /* take a reference for the request */ + + job->ref_cnt = 1; + + return 0; + + +failjob_rls_rqst_payload: + kfree(job->request_payload.sg_list); +failjob_rls_job: + kfree(job); + return -ENOMEM; +} + + +enum fc_dispatch_result { + FC_DISPATCH_BREAK, /* on return, q is locked, break from q loop */ + FC_DISPATCH_LOCKED, /* on return, q is locked, continue on */ + FC_DISPATCH_UNLOCKED, /* on return, q is unlocked, continue on */ +}; + + +/** + * fc_bsg_host_dispatch - process fc host bsg requests and dispatch to LLDD + * @q: fc host request queue + * @shost: scsi host rport attached to + * @job: bsg job to be processed + */ +static enum fc_dispatch_result +fc_bsg_host_dispatch(struct request_queue *q, struct Scsi_Host *shost, + struct fc_bsg_job *job) +{ + struct fc_fcpinit *fcpinit = shost_priv(shost); + int cmdlen = sizeof(uint32_t); /* start with length of msgcode */ + int ret; + + /* Validate the host command */ + switch (job->request->msgcode) { + case FC_BSG_HST_ADD_RPORT: + cmdlen += sizeof(struct fc_bsg_host_add_rport); + break; + + case FC_BSG_HST_DEL_RPORT: + cmdlen += sizeof(struct fc_bsg_host_del_rport); + break; + + case FC_BSG_HST_ELS_NOLOGIN: + cmdlen += sizeof(struct fc_bsg_host_els); + /* there better be a xmt and rcv payloads */ + if ((!job->request_payload.payload_len) || + (!job->reply_payload.payload_len)) { + ret = -EINVAL; + goto fail_host_msg; + } + break; + + case FC_BSG_HST_CT: + cmdlen += sizeof(struct fc_bsg_host_ct); + /* there better be xmt and rcv payloads */ + if ((!job->request_payload.payload_len) || + (!job->reply_payload.payload_len)) { + ret = -EINVAL; + goto fail_host_msg; + } + break; + + case FC_BSG_HST_VENDOR: + cmdlen += sizeof(struct fc_bsg_host_vendor); + if ((shost->hostt->vendor_id == 0L) || + (job->request->rqst_data.h_vendor.vendor_id != + shost->hostt->vendor_id)) { + ret = -ESRCH; + goto fail_host_msg; + } + break; + + default: + ret = -EBADR; + goto fail_host_msg; + } + + /* check if we really have all the request data needed */ + if (job->request_len < cmdlen) { + ret = -ENOMSG; + goto fail_host_msg; + } + + ret = fcpinit->f->bsg_request(job); + if (!ret) + return FC_DISPATCH_UNLOCKED; + +fail_host_msg: + /* return the errno failure code as the only status */ + BUG_ON(job->reply_len < sizeof(uint32_t)); + job->reply->reply_payload_rcv_len = 0; + job->reply->result = ret; + job->reply_len = sizeof(uint32_t); + fc_bsg_jobdone(job); + return FC_DISPATCH_UNLOCKED; +} + + +/* + * fc_bsg_goose_queue - restart rport queue in case it was stopped + * @rport: rport to be restarted + */ +static void fc_bsg_goose_queue(struct fc_rport *rport) +{ + struct fc_fcptarg *fcptarg = rport_to_fcptarg(rport); + int flagset; + unsigned long flags; + + if (!fcptarg->rqst_q) + return; + + get_device(&fcptarg->dev); + + spin_lock_irqsave(fcptarg->rqst_q->queue_lock, flags); + flagset = test_bit(QUEUE_FLAG_REENTER, &fcptarg->rqst_q->queue_flags) && + !test_bit(QUEUE_FLAG_REENTER, &fcptarg->rqst_q->queue_flags); + if (flagset) + queue_flag_set(QUEUE_FLAG_REENTER, fcptarg->rqst_q); + __blk_run_queue(fcptarg->rqst_q); + if (flagset) + queue_flag_clear(QUEUE_FLAG_REENTER, fcptarg->rqst_q); + spin_unlock_irqrestore(fcptarg->rqst_q->queue_lock, flags); + + put_device(&fcptarg->dev); +} + +/** + * fc_bsg_rport_dispatch - process rport bsg requests and dispatch to LLDD + * @q: rport request queue + * @shost: scsi host rport attached to + * @rport: rport request destined to + * @job: bsg job to be processed + */ +static enum fc_dispatch_result +fc_bsg_rport_dispatch(struct request_queue *q, struct Scsi_Host *shost, + struct fc_rport *rport, struct fc_bsg_job *job) +{ + struct fc_fcpinit *fcpinit = shost_priv(shost); + int cmdlen = sizeof(uint32_t); /* start with length of msgcode */ + int ret; + + /* Validate the rport command */ + switch (job->request->msgcode) { + case FC_BSG_RPT_ELS: + cmdlen += sizeof(struct fc_bsg_rport_els); + goto check_bidi; + + case FC_BSG_RPT_CT: + cmdlen += sizeof(struct fc_bsg_rport_ct); +check_bidi: + /* there better be xmt and rcv payloads */ + if ((!job->request_payload.payload_len) || + (!job->reply_payload.payload_len)) { + ret = -EINVAL; + goto fail_rport_msg; + } + break; + default: + ret = -EBADR; + goto fail_rport_msg; + } + + /* check if we really have all the request data needed */ + if (job->request_len < cmdlen) { + ret = -ENOMSG; + goto fail_rport_msg; + } + + ret = fcpinit->f->bsg_request(job); + if (!ret) + return FC_DISPATCH_UNLOCKED; + +fail_rport_msg: + /* return the errno failure code as the only status */ + BUG_ON(job->reply_len < sizeof(uint32_t)); + job->reply->reply_payload_rcv_len = 0; + job->reply->result = ret; + job->reply_len = sizeof(uint32_t); + fc_bsg_jobdone(job); + return FC_DISPATCH_UNLOCKED; +} + + +/** + * fc_bsg_request_handler - generic handler for bsg requests + * @q: request queue to manage + * @shost: Scsi_Host related to the bsg object + * @rport: FC remote port related to the bsg object (optional) + * @dev: device structure for bsg object + */ +static void fc_bsg_request_handler(struct request_queue *q, + struct Scsi_Host *shost, + struct fc_fcptarg *fcptarg, + struct device *dev) +{ + struct fc_rport *rport = fcptarg->rport; + struct request *req; + struct fc_bsg_job *job; + enum fc_dispatch_result ret; + + if (!get_device(dev)) + return; + + while (!blk_queue_plugged(q)) { + if (rport && (rport->port_state == FC_PORTSTATE_BLOCKED) && + !(rport->flags & FC_RPORT_FAST_FAIL_TIMEDOUT)) + break; + + req = blk_fetch_request(q); + if (!req) + break; + + if (rport && (rport->port_state != FC_PORTSTATE_ONLINE)) { + req->errors = -ENXIO; + spin_unlock_irq(q->queue_lock); + blk_end_request(req, -ENXIO, blk_rq_bytes(req)); + spin_lock_irq(q->queue_lock); + continue; + } + + spin_unlock_irq(q->queue_lock); + + ret = fc_req_to_bsgjob(shost, rport, req); + if (ret) { + req->errors = ret; + blk_end_request(req, ret, blk_rq_bytes(req)); + spin_lock_irq(q->queue_lock); + continue; + } + + job = req->special; + + /* check if we have the msgcode value at least */ + if (job->request_len < sizeof(uint32_t)) { + BUG_ON(job->reply_len < sizeof(uint32_t)); + job->reply->reply_payload_rcv_len = 0; + job->reply->result = -ENOMSG; + job->reply_len = sizeof(uint32_t); + fc_bsg_jobdone(job); + spin_lock_irq(q->queue_lock); + continue; + } + + /* the dispatch routines will unlock the queue_lock */ + if (rport) + ret = fc_bsg_rport_dispatch(q, shost, rport, job); + else + ret = fc_bsg_host_dispatch(q, shost, job); + + /* did dispatcher hit state that can't process any more */ + if (ret == FC_DISPATCH_BREAK) + break; + + /* did dispatcher had released the lock */ + if (ret == FC_DISPATCH_UNLOCKED) + spin_lock_irq(q->queue_lock); + } + + spin_unlock_irq(q->queue_lock); + put_device(dev); + spin_lock_irq(q->queue_lock); +} + +/** + * fc_bsg_rport_handler - handler for bsg requests for a fc rport + * @q: rport request queue + */ +static void fc_bsg_fcptarg_handler(struct request_queue *q) +{ + struct fc_fcptarg *fcptarg = q->queuedata; + struct Scsi_Host *shost = fcptarg_to_shost(fcptarg); + + /* + * TODO: Unknown whether this call works, last argument is suspect. + */ + fc_bsg_request_handler(q, shost, fcptarg, &fcptarg->dev); +} + +/** + * fc_bsg_fcptargadd - Create and add the bsg hooks so we can receive requests + * @shost: shost that rport is attached to + * @rport: rport that the bsg hooks are being attached to + */ +static int fc_bsg_fcptargadd(struct fc_fcpinit *fcpinit, + struct fc_fcptarg *fcptarg) +{ + struct device *dev = &fcptarg->dev; + struct Scsi_Host *shost = fcpinit_to_shost(fcpinit); + struct request_queue *q; + int err; + + fcptarg->rqst_q = NULL; + + if (!fcpinit->f->bsg_request) + return -ENOTSUPP; + + q = __scsi_alloc_queue(shost, fc_bsg_fcptarg_handler); + if (!q) { + printk(KERN_ERR "%s: bsg interface failed to " + "initialize - no request queue\n", + dev->kobj.name); + return -ENOMEM; + } + + q->queuedata = fcptarg; + queue_flag_set_unlocked(QUEUE_FLAG_BIDI, q); + blk_queue_softirq_done(q, fc_bsg_softirq_done); + blk_queue_rq_timed_out(q, fc_bsg_job_timeout); + blk_queue_rq_timeout(q, BLK_DEFAULT_SG_TIMEOUT); + + err = bsg_register_queue(q, dev, NULL, NULL); + if (err) { + printk(KERN_ERR "%s: bsg interface failed to " + "initialize - register queue\n", + dev->kobj.name); + blk_cleanup_queue(q); + return err; + } + + fcptarg->rqst_q = q; + return 0; +} + +static void fcp_targ_block(struct fc_rport *rport) +{ + struct fc_fcptarg *fcptarg = rport_to_fcptarg(rport); + scsi_target_block(&fcptarg->dev); +} + +static void fcp_targ_unblock(struct fc_rport *rport) +{ + struct fc_fcptarg *fcptarg = rport_to_fcptarg(rport); + scsi_target_unblock(&fcptarg->dev); +} + +static void fcp_targ_terminate_io(struct fc_rport *rport) +{ + /* Involve the LLDD if possible to terminate all io on the rport. */ + if (rport->f->terminate_rport_io) + rport->f->terminate_rport_io(rport); + + /* + * must unblock to flush queued IO. The caller will have set + * the port_state or flags, so that fc_remote_port_chkready will + * fail IO. + */ + fcp_targ_unblock(rport); +} + +static void fcp_starget_delete(struct fc_rport *rport) +{ + struct fc_fcptarg *fcptarg = rport_to_fcptarg(rport); + fcp_targ_terminate_io(rport); + scsi_remove_target(&fcptarg->dev); +} + +/** + * 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 fcp_starget_delete_work(struct work_struct *work) +{ + struct fc_fcptarg *fcptarg = + container_of(work, struct fc_fcptarg, stgt_delete_work); + struct fc_rport *rport = fcptarg_to_rport(fcptarg); + fcp_starget_delete(rport); +} + +static void fcp_queue_starget_delete(struct fc_rport *rport) +{ + struct fc_fcptarg *fcptarg = rport_to_fcptarg(rport); + struct fc_fcvport *fcvport = rport_to_fcvport(rport); + fc_queue_work(fcvport, &fcptarg->stgt_delete_work); +} + +static void fcp_targ_queue_scan(struct fc_rport *rport) +{ + struct fc_fcptarg *fcptarg = rport_to_fcptarg(rport); + struct Scsi_Host *shost = fcptarg_to_shost(fcptarg); + scsi_queue_work(shost, &fcptarg->scan_work); +} + +/** + * fcp_scsi_scan_rport - called to perform a scsi scan on a remote port. + * @work: remote port to be scanned. + */ +static void fcp_scsi_scan_rport(struct work_struct *work) +{ + struct fc_fcptarg *fcptarg = + container_of(work, struct fc_fcptarg, scan_work); + struct fc_rport *rport; + struct fc_fcvport *fcvport; + unsigned long flags; + + rport = fcptarg_to_rport(fcptarg); + fcvport = rport_to_fcvport(rport); + + if ((rport->port_state == FC_PORTSTATE_ONLINE) && + (rport->roles & FC_PORT_ROLE_FCP_TARGET) && + !(rport->f->disable_target_scan)) { + scsi_scan_target(&fcptarg->dev, + rport->channel, + fcptarg->scsi_target_id, + SCAN_WILD_CARD, 1); + } + + spin_lock_irqsave(&fcvport->lock, flags); + rport->flags &= ~FC_RPORT_SCAN_PENDING; + spin_unlock_irqrestore(&fcvport->lock, flags); +} + +static void fcp_init_scsi_flush_work(struct fc_fcvport *fcvport) +{ + struct fc_fcpinit *fcpinit = fcvport_to_fcpinit(fcvport); + struct Scsi_Host *shost = fcpinit_to_shost(fcpinit); + scsi_flush_work(shost); +} + +/* + * Return 1 to create, 0 to skip creation + */ +static int fcp_targ_rolechg(struct fc_rport *rport, u32 roles) +{ + struct fc_fcvport *fcvport = rport_to_fcvport(rport); + struct fc_fcpinit *fcpinit = fcvport_to_fcpinit(fcvport); + struct fc_fcptarg *fcptarg = rport_to_fcptarg(rport); + struct Scsi_Host *shost = fcpinit_to_shost(fcpinit); + int create = 0; + int ret; + + if (roles & FC_PORT_ROLE_FCP_TARGET) { + if (fcptarg->scsi_target_id == -1) { + fcptarg->scsi_target_id = fcpinit->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); + } + + return create; +} + +static void fcp_targ_final_delete(struct fc_rport *rport) +{ + struct fc_fcvport *fcvport = rport_to_fcvport(rport); + struct fc_fcpinit *fcpinit = fcvport_to_fcpinit(fcvport); + struct Scsi_Host *shost = fcpinit_to_shost(fcpinit); + + if (rport->roles & FC_PORT_ROLE_FCP_INITIATOR && + shost->active_mode & MODE_TARGET) + fc_tgt_it_nexus_destroy(shost, (unsigned long)rport); +} + +static struct fc4_template fcp_fc4_template = { + .fc4_init_add = fcp_init_add, + .fc4_init_del = fcp_init_del, + + .fc4_targ_add = fcp_targ_add, + .fc4_targ_del = fcp_targ_del, + + .fc4_bsg_goose_queue = fc_bsg_goose_queue, + .fc4_targ_block = fcp_targ_block, + .fc4_targ_unblock = fcp_targ_unblock, + .fc4_targ_terminate_io = fcp_targ_terminate_io, + .fc4_starget_delete = fcp_starget_delete, + .fc4_queue_starget_delete = fcp_queue_starget_delete, + .fc4_targ_queue_scan = fcp_targ_queue_scan, + .fc4_init_scsi_flush_work = fcp_init_scsi_flush_work, + .fc4_targ_rolechg = fcp_targ_rolechg, + .fc4_targ_final_delete = fcp_targ_final_delete, +}; + +static __init int fcp_transport_init(void) +{ + return register_fc4(&fcp_fc4_template); +} + +static void __exit fcp_transport_exit(void) +{ + unregister_fc4(); +} + +/* Original Author: Martin Hicks */ +/* Major Updates by: James Smart */ +MODULE_AUTHOR("Robert Love"); +MODULE_DESCRIPTION("FCP Transport Attributes"); +MODULE_LICENSE("GPL"); + +module_init(fcp_transport_init); +module_exit(fcp_transport_exit); diff --git a/include/scsi/scsi_transport_fcp.h b/include/scsi/scsi_transport_fcp.h new file mode 100644 index 0000000..7b41e73 --- /dev/null +++ b/include/scsi/scsi_transport_fcp.h @@ -0,0 +1,325 @@ +/* + * Copyright(c) 2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _FCP_H_ +#define _FCP_H_ + +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> + +#include <scsi/scsi_transport_fcp.h> +#include <fc/fc.h> + +/* + * FC SCSI Target Attributes + * + * The SCSI Target is considered an extention of a remote port (as + * a remote port can be more than a SCSI Target). Within the scsi + * subsystem, we leave the Target as a separate entity. Doing so + * provides backward compatibility with prior FC transport api's, + * and lets remote ports be handled entirely within the FC transport + * and independently from the scsi subsystem. The drawback is that + * some data will be duplicated. + */ + +struct fc_starget_attrs { /* aka fc_target_attrs */ + /* Dynamic Attributes */ + u64 node_name; + u64 port_name; + u32 port_id; +}; + +#define fc_starget_node_name(x) \ + (((struct fc_starget_attrs *)&(x)->starget_data)->node_name) +#define fc_starget_port_name(x) \ + (((struct fc_starget_attrs *)&(x)->starget_data)->port_name) +#define fc_starget_port_id(x) \ + (((struct fc_starget_attrs *)&(x)->starget_data)->port_id) + +/* + * FC Local Port (Host) Statistics + */ + +/* FC Statistics - Following FC HBAAPI v2.0 guidelines */ +struct fcpinit_statistics { + /* port statistics */ + u64 seconds_since_last_reset; + u64 tx_frames; + u64 tx_words; + u64 rx_frames; + u64 rx_words; + u64 lip_count; + u64 nos_count; + u64 error_frames; + u64 dumped_frames; + u64 link_failure_count; + u64 loss_of_sync_count; + u64 loss_of_signal_count; + u64 prim_seq_protocol_err_count; + u64 invalid_tx_word_count; + u64 invalid_crc_count; + + /* fc4 statistics (only FCP supported currently) */ + u64 fcp_input_requests; + u64 fcp_output_requests; + u64 fcp_control_requests; + u64 fcp_input_megabytes; + u64 fcp_output_megabytes; +}; + +/* + * FC Event Codes - Polled and Async, following FC HBAAPI v2.0 guidelines + */ + +/* + * fcpinit_event_code: If you alter this, you also need to alter + * scsi_transport_fc.c (for the ascii descriptions). + */ +enum fcpinit_event_code { + FCH_EVT_LIP = 0x1, + FCH_EVT_LINKUP = 0x2, + FCH_EVT_LINKDOWN = 0x3, + FCH_EVT_LIPRESET = 0x4, + FCH_EVT_RSCN = 0x5, + FCH_EVT_ADAPTER_CHANGE = 0x103, + FCH_EVT_PORT_UNKNOWN = 0x200, + FCH_EVT_PORT_OFFLINE = 0x201, + FCH_EVT_PORT_ONLINE = 0x202, + FCH_EVT_PORT_FABRIC = 0x204, + FCH_EVT_LINK_UNKNOWN = 0x500, + FCH_EVT_VENDOR_UNIQUE = 0xffff, +}; + +#define FC_SYMBOLIC_NAME_SIZE 256 + +struct fc_bsg_buffer { + unsigned int payload_len; + int sg_cnt; + struct scatterlist *sg_list; +}; + +/* Values for fc_bsg_job->state_flags (bitflags) */ +#define FC_RQST_STATE_INPROGRESS 0 +#define FC_RQST_STATE_DONE 1 + +struct fc_bsg_job { + struct Scsi_Host *shost; + struct fc_rport *rport; + struct device *dev; + struct request *req; + spinlock_t job_lock; + unsigned int state_flags; + unsigned int ref_cnt; + void (*job_done)(struct fc_bsg_job *); + + struct fc_bsg_request *request; + struct fc_bsg_reply *reply; + unsigned int request_len; + unsigned int reply_len; + /* + * On entry : reply_len indicates the buffer size allocated for + * the reply. + * + * Upon completion : the message handler must set reply_len + * to indicates the size of the reply to be returned to the + * caller. + */ + + /* DMA payloads for the request/response */ + struct fc_bsg_buffer request_payload; + struct fc_bsg_buffer reply_payload; + + void *dd_data; /* Used for driver-specific storage */ +}; + +#define FCPTARG_NUM_ATTRS 1 +struct fc_fcptarg { + struct device dev; + struct device_attribute attrs[FCPTARG_NUM_ATTRS]; + struct fc_rport *rport; + struct fc_fcpinit *fcpinit; + struct request_queue *rqst_q; /* bsg support */ + struct work_struct stgt_delete_work; + struct work_struct scan_work; + u32 scsi_target_id; +}; + +#define dev_to_fcptarg(d) \ + container_of((d), struct fc_fcptarg, dev) +#define fcptarg_to_rport(t) \ + ((t)->rport) +#define fcptarg_scsi_target_id(x) \ + ((x)->scsi_target_id) + +#define FCPINIT_NUM_ATTRS 4 +struct fc_fcpinit { + struct device dev; + struct fcp_template *f; + struct device_attribute attrs[FCPINIT_NUM_ATTRS]; + + struct Scsi_Host *shost; + struct fc_fcvport *fcvport; + + u32 next_target_id; + + /* Fixed Attributes */ + u64 permanent_port_name; + + /* Dynamic Attributes */ + enum fc_port_state port_state; + char system_hostname[FC_SYMBOLIC_NAME_SIZE]; + + /* bsg support */ + struct request_queue *rqst_q; +}; + +#define fcpinit_next_target_id(x) \ + ((x)->next_target_id) + +/* The functions by which the transport class and the driver communicate */ +struct fc_function_template { + + /* target-mode drivers' functions */ + int (*tsk_mgmt_response)(struct Scsi_Host *, u64, u64, int); + int (*it_nexus_response)(struct Scsi_Host *, u64, int); + + /* + * The driver sets these to tell the transport class it + * wants the attributes displayed in sysfs. If the show_ flag + * is not set, the attribute will be private to the transport + * class + */ +}; + +struct scsi_transport_template *fc_attach_transport( + struct fc_function_template *); +void fc_release_transport(struct scsi_transport_template *); + +#define fcpinit_permanent_port_name(x) \ + ((x)->permanent_port_name) +#define fcpinit_port_state(x) \ + ((x)->port_state) +#define fcpinit_system_hostname(x) \ + ((x)->system_hostname) + +#define dev_to_fcpinit(d) \ + container_of((d), struct fc_fcpinit, dev) + +#define fcpinit_to_fcvport(x) \ + ((x)->fcvport) + +void fcpinit_post_event(struct Scsi_Host *shost, u32 event_number, + enum fcpinit_event_code event_code, u32 event_data); +void fcpinit_post_vendor_event(struct Scsi_Host *shost, u32 event_number, + u32 data_len, char *data_buf, u64 vendor_id); + + +int scsi_is_fcptarg(const struct device *dev); + +#define starget_to_fcptarg(s) \ + scsi_is_fcptarg(s->dev.parent) ? container_of((s->dev.parent), struct fc_fcptarg, dev) : NULL + +#define rport_to_fcptarg(r) \ + (struct fc_fcptarg *)((r)->fc4_priv) + +#define fcptarg_to_shost(t) \ + container_of(((t)->dev.parent), struct Scsi_Host, shost_gendev) + +#define fcptarg_to_fcpinit(t) \ + ((t)->fcpinit) + +#define fcvport_to_fcpinit(v) \ + (struct fc_fcpinit *)((v)->fc4_priv) + +#define fcpinit_to_shost(i) \ + ((i)->shost) + +/** + * fcpinit_priv() - Return the private data from a local port + */ +static inline void *fcpinit_priv(const struct fc_fcpinit *fcpinit) +{ + return (void *)(fcpinit + 1); +} + +/** + * fc_remote_port_chkready - called to validate the remote port state + * prior to initiating io to the port. + * + * Returns a scsi result code that can be returned by the LLDD. + * + * @rport: remote port to be checked + **/ +static inline int +fc_remote_port_chkready(struct fc_rport *rport) +{ + int result; + + switch (rport->port_state) { + case FC_PORTSTATE_ONLINE: + if (rport->roles & FC_PORT_ROLE_FCP_TARGET) + result = 0; + else if (rport->flags & FC_RPORT_DEVLOSS_PENDING) + result = DID_IMM_RETRY << 16; + else + result = DID_NO_CONNECT << 16; + break; + case FC_PORTSTATE_BLOCKED: + if (rport->flags & FC_RPORT_FAST_FAIL_TIMEDOUT) + result = DID_TRANSPORT_FAILFAST << 16; + else + result = DID_IMM_RETRY << 16; + break; + default: + result = DID_NO_CONNECT << 16; + break; + } + return result; +} + +struct scsi_transport_template *fc_attach_transport( + struct fc_function_template *); +void fc_release_transport(struct scsi_transport_template *); + +struct fcp_template { + int fcpinit_priv_size; + void (*fcp_fcpinit_callback)(struct fc_fcpinit *fcpinit); + struct scsi_host_template shost_template; + + /* Stuff for fcpinit instance */ + void (*get_fcpinit_port_state)(struct fc_fcpinit *); + void (*set_fcpinit_system_hostname)(struct fc_fcpinit *); + + struct fcpinit_statistics * (*get_fcpinit_stats)(struct fc_fcpinit *); + void (*reset_fcpinit_stats)(struct fc_fcpinit *); + + /* bsg support */ + int (*bsg_request)(struct fc_bsg_job *); + int (*bsg_timeout)(struct fc_bsg_job *); + + /* host fixed attributes */ + unsigned long show_fcpinit_permanent_port_name:1; + + /* host dynamic attributes */ + unsigned long show_fcpinit_port_state:1; + unsigned long show_fcpinit_system_hostname:1; + + /* TODO: Is this in the right place? */ + u32 dd_bsg_size; +}; + +#endif /* _FCP_H_ */ -- To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html