From: Robert Love <robert.w.love@intel.com>
To: linux-scsi@vger.kernel.org
Cc: abjoglek@cisco.com, giridhar.malavali@qlogic.com,
james.smart@emulex.com, bprakash@broadcom.com
Subject: [RFC PATCH 2/8] scsi_transport_fcp: Create FC/SCSI interaction layer
Date: Mon, 07 Mar 2011 18:31:54 -0800 [thread overview]
Message-ID: <20110308023154.6205.2717.stgit@localhost6.localdomain6> (raw)
In-Reply-To: <20110308023143.6205.54720.stgit@localhost6.localdomain6>
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: fcp_statistics
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@intel.com>
---
drivers/scsi/Kconfig | 10
drivers/scsi/Makefile | 1
drivers/scsi/scsi_transport_fcp.c | 1571 +++++++++++++++++++++++++++++++++++++
include/scsi/scsi_transport_fcp.h | 307 +++++++
4 files changed, 1889 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 4a1f029..47450a6 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 b57c532..4526c46 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..ca30566
--- /dev/null
+++ b/drivers/scsi/scsi_transport_fcp.c
@@ -0,0 +1,1571 @@
+/*
+ * Copyright(c) 2011 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"
+
+static struct fc4_template fcp_fc4_template;
+
+static int fcp_bsg_hostadd(struct Scsi_Host *);
+static void fcp_bsg_remove(struct request_queue *q);
+static int fcp_bsg_targadd(struct fcp_init *fcpinit,
+ struct fcp_targ *fcptarg);
+static void fcp_bsg_request_handler(struct request_queue *q,
+ struct Scsi_Host *shost,
+ struct fcp_targ *fcptarg,
+ struct device *dev);
+static void fcp_bsg_softirq_done(struct request *rq);
+static enum blk_eh_timer_return fcp_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 fcp_init_event_code value;
+ char *name;
+} fcp_init_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(fcp, init_event_code, fcp_init_event_code,
+ fcp_init_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 fcp_init *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_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 fcp_init_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 fcp_get_stat(stats, offset) \
+ (unsigned long long)*(u64 *)(((u8 *) (stats)) + (offset))
+
+/* Show a given an attribute in the statistics group */
+static ssize_t fcp_stat_show(const struct device *dev, char *buf,
+ unsigned long offset)
+{
+ struct fcp_init *fcpinit = dev_to_fcpinit(dev);
+ struct fcp_init_statistics *stats;
+ ssize_t ret = -ENOENT;
+
+ if (offset > sizeof(struct fcp_init_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",
+ fcp_get_stat(stats, offset));
+ }
+
+ return ret;
+}
+
+/* generate a read-only statistics attribute */
+#define fcp_init_statistic(name) \
+ static ssize_t show_fcstat_##name(struct device *cd, \
+ struct device_attribute *attr, \
+ char *buf) \
+ { \
+ return fcp_stat_show(cd, buf, \
+ offsetof(struct fcp_init_statistics, \
+ name)); \
+ } \
+ static FC_DEVICE_ATTR(fcpinit, name, S_IRUGO, show_fcstat_##name, NULL)
+
+fcp_init_statistic(seconds_since_last_reset);
+fcp_init_statistic(tx_frames);
+fcp_init_statistic(tx_words);
+fcp_init_statistic(rx_frames);
+fcp_init_statistic(rx_words);
+fcp_init_statistic(lip_count);
+fcp_init_statistic(nos_count);
+fcp_init_statistic(error_frames);
+fcp_init_statistic(dumped_frames);
+fcp_init_statistic(link_failure_count);
+fcp_init_statistic(loss_of_sync_count);
+fcp_init_statistic(loss_of_signal_count);
+fcp_init_statistic(prim_seq_protocol_err_count);
+fcp_init_statistic(invalid_tx_word_count);
+fcp_init_statistic(invalid_crc_count);
+fcp_init_statistic(fcp_input_requests);
+fcp_init_statistic(fcp_output_requests);
+fcp_init_statistic(fcp_control_requests);
+fcp_init_statistic(fcp_input_megabytes);
+fcp_init_statistic(fcp_output_megabytes);
+
+static ssize_t fcp_reset_statistics(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fcp_init *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(fcpinit, reset_statistics, S_IWUSR, NULL,
+ fcp_reset_statistics);
+
+static struct attribute *fcp_statistics_attrs[] = {
+ &device_attr_fcpinit_seconds_since_last_reset.attr,
+ &device_attr_fcpinit_tx_frames.attr,
+ &device_attr_fcpinit_tx_words.attr,
+ &device_attr_fcpinit_rx_frames.attr,
+ &device_attr_fcpinit_rx_words.attr,
+ &device_attr_fcpinit_lip_count.attr,
+ &device_attr_fcpinit_nos_count.attr,
+ &device_attr_fcpinit_error_frames.attr,
+ &device_attr_fcpinit_dumped_frames.attr,
+ &device_attr_fcpinit_link_failure_count.attr,
+ &device_attr_fcpinit_loss_of_sync_count.attr,
+ &device_attr_fcpinit_loss_of_signal_count.attr,
+ &device_attr_fcpinit_prim_seq_protocol_err_count.attr,
+ &device_attr_fcpinit_invalid_tx_word_count.attr,
+ &device_attr_fcpinit_invalid_crc_count.attr,
+ &device_attr_fcpinit_fcp_input_requests.attr,
+ &device_attr_fcpinit_fcp_output_requests.attr,
+ &device_attr_fcpinit_fcp_control_requests.attr,
+ &device_attr_fcpinit_fcp_input_megabytes.attr,
+ &device_attr_fcpinit_fcp_output_megabytes.attr,
+ &device_attr_fcpinit_reset_statistics.attr,
+ NULL
+};
+
+static struct attribute_group fcp_statistics_group = {
+ .name = "statistics",
+ .attrs = fcp_statistics_attrs,
+};
+
+#define fcp_targ_rd_attr(field, format_string, sz) \
+ fc_show_function(fcp, targ, field, format_string, sz, ) \
+ static FC_DEVICE_ATTR(fcptarg, field, S_IRUGO, \
+ show_targ_##field, NULL)
+
+fcp_targ_rd_attr(scsi_target_id, "%d\n", 20);
+
+/**
+ * fcp_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 fcp_timed_out(struct scsi_cmnd *scmd)
+{
+ struct scsi_target *starget = scsi_target(scmd->device);
+ struct fcp_targ *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 fcp_user_scan_tgt(struct Scsi_Host *shost, uint channel,
+ uint id, uint lun)
+{
+ struct fcp_init *fcpinit = shost_priv(shost);
+ struct fc_vport *fcvport = fcpinit_to_fcvport(fcpinit);
+ struct fc_rport *rport;
+ struct fcp_targ *fcptarg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&fcvport->lock, flags);
+
+ list_for_each_entry(rport, &fc_vport_rports(fcvport), peers) {
+ if (!(rport->roles & FC_PORT_ROLE_FCP_TARGET))
+ continue;
+
+ 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(&fcptarg->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 fcp_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++)
+ fcp_user_scan_tgt(shost, chlo, tgtlo, lun);
+
+ return 0;
+}
+
+static int fcp_tsk_mgmt_response(struct Scsi_Host *shost, u64 nexus, u64 tm_id,
+ int result)
+{
+ /*
+ * This was previously calling the fc_internal template's
+ * callback, why does the Scsi_Host have one too? Is there
+ * a problem with calling the Scsi_Host's callback directly?
+ */
+ return shost->transportt->tsk_mgmt_response(shost, nexus,
+ tm_id, result);
+}
+
+static int fcp_it_nexus_response(struct Scsi_Host *shost, u64 nexus, int result)
+{
+ /*
+ * This was previously calling the fc_internal template's
+ * callback, why does the Scsi_Host have one too? Is there
+ * a problem with calling the Scsi_Host's callback directly?
+ */
+ return shost->transportt->it_nexus_response(shost, nexus, result);
+}
+
+/**
+ * fcp_init_release() - Put the Scsi_Host reference to free the fcpinit
+ * @dev: The fcpinit's embedded device
+ */
+static void fcp_init_release(struct device *dev)
+{
+ struct fcp_init *fcpinit = dev_to_fcpinit(dev);
+ struct Scsi_Host *shost = fcpinit_to_shost(fcpinit);
+
+ scsi_host_put(shost);
+}
+
+/**
+ * fcp_init_del() - Delete a init instance
+ * @fcvport: The FC vport associated with the fcpinit to be deleted
+ *
+ * Determine the fcpinit from the FC vport. Flush any work on the
+ * associated Scsi_Host, remove the fcpinit and Scsi_Host from
+ * sysfs, notify the LLD and drop the self-refrence to the fcpinit.
+ */
+static int fcp_init_remove(struct device *dev)
+{
+ struct fc_vport *fcvport = dev_to_fcvport(dev);
+ struct fcp_init *fcpinit = fcvport_to_fcpinit(fcvport);
+ struct Scsi_Host *shost = fcpinit_to_shost(fcpinit);
+
+ fcp_bsg_remove(fcpinit->rqst_q);
+
+ /* flush all scan work items */
+ scsi_flush_work(shost);
+
+ device_del(&fcpinit->dev);
+ put_device(fcpinit->dev.parent);
+ fcpinit->dev.parent = NULL;
+
+ /*
+ * Detach from the scsi-ml. This frees the shost,
+ * fcpinit and any LLD private data.
+ */
+ scsi_remove_host(shost);
+
+ if (fcpinit->f->fcp_fcpinit_del)
+ fcpinit->f->fcp_fcpinit_del(fcpinit);
+
+ put_device(&fcpinit->dev); /* Self Reference from device_initialize */
+
+ return 0;
+}
+
+static struct scsi_transport_template fcpinit_scsi_transport_template = {
+ /* Use the shost workq for scsi scanning */
+ .create_work_queue = 1,
+
+ .eh_timed_out = fcp_timed_out,
+
+ .user_scan = fcp_user_scan,
+
+ /* target-mode drivers' functions */
+ .tsk_mgmt_response = fcp_tsk_mgmt_response,
+ .it_nexus_response = fcp_it_nexus_response,
+};
+
+/**
+ * fcp_init_probe() - Create and add a fcpinit device
+ * @dev: The FC vport
+ *
+ * Allocate the fcpinit structure with the Scsi_Host. Add
+ * it and the Scsi_Host to sysfs and notify the LLD.
+ */
+static int fcp_init_probe(struct device *dev)
+{
+ struct fc_vport *fcvport = dev_to_fcvport(dev);
+ struct fc_fabric *fcfabric = fcvport_to_fcfabric(fcvport);
+ struct fc_port *fcport = fcfabric_to_fcport(fcfabric);
+ struct fcp_template *f = fcport->fc4_f;
+ struct Scsi_Host *shost;
+ struct fcp_init *fcpinit;
+ int error = 0;
+
+ int priv_size = sizeof(struct fcp_init) + f->fcpinit_priv_size;
+ shost = scsi_host_alloc(&f->shost_template, priv_size);
+ if (unlikely(!shost)) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ shost->transportt = &fcpinit_scsi_transport_template;
+ fcvport->fc4_f = &fcp_fc4_template;
+
+ fcpinit = shost_priv(shost);
+ fcpinit->f = f;
+
+ dev_set_drvdata(dev, fcpinit);
+ fcpinit->fcvport = fcvport;
+ fcpinit->shost = shost;
+ fcpinit->next_target_id = 0;
+
+ device_initialize(&fcpinit->dev); /* Requires a put_device */
+ fcpinit->dev.release = fcp_init_release;
+ dev_set_name(&fcpinit->dev, "fcpinit_%d", shost->host_no);
+
+ /*
+ * Need to call back to the LLD with priv memory
+ */
+ if (fcpinit->f->fcp_fcpinit_add)
+ fcpinit->f->fcp_fcpinit_add(fcpinit);
+
+ fcpinit->dev.parent = get_device(&fcvport->dev);
+
+ error = device_add(&fcpinit->dev);
+ if (error)
+ goto out_del;
+
+ if (fcpinit->f->get_fcpinit_stats)
+ error = sysfs_update_group(&fcpinit->dev.kobj,
+ &fcp_statistics_group);
+
+ /* add the new host to the SCSI-ml */
+ error = scsi_add_host(shost, &fcpinit->dev);
+ if (error)
+ goto out_del_dev;
+
+ fcp_bsg_hostadd(shost);
+ /* ignore any bsg add error - we just can't do sgio */
+
+ dev_printk(KERN_NOTICE, fcpinit->dev.parent,
+ "%s created via shost%d channel %d\n",
+ dev_name(&fcpinit->dev), shost->host_no,
+ fcvport->channel);
+
+ return 0;
+
+out_del_dev:
+ device_del(&fcpinit->dev);
+out_del:
+ put_device(&fcvport->dev);
+ scsi_host_put(shost);
+out:
+ return error;
+}
+
+/**
+ * fcp_init_post_event() - called to post an even on an fcp_init.
+ * @shost: host the event occurred on
+ * @event_number: fc event number obtained from get_fc_event_number()
+ * @event_code: fcp_init event being posted
+ * @event_data: 32bits of data for the event being posted
+ *
+ * Notes:
+ * This routine assumes no locks are held on entry.
+ */
+void fcp_init_post_event(struct Scsi_Host *shost, u32 event_number,
+ enum fcp_init_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_fcp_init_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(fcp_init_post_event);
+
+/**
+ * fcp_init_post_vendor_event() - called to post a vendor unique event on an fcp_init
+ * @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 fcp_init_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(fcp_init_post_vendor_event);
+
+/**
+ * fcp_bsg_host_handler() - handler for bsg requests for a fc host
+ * @q: fc host request queue
+ */
+static void fcp_bsg_host_handler(struct request_queue *q)
+{
+ struct Scsi_Host *shost = q->queuedata;
+
+ fcp_bsg_request_handler(q, shost, NULL, &shost->shost_gendev);
+}
+
+/**
+ * fcp_bsg_hostadd() - Create and add the bsg hooks so we can receive requests
+ * @shost: shost for fcp_init
+ */
+static int fcp_bsg_hostadd(struct Scsi_Host *shost)
+{
+ struct fcp_init *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, fcp_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, fcp_bsg_softirq_done);
+ blk_queue_rq_timed_out(q, fcp_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;
+}
+
+/**
+ * fcp_bsg_remove() - Deletes the bsg hooks on fchosts/fcptargs
+ * @q: the request_queue that is to be torn down.
+ */
+void fcp_bsg_remove(struct request_queue *q)
+{
+ if (q) {
+ bsg_unregister_queue(q);
+ blk_cleanup_queue(q);
+ }
+}
+
+/**
+ * fcp_targ_dev_release() - Free the fcptarg's memory
+ * @dev: The fcptarg's embedded device
+ *
+ * Called when the last fcptarg reference is released.
+ */
+static void fcp_targ_dev_release(struct device *dev)
+{
+ struct fcp_targ *fcptarg = dev_to_fcptarg(dev);
+ kfree(fcptarg);
+}
+
+/**
+ * scsi_is_fcptarg() - Determines if a device is a fcptarg
+ * @dev: The device to check
+ */
+int scsi_is_fcptarg(const struct device *dev)
+{
+ return dev->release == fcp_targ_dev_release;
+}
+EXPORT_SYMBOL(scsi_is_fcptarg);
+
+/**
+ * fcp_targ_probe() - Create a SCSI-FCP targ device
+ * @dev: The embedded device in the FC rport
+ *
+ * Called from the FC layer when an rport is added to
+ * a SCSI-FCP capable initiator. Attaches the FC rport
+ * to the scsi_target device.
+ */
+static int fcp_targ_probe(struct device *dev)
+{
+ struct fc_rport *rport = dev_to_fcrport(dev);
+ struct fc_vport *fcvport = rport_to_fcvport(rport);
+ struct fcp_init *fcpinit = fcvport_to_fcpinit(fcvport);
+ struct Scsi_Host *shost = fcpinit_to_shost(fcpinit);
+ struct fcp_targ *fcptarg;
+ int error = 0;
+ int count = 0;
+
+ fcptarg = kzalloc(sizeof(struct fcp_targ), GFP_KERNEL);
+ if (unlikely(!fcptarg)) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ dev_set_drvdata(dev, 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;
+
+ device_initialize(&fcptarg->dev); /* takes self reference */
+ fcptarg->dev.parent = get_device(&shost->shost_gendev);
+
+ fcptarg->dev.release = fcp_targ_dev_release;
+ dev_set_name(&fcptarg->dev, "fcptarg-%d:%d-%d",
+ shost->host_no, rport->channel, rport->number);
+
+ error = device_add(&fcptarg->dev);
+ if (error)
+ goto out_del;
+
+ fcp_bsg_targadd(fcpinit, fcptarg);
+ /* ignore any bsg add error - we just can't do sgio */
+
+ FC_SETUP_ATTR_RD_NS(fcptarg, scsi_target_id);
+
+ BUG_ON(count > FCP_TARG_NUM_ATTRS);
+ FC_CREATE_ATTRS(fcptarg, count);
+
+ if (error || count != 0) {
+ error = -EINVAL;
+ goto out_del_dev;
+ }
+
+ return 0;
+
+out_del_dev:
+ put_device(&shost->shost_gendev);
+out_del:
+ kfree(fcptarg);
+out:
+ return error;
+}
+
+/**
+ * fcp_targ_remove() - Delete a fcptarg from sysfs
+ * @dev: The FC rport associated with the fcptarg
+ */
+static int fcp_targ_remove(struct device *dev)
+{
+ struct fc_rport *rport = dev_to_fcrport(dev);
+ struct fcp_targ *fcptarg = rport_to_fcptarg(rport);
+
+ fcp_bsg_remove(fcptarg->rqst_q);
+
+ /* Delete SCSI target and sdevs */
+ if (fcptarg->scsi_target_id != -1)
+ fcp_starget_delete(rport);
+
+ device_del(&fcptarg->dev);
+ put_device(fcptarg->dev.parent);
+ put_device(&fcptarg->dev);
+
+ return 0;
+}
+
+/**
+ * fcp_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.
+ */
+int fcp_block_scsi_eh(struct scsi_cmnd *cmnd)
+{
+ struct fcp_targ *fcptarg = starget_to_fcptarg(
+ scsi_target(cmnd->device));
+ struct fc_rport *rport = fcptarg->rport;
+ struct fcp_init *fcpinit = fcptarg->fcpinit;
+ struct fc_vport *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);
+
+ if (rport->flags & FC_RPORT_FAST_FAIL_TIMEDOUT)
+ return FAST_IO_FAIL;
+
+ return 0;
+}
+EXPORT_SYMBOL(fcp_block_scsi_eh);
+
+/*
+ * BSG support
+ */
+
+/**
+ * fcp_destroy_bsgjob - routine to teardown/delete a fc bsg job
+ * @job: fcp_bsg_job that is to be torn down
+ */
+static void fcp_destroy_bsgjob(struct fcp_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);
+}
+
+/**
+ * fcp_bsg_jobdone - completion routine for bsg requests that the LLD has
+ * completed
+ * @job: fcp_bsg_job that is complete
+ */
+static void fcp_bsg_jobdone(struct fcp_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);
+}
+
+/**
+ * fcp_bsg_softirq_done - softirq done routine for destroying the bsg requests
+ * @rq: BSG request that holds the job to be destroyed
+ */
+static void fcp_bsg_softirq_done(struct request *rq)
+{
+ struct fcp_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);
+ fcp_destroy_bsgjob(job);
+}
+
+/**
+ * fcp_bsg_job_timeout - handler for when a bsg request timesout
+ * @req: request that timed out
+ */
+static enum blk_eh_timer_return fcp_bsg_job_timeout(struct request *req)
+{
+ struct fcp_bsg_job *job = (void *) req->special;
+ struct Scsi_Host *shost = job->shost;
+ struct fcp_init *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 fcp_bsg_map_buffer(struct fcp_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;
+}
+
+
+/**
+ * fcp_req_to_bsgjob - Allocate/create the fcp_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 fcp_req_to_bsgjob(struct Scsi_Host *shost, struct fc_rport *rport,
+ struct request *req)
+{
+ struct fcp_init *fcpinit = shost_priv(shost);
+ struct request *rsp = req->next_rq;
+ struct fcp_bsg_job *job;
+ int ret;
+
+ BUG_ON(req->special);
+
+ job = kzalloc(sizeof(struct fcp_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 = fcp_bsg_map_buffer(&job->request_payload, req);
+ if (ret)
+ goto failjob_rls_job;
+ }
+ if (rsp && rsp->bio) {
+ ret = fcp_bsg_map_buffer(&job->reply_payload, rsp);
+ if (ret)
+ goto failjob_rls_rqst_payload;
+ }
+ job->job_done = fcp_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 */
+};
+
+/**
+ * fcp_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
+fcp_bsg_host_dispatch(struct request_queue *q, struct Scsi_Host *shost,
+ struct fcp_bsg_job *job)
+{
+ struct fcp_init *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);
+ fcp_bsg_jobdone(job);
+ return FC_DISPATCH_UNLOCKED;
+}
+
+
+/*
+ * fcp_bsg_goose_queue - restart rport queue in case it was stopped
+ * @rport: rport to be restarted
+ */
+static void fcp_bsg_goose_queue(struct fc_rport *rport)
+{
+ struct fcp_targ *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);
+}
+
+/**
+ * fcp_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
+fcp_bsg_rport_dispatch(struct request_queue *q, struct Scsi_Host *shost,
+ struct fc_rport *rport, struct fcp_bsg_job *job)
+{
+ struct fcp_init *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);
+ fcp_bsg_jobdone(job);
+ return FC_DISPATCH_UNLOCKED;
+}
+
+
+/**
+ * fcp_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 fcp_bsg_request_handler(struct request_queue *q,
+ struct Scsi_Host *shost,
+ struct fcp_targ *fcptarg,
+ struct device *dev)
+{
+ struct fc_rport *rport = fcptarg->rport;
+ struct request *req;
+ struct fcp_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 = fcp_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);
+ fcp_bsg_jobdone(job);
+ spin_lock_irq(q->queue_lock);
+ continue;
+ }
+
+ /* the dispatch routines will unlock the queue_lock */
+ if (rport)
+ ret = fcp_bsg_rport_dispatch(q, shost, rport, job);
+ else
+ ret = fcp_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);
+}
+
+/**
+ * fcp_bsg_targ_handler - handler for bsg requests for a fc rport
+ * @q: rport request queue
+ */
+static void fcp_bsg_targ_handler(struct request_queue *q)
+{
+ struct fcp_targ *fcptarg = q->queuedata;
+ struct Scsi_Host *shost = fcptarg_to_shost(fcptarg);
+
+ fcp_bsg_request_handler(q, shost, fcptarg, &fcptarg->dev);
+}
+
+/**
+ * fcp_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 fcp_bsg_targadd(struct fcp_init *fcpinit,
+ struct fcp_targ *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, fcp_bsg_targ_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, fcp_bsg_softirq_done);
+ blk_queue_rq_timed_out(q, fcp_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 fcp_targ *fcptarg = rport_to_fcptarg(rport);
+ scsi_target_block(&fcptarg->dev);
+}
+
+static void fcp_targ_unblock(struct fc_rport *rport)
+{
+ struct fcp_targ *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 fcp_targ *fcptarg = rport_to_fcptarg(rport);
+ fcp_targ_terminate_io(rport);
+ scsi_remove_target(&fcptarg->dev);
+}
+
+/**
+ * fcp_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 fcp_targ *fcptarg =
+ container_of(work, struct fcp_targ, 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 fcp_targ *fcptarg = rport_to_fcptarg(rport);
+ struct fc_vport *fcvport = rport_to_fcvport(rport);
+ fc_vport_queue_work(fcvport, &fcptarg->stgt_delete_work);
+}
+
+static void fcp_targ_queue_scan(struct fc_rport *rport)
+{
+ struct fcp_targ *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 fcp_targ *fcptarg =
+ container_of(work, struct fcp_targ, scan_work);
+ struct fc_rport *rport;
+ struct fc_vport *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_vport *fcvport)
+{
+ struct fcp_init *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_vport *fcvport = rport_to_fcvport(rport);
+ struct fcp_init *fcpinit = fcvport_to_fcpinit(fcvport);
+ struct fcp_targ *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;
+}
+
+/**
+ * fcp_targ_final_delete() - Final delete routine for the fcp_targ
+ * @rport: The FC rport associated with the fcp_targ
+ */
+static void fcp_targ_final_delete(struct fc_rport *rport)
+{
+ struct fc_vport *fcvport = rport_to_fcvport(rport);
+ struct fcp_init *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_bsg_goose_queue = fcp_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,
+};
+
+struct device_driver fcp_targ_drv = {
+ .name = "fcp_target",
+ .bus = &fc_rport_bus_type,
+ .probe = fcp_targ_probe,
+ .remove = fcp_targ_remove,
+};
+
+struct device_driver fcp_init_drv = {
+ .name = "fcp_initiator",
+ .bus = &fc_vport_bus_type,
+ .probe = fcp_init_probe,
+ .remove = fcp_init_remove,
+};
+
+/**
+ * fcp_transport_init() - Initialize the FCP transport
+ *
+ * Registers the FCP transport with the FC module.
+ */
+static __init int fcp_transport_init(void)
+{
+ int error;
+ error = driver_register(&fcp_init_drv);
+ if (error)
+ goto err;
+
+ error = driver_register(&fcp_targ_drv);
+ if (error)
+ goto unreg_init;
+
+ return 0;
+
+unreg_init:
+ driver_unregister(&fcp_targ_drv);
+err:
+ return error;
+}
+
+/**
+ * fcp_transport_exit() - Tear down the FCP transport
+ *
+ * Unregisters the FCP transport with the FC module.
+ */
+static void __exit fcp_transport_exit(void)
+{
+ driver_unregister(&fcp_targ_drv);
+ driver_unregister(&fcp_init_drv);
+}
+
+/* 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..ca13b48
--- /dev/null
+++ b/include/scsi/scsi_transport_fcp.h
@@ -0,0 +1,307 @@
+/*
+ * Copyright(c) 2011 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 fcp_init_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
+ */
+
+/*
+ * fcp_init_event_code: If you alter this, you also need to alter
+ * scsi_transport_fc.c (for the ascii descriptions).
+ */
+enum fcp_init_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 fcp_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 fcp_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 fcp_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 fcp_bsg_buffer request_payload;
+ struct fcp_bsg_buffer reply_payload;
+
+ void *dd_data; /* Used for driver-specific storage */
+};
+
+#define FCP_TARG_NUM_ATTRS 1
+struct fcp_targ {
+ struct device dev;
+ struct device_attribute attrs[FCP_TARG_NUM_ATTRS];
+ struct fc_rport *rport;
+ struct fcp_init *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 fcp_targ, dev)
+#define fcptarg_to_rport(t) \
+ ((t)->rport)
+#define fcp_targ_scsi_target_id(x) \
+ ((x)->scsi_target_id)
+
+struct fcp_init {
+ struct device dev;
+ struct fcp_template *f;
+
+ struct Scsi_Host *shost;
+ struct fc_vport *fcvport;
+
+ struct fcp_init_statistics fcp_stats;
+
+ u32 next_target_id;
+
+ /* bsg support */
+ struct request_queue *rqst_q;
+};
+
+#define fcpinit_to_fcvport(x) \
+ ((x)->fcvport)
+
+#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 dev_to_fcpinit(d) \
+ container_of((d), struct fcp_init, dev)
+
+#define fcpinit_to_fcvport(x) \
+ ((x)->fcvport)
+
+void fcpinit_post_event(struct Scsi_Host *shost, u32 event_number,
+ enum fcp_init_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);
+int fcp_block_scsi_eh(struct scsi_cmnd *cmnd);
+
+#define starget_to_fcptarg(s) \
+ scsi_is_fcptarg(s->dev.parent) ? \
+ container_of((s->dev.parent), \
+ struct fcp_targ, dev) : NULL
+
+#define rport_to_fcptarg(r) \
+ ((struct fcp_targ *)(dev_get_drvdata(&(r)->dev)))
+
+#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 fcp_init *)(dev_get_drvdata(&(v)->dev)))
+
+#define fcpinit_to_shost(i) \
+ ((i)->shost)
+
+/**
+ * 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;
+}
+
+/**
+ * fcp_init_priv() - Return the private data from a local port
+ */
+static inline void *fcp_init_priv(const struct fcp_init *fcpinit)
+{
+ return (void *)(fcpinit + 1);
+}
+
+struct fcp_template {
+ int fcpinit_priv_size;
+ void (*fcp_fcpinit_add)(struct fcp_init *fcpinit);
+ void (*fcp_fcpinit_del)(struct fcp_init *fcpinit);
+ struct scsi_host_template shost_template;
+
+ struct fcp_init_statistics * (*get_fcpinit_stats)(struct fcp_init *);
+ void (*reset_fcpinit_stats)(struct fcp_init *);
+
+ /* bsg support */
+ int (*bsg_request)(struct fcp_bsg_job *);
+ int (*bsg_timeout)(struct fcp_bsg_job *);
+
+ /* host dynamic attributes */
+ unsigned long show_fcpinit_port_state:1;
+
+ /* TODO: Is this in the right place? */
+ u32 dd_bsg_size;
+};
+
+#endif /* _FCP_H_ */
next prev parent reply other threads:[~2011-03-08 2:31 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-03-08 2:31 [RFC PATCH 0/8] FC Sysfs and FCoE attributes Robert Love
2011-03-08 2:31 ` [RFC PATCH 1/8] fc: Create FC sybsystem Robert Love
2011-03-08 2:31 ` Robert Love [this message]
2011-03-08 2:31 ` [RFC PATCH 3/8] libfc, libfcoe, fcoe: Make use of FC subsystem Robert Love
2011-03-08 2:32 ` [RFC PATCH 4/8] fc: Add FCoE attributes to FC sysfs Robert Love
2011-03-08 2:32 ` [RFC PATCH 5/8] libfcoe, fcoe: Use FCoE attributes Robert Love
2011-03-08 2:32 ` [RFC PATCH 6/8] fnic: Convert to new FC Sysfs infrastructure Robert Love
2011-03-08 2:32 ` [RFC PATCH 7/8] fc: Add a hbaapi_lib attribute to the fcport structure Robert Love
2011-03-08 2:32 ` [RFC PATCH 8/8] Documentation: Add fc_sysfs document Robert Love
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20110308023154.6205.2717.stgit@localhost6.localdomain6 \
--to=robert.w.love@intel.com \
--cc=abjoglek@cisco.com \
--cc=bprakash@broadcom.com \
--cc=giridhar.malavali@qlogic.com \
--cc=james.smart@emulex.com \
--cc=linux-scsi@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox