diff options
author | Robert Love <robert.w.love@intel.com> | 2008-12-09 15:10:24 -0800 |
---|---|---|
committer | James Bottomley <James.Bottomley@HansenPartnership.com> | 2008-12-29 11:24:33 -0600 |
commit | 85b4aa4926a50210b683ac89326e338e7d131211 (patch) | |
tree | 127b6586573194f2d37b5a298e12c8b7d24a6fac /drivers/scsi/fcoe | |
parent | 42e9a92fe6a9095bd68a379aaec7ad2be0337f7a (diff) |
[SCSI] fcoe: Fibre Channel over Ethernet
Encapsulation protocol for running Fibre Channel over Ethernet interfaces.
Creates virtual Fibre Channel host adapters using libfc.
This layer is the LLD to the scsi-ml. It allocates the Scsi_Host, utilizes
libfc for Fibre Channel protocol processing and interacts with netdev to
send/receive Ethernet packets.
Signed-off-by: Robert Love <robert.w.love@intel.com>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Diffstat (limited to 'drivers/scsi/fcoe')
-rw-r--r-- | drivers/scsi/fcoe/Makefile | 8 | ||||
-rw-r--r-- | drivers/scsi/fcoe/fc_transport_fcoe.c | 446 | ||||
-rw-r--r-- | drivers/scsi/fcoe/fcoe_sw.c | 494 | ||||
-rw-r--r-- | drivers/scsi/fcoe/libfcoe.c | 1510 |
4 files changed, 2458 insertions, 0 deletions
diff --git a/drivers/scsi/fcoe/Makefile b/drivers/scsi/fcoe/Makefile new file mode 100644 index 000000000000..b78da06d7c0e --- /dev/null +++ b/drivers/scsi/fcoe/Makefile @@ -0,0 +1,8 @@ +# $Id: Makefile + +obj-$(CONFIG_FCOE) += fcoe.o + +fcoe-y := \ + libfcoe.o \ + fcoe_sw.o \ + fc_transport_fcoe.o diff --git a/drivers/scsi/fcoe/fc_transport_fcoe.c b/drivers/scsi/fcoe/fc_transport_fcoe.c new file mode 100644 index 000000000000..bf7fe6fc0820 --- /dev/null +++ b/drivers/scsi/fcoe/fc_transport_fcoe.c @@ -0,0 +1,446 @@ +/* + * Copyright(c) 2007 - 2008 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. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/pci.h> +#include <scsi/libfcoe.h> +#include <scsi/fc_transport_fcoe.h> + +/* internal fcoe transport */ +struct fcoe_transport_internal { + struct fcoe_transport *t; + struct net_device *netdev; + struct list_head list; +}; + +/* fcoe transports list and its lock */ +static LIST_HEAD(fcoe_transports); +static DEFINE_MUTEX(fcoe_transports_lock); + +/** + * fcoe_transport_default - returns ptr to the default transport fcoe_sw + **/ +struct fcoe_transport *fcoe_transport_default(void) +{ + return &fcoe_sw_transport; +} + +/** + * fcoe_transport_to_pcidev - get the pci dev from a netdev + * @netdev: the netdev that pci dev will be retrived from + * + * Returns: NULL or the corrsponding pci_dev + **/ +struct pci_dev *fcoe_transport_pcidev(const struct net_device *netdev) +{ + if (!netdev->dev.parent) + return NULL; + return to_pci_dev(netdev->dev.parent); +} + +/** + * fcoe_transport_device_lookup - find out netdev is managed by the + * transport + * assign a transport to a device + * @netdev: the netdev the transport to be attached to + * + * This will look for existing offload driver, if not found, it falls back to + * the default sw hba (fcoe_sw) as its fcoe transport. + * + * Returns: 0 for success + **/ +static struct fcoe_transport_internal *fcoe_transport_device_lookup( + struct fcoe_transport *t, struct net_device *netdev) +{ + struct fcoe_transport_internal *ti; + + /* assign the transpor to this device */ + mutex_lock(&t->devlock); + list_for_each_entry(ti, &t->devlist, list) { + if (ti->netdev == netdev) { + mutex_unlock(&t->devlock); + return ti; + } + } + mutex_unlock(&t->devlock); + return NULL; +} +/** + * fcoe_transport_device_add - assign a transport to a device + * @netdev: the netdev the transport to be attached to + * + * This will look for existing offload driver, if not found, it falls back to + * the default sw hba (fcoe_sw) as its fcoe transport. + * + * Returns: 0 for success + **/ +static int fcoe_transport_device_add(struct fcoe_transport *t, + struct net_device *netdev) +{ + struct fcoe_transport_internal *ti; + + ti = fcoe_transport_device_lookup(t, netdev); + if (ti) { + printk(KERN_DEBUG "fcoe_transport_device_add:" + "device %s is already added to transport %s\n", + netdev->name, t->name); + return -EEXIST; + } + /* allocate an internal struct to host the netdev and the list */ + ti = kzalloc(sizeof(*ti), GFP_KERNEL); + if (!ti) + return -ENOMEM; + + ti->t = t; + ti->netdev = netdev; + INIT_LIST_HEAD(&ti->list); + dev_hold(ti->netdev); + + mutex_lock(&t->devlock); + list_add(&ti->list, &t->devlist); + mutex_unlock(&t->devlock); + + printk(KERN_DEBUG "fcoe_transport_device_add:" + "device %s added to transport %s\n", + netdev->name, t->name); + + return 0; +} + +/** + * fcoe_transport_device_remove - remove a device from its transport + * @netdev: the netdev the transport to be attached to + * + * this removes the device from the transport so the given transport will + * not manage this device any more + * + * Returns: 0 for success + **/ +static int fcoe_transport_device_remove(struct fcoe_transport *t, + struct net_device *netdev) +{ + struct fcoe_transport_internal *ti; + + ti = fcoe_transport_device_lookup(t, netdev); + if (!ti) { + printk(KERN_DEBUG "fcoe_transport_device_remove:" + "device %s is not managed by transport %s\n", + netdev->name, t->name); + return -ENODEV; + } + mutex_lock(&t->devlock); + list_del(&ti->list); + mutex_unlock(&t->devlock); + printk(KERN_DEBUG "fcoe_transport_device_remove:" + "device %s removed from transport %s\n", + netdev->name, t->name); + dev_put(ti->netdev); + kfree(ti); + return 0; +} + +/** + * fcoe_transport_device_remove_all - remove all from transport devlist + * + * this removes the device from the transport so the given transport will + * not manage this device any more + * + * Returns: 0 for success + **/ +static void fcoe_transport_device_remove_all(struct fcoe_transport *t) +{ + struct fcoe_transport_internal *ti, *tmp; + + mutex_lock(&t->devlock); + list_for_each_entry_safe(ti, tmp, &t->devlist, list) { + list_del(&ti->list); + kfree(ti); + } + mutex_unlock(&t->devlock); +} + +/** + * fcoe_transport_match - use the bus device match function to match the hw + * @t: the fcoe transport + * @netdev: + * + * This function is used to check if the givne transport wants to manage the + * input netdev. if the transports implements the match function, it will be + * called, o.w. we just compare the pci vendor and device id. + * + * Returns: true for match up + **/ +static bool fcoe_transport_match(struct fcoe_transport *t, + struct net_device *netdev) +{ + /* match transport by vendor and device id */ + struct pci_dev *pci; + + pci = fcoe_transport_pcidev(netdev); + + if (pci) { + printk(KERN_DEBUG "fcoe_transport_match:" + "%s:%x:%x -- %s:%x:%x\n", + t->name, t->vendor, t->device, + netdev->name, pci->vendor, pci->device); + + /* if transport supports match */ + if (t->match) + return t->match(netdev); + + /* else just compare the vendor and device id: pci only */ + return (t->vendor == pci->vendor) && (t->device == pci->device); + } + return false; +} + +/** + * fcoe_transport_lookup - check if the transport is already registered + * @t: the transport to be looked up + * + * This compares the parent device (pci) vendor and device id + * + * Returns: NULL if not found + * + * TODO - return default sw transport if no other transport is found + **/ +static struct fcoe_transport *fcoe_transport_lookup( + struct net_device *netdev) +{ + struct fcoe_transport *t; + + mutex_lock(&fcoe_transports_lock); + list_for_each_entry(t, &fcoe_transports, list) { + if (fcoe_transport_match(t, netdev)) { + mutex_unlock(&fcoe_transports_lock); + return t; + } + } + mutex_unlock(&fcoe_transports_lock); + + printk(KERN_DEBUG "fcoe_transport_lookup:" + "use default transport for %s\n", netdev->name); + return fcoe_transport_default(); +} + +/** + * fcoe_transport_register - adds a fcoe transport to the fcoe transports list + * @t: ptr to the fcoe transport to be added + * + * Returns: 0 for success + **/ +int fcoe_transport_register(struct fcoe_transport *t) +{ + struct fcoe_transport *tt; + + /* TODO - add fcoe_transport specific initialization here */ + mutex_lock(&fcoe_transports_lock); + list_for_each_entry(tt, &fcoe_transports, list) { + if (tt == t) { + mutex_unlock(&fcoe_transports_lock); + return -EEXIST; + } + } + list_add_tail(&t->list, &fcoe_transports); + mutex_unlock(&fcoe_transports_lock); + + mutex_init(&t->devlock); + INIT_LIST_HEAD(&t->devlist); + + printk(KERN_DEBUG "fcoe_transport_register:%s\n", t->name); + + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_transport_register); + +/** + * fcoe_transport_unregister - remove the tranport fro the fcoe transports list + * @t: ptr to the fcoe transport to be removed + * + * Returns: 0 for success + **/ +int fcoe_transport_unregister(struct fcoe_transport *t) +{ + struct fcoe_transport *tt, *tmp; + + mutex_lock(&fcoe_transports_lock); + list_for_each_entry_safe(tt, tmp, &fcoe_transports, list) { + if (tt == t) { + list_del(&t->list); + mutex_unlock(&fcoe_transports_lock); + fcoe_transport_device_remove_all(t); + printk(KERN_DEBUG "fcoe_transport_unregister:%s\n", + t->name); + return 0; + } + } + mutex_unlock(&fcoe_transports_lock); + return -ENODEV; +} +EXPORT_SYMBOL_GPL(fcoe_transport_unregister); + +/* + * fcoe_load_transport_driver - load an offload driver by alias name + * @netdev: the target net device + * + * Requests for an offload driver module as the fcoe transport, if fails, it + * falls back to use the SW HBA (fcoe_sw) as its transport + * + * TODO - + * 1. supports only PCI device + * 2. needs fix for VLAn and bonding + * 3. pure hw fcoe hba may not have netdev + * + * Returns: 0 for success + **/ +int fcoe_load_transport_driver(struct net_device *netdev) +{ + struct pci_dev *pci; + struct device *dev = netdev->dev.parent; + + if (fcoe_transport_lookup(netdev)) { + /* load default transport */ + printk(KERN_DEBUG "fcoe: already loaded transport for %s\n", + netdev->name); + return -EEXIST; + } + + pci = to_pci_dev(dev); + if (dev->bus != &pci_bus_type) { + printk(KERN_DEBUG "fcoe: support noly PCI device\n"); + return -ENODEV; + } + printk(KERN_DEBUG "fcoe: loading driver fcoe-pci-0x%04x-0x%04x\n", + pci->vendor, pci->device); + + return request_module("fcoe-pci-0x%04x-0x%04x", + pci->vendor, pci->device); + +} +EXPORT_SYMBOL_GPL(fcoe_load_transport_driver); + +/** + * fcoe_transport_attach - load transport to fcoe + * @netdev: the netdev the transport to be attached to + * + * This will look for existing offload driver, if not found, it falls back to + * the default sw hba (fcoe_sw) as its fcoe transport. + * + * Returns: 0 for success + **/ +int fcoe_transport_attach(struct net_device *netdev) +{ + struct fcoe_transport *t; + + /* find the corresponding transport */ + t = fcoe_transport_lookup(netdev); + if (!t) { + printk(KERN_DEBUG "fcoe_transport_attach" + ":no transport for %s:use %s\n", + netdev->name, t->name); + return -ENODEV; + } + /* add to the transport */ + if (fcoe_transport_device_add(t, netdev)) { + printk(KERN_DEBUG "fcoe_transport_attach" + ":failed to add %s to tramsport %s\n", + netdev->name, t->name); + return -EIO; + } + /* transport create function */ + if (t->create) + t->create(netdev); + + printk(KERN_DEBUG "fcoe_transport_attach:transport %s for %s\n", + t->name, netdev->name); + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_transport_attach); + +/** + * fcoe_transport_release - unload transport from fcoe + * @netdev: the net device on which fcoe is to be released + * + * Returns: 0 for success + **/ +int fcoe_transport_release(struct net_device *netdev) +{ + struct fcoe_transport *t; + + /* find the corresponding transport */ + t = fcoe_transport_lookup(netdev); + if (!t) { + printk(KERN_DEBUG "fcoe_transport_release:" + "no transport for %s:use %s\n", + netdev->name, t->name); + return -ENODEV; + } + /* remove the device from the transport */ + if (fcoe_transport_device_remove(t, netdev)) { + printk(KERN_DEBUG "fcoe_transport_release:" + "failed to add %s to tramsport %s\n", + netdev->name, t->name); + return -EIO; + } + /* transport destroy function */ + if (t->destroy) + t->destroy(netdev); + + printk(KERN_DEBUG "fcoe_transport_release:" + "device %s dettached from transport %s\n", + netdev->name, t->name); + + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_transport_release); + +/** + * fcoe_transport_init - initializes fcoe transport layer + * + * This prepares for the fcoe transport layer + * + * Returns: none + **/ +int __init fcoe_transport_init(void) +{ + INIT_LIST_HEAD(&fcoe_transports); + mutex_init(&fcoe_transports_lock); + return 0; +} + +/** + * fcoe_transport_exit - cleans up the fcoe transport layer + * This cleans up the fcoe transport layer. removing any transport on the list, + * note that the transport destroy func is not called here. + * + * Returns: none + **/ +int __exit fcoe_transport_exit(void) +{ + struct fcoe_transport *t, *tmp; + + mutex_lock(&fcoe_transports_lock); + list_for_each_entry_safe(t, tmp, &fcoe_transports, list) { + list_del(&t->list); + mutex_unlock(&fcoe_transports_lock); + fcoe_transport_device_remove_all(t); + mutex_lock(&fcoe_transports_lock); + } + mutex_unlock(&fcoe_transports_lock); + return 0; +} diff --git a/drivers/scsi/fcoe/fcoe_sw.c b/drivers/scsi/fcoe/fcoe_sw.c new file mode 100644 index 000000000000..dc4cd5e25760 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_sw.c @@ -0,0 +1,494 @@ +/* + * Copyright(c) 2007 - 2008 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. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/if_vlan.h> +#include <net/rtnetlink.h> + +#include <scsi/fc/fc_els.h> +#include <scsi/fc/fc_encaps.h> +#include <scsi/fc/fc_fs.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_fc.h> + +#include <scsi/libfc.h> +#include <scsi/libfcoe.h> +#include <scsi/fc_transport_fcoe.h> + +#define FCOE_SW_VERSION "0.1" +#define FCOE_SW_NAME "fcoesw" +#define FCOE_SW_VENDOR "Open-FCoE.org" + +#define FCOE_MAX_LUN 255 +#define FCOE_MAX_FCP_TARGET 256 + +#define FCOE_MAX_OUTSTANDING_COMMANDS 1024 + +#define FCOE_MIN_XID 0x0001 /* the min xid supported by fcoe_sw */ +#define FCOE_MAX_XID 0x07ef /* the max xid supported by fcoe_sw */ + +static struct scsi_transport_template *scsi_transport_fcoe_sw; + +struct fc_function_template fcoe_sw_transport_function = { + .show_host_node_name = 1, + .show_host_port_name = 1, + .show_host_supported_classes = 1, + .show_host_supported_fc4s = 1, + .show_host_active_fc4s = 1, + .show_host_maxframe_size = 1, + + .show_host_port_id = 1, + .show_host_supported_speeds = 1, + .get_host_speed = fc_get_host_speed, + .show_host_speed = 1, + .show_host_port_type = 1, + .get_host_port_state = fc_get_host_port_state, + .show_host_port_state = 1, + .show_host_symbolic_name = 1, + + .dd_fcrport_size = sizeof(struct fc_rport_libfc_priv), + .show_rport_maxframe_size = 1, + .show_rport_supported_classes = 1, + + .show_host_fabric_name = 1, + .show_starget_node_name = 1, + .show_starget_port_name = 1, + .show_starget_port_id = 1, + .set_rport_dev_loss_tmo = fc_set_rport_loss_tmo, + .show_rport_dev_loss_tmo = 1, + .get_fc_host_stats = fc_get_host_stats, + .issue_fc_host_lip = fcoe_reset, + + .terminate_rport_io = fc_rport_terminate_io, +}; + +static struct scsi_host_template fcoe_sw_shost_template = { + .module = THIS_MODULE, + .name = "FCoE Driver", + .proc_name = FCOE_SW_NAME, + .queuecommand = fc_queuecommand, + .eh_abort_handler = fc_eh_abort, + .eh_device_reset_handler = fc_eh_device_reset, + .eh_host_reset_handler = fc_eh_host_reset, + .slave_alloc = fc_slave_alloc, + .change_queue_depth = fc_change_queue_depth, + .change_queue_type = fc_change_queue_type, + .this_id = -1, + .cmd_per_lun = 32, + .can_queue = FCOE_MAX_OUTSTANDING_COMMANDS, + .use_clustering = ENABLE_CLUSTERING, + .sg_tablesize = SG_ALL, + .max_sectors = 0xffff, +}; + +/* + * fcoe_sw_lport_config - sets up the fc_lport + * @lp: ptr to the fc_lport + * @shost: ptr to the parent scsi host + * + * Returns: 0 for success + * + */ +static int fcoe_sw_lport_config(struct fc_lport *lp) +{ + int i = 0; + + lp->link_status = 0; + lp->max_retry_count = 3; + lp->e_d_tov = 2 * 1000; /* FC-FS default */ + lp->r_a_tov = 2 * 2 * 1000; + lp->service_params = (FCP_SPPF_INIT_FCN | FCP_SPPF_RD_XRDY_DIS | + FCP_SPPF_RETRY | FCP_SPPF_CONF_COMPL); + + /* + * allocate per cpu stats block + */ + for_each_online_cpu(i) + lp->dev_stats[i] = kzalloc(sizeof(struct fcoe_dev_stats), + GFP_KERNEL); + + /* lport fc_lport related configuration */ + fc_lport_config(lp); + + return 0; +} + +/* + * fcoe_sw_netdev_config - sets up fcoe_softc for lport and network + * related properties + * @lp : ptr to the fc_lport + * @netdev : ptr to the associated netdevice struct + * + * Must be called after fcoe_sw_lport_config() as it will use lport mutex + * + * Returns : 0 for success + * + */ +static int fcoe_sw_netdev_config(struct fc_lport *lp, struct net_device *netdev) +{ + u32 mfs; + u64 wwnn, wwpn; + struct fcoe_softc *fc; + u8 flogi_maddr[ETH_ALEN]; + + /* Setup lport private data to point to fcoe softc */ + fc = lport_priv(lp); + fc->lp = lp; + fc->real_dev = netdev; + fc->phys_dev = netdev; + + /* Require support for get_pauseparam ethtool op. */ + if (netdev->priv_flags & IFF_802_1Q_VLAN) + fc->phys_dev = vlan_dev_real_dev(netdev); + + /* Do not support for bonding device */ + if ((fc->real_dev->priv_flags & IFF_MASTER_ALB) || + (fc->real_dev->priv_flags & IFF_SLAVE_INACTIVE) || + (fc->real_dev->priv_flags & IFF_MASTER_8023AD)) { + return -EOPNOTSUPP; + } + + /* + * Determine max frame size based on underlying device and optional + * user-configured limit. If the MFS is too low, fcoe_link_ok() + * will return 0, so do this first. + */ + mfs = fc->real_dev->mtu - (sizeof(struct fcoe_hdr) + + sizeof(struct fcoe_crc_eof)); + if (fc_set_mfs(lp, mfs)) + return -EINVAL; + + lp->link_status = ~FC_PAUSE & ~FC_LINK_UP; + if (!fcoe_link_ok(lp)) + lp->link_status |= FC_LINK_UP; + + /* offload features support */ + if (fc->real_dev->features & NETIF_F_SG) + lp->sg_supp = 1; + + + skb_queue_head_init(&fc->fcoe_pending_queue); + + /* setup Source Mac Address */ + memcpy(fc->ctl_src_addr, fc->real_dev->dev_addr, + fc->real_dev->addr_len); + + wwnn = fcoe_wwn_from_mac(fc->real_dev->dev_addr, 1, 0); + fc_set_wwnn(lp, wwnn); + /* XXX - 3rd arg needs to be vlan id */ + wwpn = fcoe_wwn_from_mac(fc->real_dev->dev_addr, 2, 0); + fc_set_wwpn(lp, wwpn); + + /* + * Add FCoE MAC address as second unicast MAC address + * or enter promiscuous mode if not capable of listening + * for multiple unicast MACs. + */ + rtnl_lock(); + memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN); + dev_unicast_add(fc->real_dev, flogi_maddr, ETH_ALEN); + rtnl_unlock(); + + /* + * setup the receive function from ethernet driver + * on the ethertype for the given device + */ + fc->fcoe_packet_type.func = fcoe_rcv; + fc->fcoe_packet_type.type = __constant_htons(ETH_P_FCOE); + fc->fcoe_packet_type.dev = fc->real_dev; + dev_add_pack(&fc->fcoe_packet_type); + + return 0; +} + +/* + * fcoe_sw_shost_config - sets up fc_lport->host + * @lp : ptr to the fc_lport + * @shost : ptr to the associated scsi host + * @dev : device associated to scsi host + * + * Must be called after fcoe_sw_lport_config) and fcoe_sw_netdev_config() + * + * Returns : 0 for success + * + */ +static int fcoe_sw_shost_config(struct fc_lport *lp, struct Scsi_Host *shost, + struct device *dev) +{ + int rc = 0; + + /* lport scsi host config */ + lp->host = shost; + + lp->host->max_lun = FCOE_MAX_LUN; + lp->host->max_id = FCOE_MAX_FCP_TARGET; + lp->host->max_channel = 0; + lp->host->transportt = scsi_transport_fcoe_sw; + + /* add the new host to the SCSI-ml */ + rc = scsi_add_host(lp->host, dev); + if (rc) { + FC_DBG("fcoe_sw_shost_config:error on scsi_add_host\n"); + return rc; + } + sprintf(fc_host_symbolic_name(lp->host), "%s v%s over %s", + FCOE_SW_NAME, FCOE_SW_VERSION, + fcoe_netdev(lp)->name); + + return 0; +} + +/* + * fcoe_sw_em_config - allocates em for this lport + * @lp: the port that em is to allocated for + * + * Returns : 0 on success + */ +static inline int fcoe_sw_em_config(struct fc_lport *lp) +{ + BUG_ON(lp->emp); + + lp->emp = fc_exch_mgr_alloc(lp, FC_CLASS_3, + FCOE_MIN_XID, FCOE_MAX_XID); + if (!lp->emp) + return -ENOMEM; + + return 0; +} + +/* + * fcoe_sw_destroy - FCoE software HBA tear-down function + * @netdev: ptr to the associated net_device + * + * Returns: 0 if link is OK for use by FCoE. + */ +static int fcoe_sw_destroy(struct net_device *netdev) +{ + int cpu; + struct fc_lport *lp = NULL; + struct fcoe_softc *fc; + u8 flogi_maddr[ETH_ALEN]; + + BUG_ON(!netdev); + + printk(KERN_DEBUG "fcoe_sw_destroy:interface on %s\n", + netdev->name); + + lp = fcoe_hostlist_lookup(netdev); + if (!lp) + return -ENODEV; + + fc = fcoe_softc(lp); + + /* Logout of the fabric */ + fc_fabric_logoff(lp); + + /* Remove the instance from fcoe's list */ + fcoe_hostlist_remove(lp); + + /* Don't listen for Ethernet packets anymore */ + dev_remove_pack(&fc->fcoe_packet_type); + + /* Cleanup the fc_lport */ + fc_lport_destroy(lp); + fc_fcp_destroy(lp); + + /* Detach from the scsi-ml */ + fc_remove_host(lp->host); + scsi_remove_host(lp->host); + + /* There are no more rports or I/O, free the EM */ + if (lp->emp) + fc_exch_mgr_free(lp->emp); + + /* Delete secondary MAC addresses */ + rtnl_lock(); + memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN); + dev_unicast_delete(fc->real_dev, flogi_maddr, ETH_ALEN); + if (compare_ether_addr(fc->data_src_addr, (u8[6]) { 0 })) + dev_unicast_delete(fc->real_dev, fc->data_src_addr, ETH_ALEN); + rtnl_unlock(); + + /* Free the per-CPU revieve threads */ + fcoe_percpu_clean(lp); + + /* Free existing skbs */ + fcoe_clean_pending_queue(lp); + + /* Free memory used by statistical counters */ + for_each_online_cpu(cpu) + kfree(lp->dev_stats[cpu]); + + /* Release the net_device and Scsi_Host */ + dev_put(fc->real_dev); + scsi_host_put(lp->host); + + return 0; +} + +static struct libfc_function_template fcoe_sw_libfc_fcn_templ = { + .frame_send = fcoe_xmit, +}; + +/* + * fcoe_sw_create - this function creates the fcoe interface + * @netdev: pointer the associated netdevice + * + * Creates fc_lport struct and scsi_host for lport, configures lport + * and starts fabric login. + * + * Returns : 0 on success + */ +static int fcoe_sw_create(struct net_device *netdev) +{ + int rc; + struct fc_lport *lp = NULL; + struct fcoe_softc *fc; + struct Scsi_Host *shost; + + BUG_ON(!netdev); + + printk(KERN_DEBUG "fcoe_sw_create:interface on %s\n", + netdev->name); + + lp = fcoe_hostlist_lookup(netdev); + if (lp) + return -EEXIST; + + shost = fcoe_host_alloc(&fcoe_sw_shost_template, + sizeof(struct fcoe_softc)); + if (!shost) { + FC_DBG("Could not allocate host structure\n"); + return -ENOMEM; + } + lp = shost_priv(shost); + fc = lport_priv(lp); + + /* configure fc_lport, e.g., em */ + rc = fcoe_sw_lport_config(lp); + if (rc) { + FC_DBG("Could not configure lport\n"); + goto out_host_put; + } + + /* configure lport network properties */ + rc = fcoe_sw_netdev_config(lp, netdev); + if (rc) { + FC_DBG("Could not configure netdev for lport\n"); + goto out_host_put; + } + + /* configure lport scsi host properties */ + rc = fcoe_sw_shost_config(lp, shost, &netdev->dev); + if (rc) { + FC_DBG("Could not configure shost for lport\n"); + goto out_host_put; + } + + /* lport exch manager allocation */ + rc = fcoe_sw_em_config(lp); + if (rc) { + FC_DBG("Could not configure em for lport\n"); + goto out_host_put; + } + + /* Initialize the library */ + rc = fcoe_libfc_config(lp, &fcoe_sw_libfc_fcn_templ); + if (rc) { + FC_DBG("Could not configure libfc for lport!\n"); + goto out_lp_destroy; + } + + /* add to lports list */ + fcoe_hostlist_add(lp); + + lp->boot_time = jiffies; + + fc_fabric_login(lp); + + dev_hold(netdev); + + return rc; + +out_lp_destroy: + fc_exch_mgr_free(lp->emp); /* Free the EM */ +out_host_put: + scsi_host_put(lp->host); + return rc; +} + +/* + * fcoe_sw_match - the fcoe sw transport match function + * + * Returns : false always + */ +static bool fcoe_sw_match(struct net_device *netdev) +{ + /* FIXME - for sw transport, always return false */ + return false; +} + +/* the sw hba fcoe transport */ +struct fcoe_transport fcoe_sw_transport = { + .name = "fcoesw", + .create = fcoe_sw_create, + .destroy = fcoe_sw_destroy, + .match = fcoe_sw_match, + .vendor = 0x0, + .device = 0xffff, +}; + +/* + * fcoe_sw_init - registers fcoe_sw_transport + * + * Returns : 0 on success + */ +int __init fcoe_sw_init(void) +{ + /* attach to scsi transport */ + scsi_transport_fcoe_sw = + fc_attach_transport(&fcoe_sw_transport_function); + if (!scsi_transport_fcoe_sw) { + printk(KERN_ERR "fcoe_sw_init:fc_attach_transport() failed\n"); + return -ENODEV; + } + /* register sw transport */ + fcoe_transport_register(&fcoe_sw_transport); + return 0; +} + +/* + * fcoe_sw_exit - unregisters fcoe_sw_transport + * + * Returns : 0 on success + */ +int __exit fcoe_sw_exit(void) +{ + /* dettach the transport */ + fc_release_transport(scsi_transport_fcoe_sw); + fcoe_transport_unregister(&fcoe_sw_transport); + return 0; +} diff --git a/drivers/scsi/fcoe/libfcoe.c b/drivers/scsi/fcoe/libfcoe.c new file mode 100644 index 000000000000..1cb549c4fac4 --- /dev/null +++ b/drivers/scsi/fcoe/libfcoe.c @@ -0,0 +1,1510 @@ +/* + * Copyright(c) 2007 - 2008 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. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/if_ether.h> +#include <linux/if_vlan.h> +#include <linux/kthread.h> +#include <linux/crc32.h> +#include <linux/cpu.h> +#include <linux/fs.h> +#include <linux/sysfs.h> +#include <linux/ctype.h> +#include <scsi/scsi_tcq.h> +#include <scsi/scsicam.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_fc.h> +#include <net/rtnetlink.h> + +#include <scsi/fc/fc_encaps.h> + +#include <scsi/libfc.h> +#include <scsi/fc_frame.h> +#include <scsi/libfcoe.h> +#include <scsi/fc_transport_fcoe.h> + +static int debug_fcoe; + +#define FCOE_MAX_QUEUE_DEPTH 256 + +/* destination address mode */ +#define FCOE_GW_ADDR_MODE 0x00 +#define FCOE_FCOUI_ADDR_MODE 0x01 + +#define FCOE_WORD_TO_BYTE 4 + +MODULE_AUTHOR("Open-FCoE.org"); +MODULE_DESCRIPTION("FCoE"); +MODULE_LICENSE("GPL"); + +/* fcoe host list */ +LIST_HEAD(fcoe_hostlist); +DEFINE_RWLOCK(fcoe_hostlist_lock); +DEFINE_TIMER(fcoe_timer, NULL, 0, 0); +struct fcoe_percpu_s *fcoe_percpu[NR_CPUS]; + + +/* Function Prototyes */ +static int fcoe_check_wait_queue(struct fc_lport *); +static void fcoe_insert_wait_queue_head(struct fc_lport *, struct sk_buff *); +static void fcoe_insert_wait_queue(struct fc_lport *, struct sk_buff *); +static void fcoe_recv_flogi(struct fcoe_softc *, struct fc_frame *, u8 *); +#ifdef CONFIG_HOTPLUG_CPU +static int fcoe_cpu_callback(struct notifier_block *, ulong, void *); +#endif /* CONFIG_HOTPLUG_CPU */ +static int fcoe_device_notification(struct notifier_block *, ulong, void *); +static void fcoe_dev_setup(void); +static void fcoe_dev_cleanup(void); + +/* notification function from net device */ +static struct notifier_block fcoe_notifier = { + .notifier_call = fcoe_device_notification, +}; + + +#ifdef CONFIG_HOTPLUG_CPU +static struct notifier_block fcoe_cpu_notifier = { + .notifier_call = fcoe_cpu_callback, +}; + +/** + * fcoe_create_percpu_data - creates the associated cpu data + * @cpu: index for the cpu where fcoe cpu data will be created + * + * create percpu stats block, from cpu add notifier + * + * Returns: none + **/ +static void fcoe_create_percpu_data(int cpu) +{ + struct fc_lport *lp; + struct fcoe_softc *fc; + + write_lock_bh(&fcoe_hostlist_lock); + list_for_each_entry(fc, &fcoe_hostlist, list) { + lp = fc->lp; + if (lp->dev_stats[cpu] == NULL) + lp->dev_stats[cpu] = + kzalloc(sizeof(struct fcoe_dev_stats), + GFP_KERNEL); + } + write_unlock_bh(&fcoe_hostlist_lock); +} + +/** + * fcoe_destroy_percpu_data - destroys the associated cpu data + * @cpu: index for the cpu where fcoe cpu data will destroyed + * + * destroy percpu stats block called by cpu add/remove notifier + * + * Retuns: none + **/ +static void fcoe_destroy_percpu_data(int cpu) +{ + struct fc_lport *lp; + struct fcoe_softc *fc; + + write_lock_bh(&fcoe_hostlist_lock); + list_for_each_entry(fc, &fcoe_hostlist, list) { + lp = fc->lp; + kfree(lp->dev_stats[cpu]); + lp->dev_stats[cpu] = NULL; + } + write_unlock_bh(&fcoe_hostlist_lock); +} + +/** + * fcoe_cpu_callback - fcoe cpu hotplug event callback + * @nfb: callback data block + * @action: event triggering the callback + * @hcpu: index for the cpu of this event + * + * this creates or destroys per cpu data for fcoe + * + * Returns NOTIFY_OK always. + **/ +static int fcoe_cpu_callback(struct notifier_block *nfb, unsigned long action, + void *hcpu) +{ + unsigned int cpu = (unsigned long)hcpu; + + switch (action) { + case CPU_ONLINE: + fcoe_create_percpu_data(cpu); + break; + case CPU_DEAD: + fcoe_destroy_percpu_data(cpu); + break; + default: + break; + } + return NOTIFY_OK; +} +#endif /* CONFIG_HOTPLUG_CPU */ + +/** + * foce_rcv - this is the fcoe receive function called by NET_RX_SOFTIRQ + * @skb: the receive skb + * @dev: associated net device + * @ptype: context + * @odldev: last device + * + * this function will receive the packet and build fc frame and pass it up + * |