From: Vasu Dev <vasu.dev@xxxxxxxxx> Renames libfcoe.c to fcoe.c, fcoe.c becomes the only .c file for fcoe.ko. Also deleted "$Id: Makefile" from fcoe module Makefle. Signed-off-by: Vasu Dev <vasu.dev@xxxxxxxxx> Signed-off-by: Robert Love <robert.w.love@xxxxxxxxx> --- drivers/scsi/fcoe/Makefile | 5 drivers/scsi/fcoe/fcoe.c | 1972 +++++++++++++++++++++++++++++++++++++++++++ drivers/scsi/fcoe/libfcoe.c | 1972 ------------------------------------------- 3 files changed, 1972 insertions(+), 1977 deletions(-) create mode 100644 drivers/scsi/fcoe/fcoe.c delete mode 100644 drivers/scsi/fcoe/libfcoe.c diff --git a/drivers/scsi/fcoe/Makefile b/drivers/scsi/fcoe/Makefile index 2d9dae4..9b590ef 100644 --- a/drivers/scsi/fcoe/Makefile +++ b/drivers/scsi/fcoe/Makefile @@ -1,6 +1 @@ -# $Id: Makefile - obj-$(CONFIG_FCOE) += fcoe.o - -fcoe-y := \ - libfcoe.o diff --git a/drivers/scsi/fcoe/fcoe.c b/drivers/scsi/fcoe/fcoe.c new file mode 100644 index 0000000..cbebbf7 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe.c @@ -0,0 +1,1972 @@ +/* + * 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> + +static int debug_fcoe; + +#define FCOE_MAX_QUEUE_DEPTH 256 +#define FCOE_LOW_QUEUE_DEPTH 32 + +/* destination address mode */ +#define FCOE_GW_ADDR_MODE 0x00 +#define FCOE_FCOUI_ADDR_MODE 0x01 + +#define FCOE_WORD_TO_BYTE 4 + +#define FCOE_VERSION "0.1" +#define FCOE_NAME "fcoe" +#define FCOE_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 */ + +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); +DEFINE_PER_CPU(struct fcoe_percpu_s, fcoe_percpu); + + +/* Function Prototyes */ +static int fcoe_check_wait_queue(struct fc_lport *); +static void fcoe_recv_flogi(struct fcoe_softc *, struct fc_frame *, u8 *); +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, +}; + +static struct scsi_transport_template *scsi_transport_fcoe_sw; + +struct fc_function_template fcoe_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_shost_template = { + .module = THIS_MODULE, + .name = "FCoE Driver", + .proc_name = FCOE_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_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_lport_config(struct fc_lport *lp) +{ + lp->link_up = 0; + lp->qfull = 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); + + fc_lport_init_stats(lp); + + /* lport fc_lport related configuration */ + fc_lport_config(lp); + + /* offload related configuration */ + lp->crc_offload = 0; + lp->seq_offload = 0; + lp->lro_enabled = 0; + lp->lro_xid = 0; + lp->lso_max = 0; + + return 0; +} + +/** + * fcoe_netdev_config() - Set up netdev for SW FCoE + * @lp : ptr to the fc_lport + * @netdev : ptr to the associated netdevice struct + * + * Must be called after fcoe_lport_config() as it will use lport mutex + * + * Returns : 0 for success + */ +static int fcoe_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; + + if (!fcoe_link_ok(lp)) + lp->link_up = 1; + + /* offload features support */ + if (fc->real_dev->features & NETIF_F_SG) + lp->sg_supp = 1; + +#ifdef NETIF_F_FCOE_CRC + if (netdev->features & NETIF_F_FCOE_CRC) { + lp->crc_offload = 1; + printk(KERN_DEBUG "fcoe:%s supports FCCRC offload\n", + netdev->name); + } +#endif +#ifdef NETIF_F_FSO + if (netdev->features & NETIF_F_FSO) { + lp->seq_offload = 1; + lp->lso_max = netdev->gso_max_size; + printk(KERN_DEBUG "fcoe:%s supports LSO for max len 0x%x\n", + netdev->name, lp->lso_max); + } +#endif + if (netdev->fcoe_ddp_xid) { + lp->lro_enabled = 1; + lp->lro_xid = netdev->fcoe_ddp_xid; + printk(KERN_DEBUG "fcoe:%s supports LRO for max xid 0x%x\n", + netdev->name, lp->lro_xid); + } + skb_queue_head_init(&fc->fcoe_pending_queue); + fc->fcoe_pending_queue_active = 0; + + /* 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_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_lport_config() and fcoe_netdev_config() + * + * Returns : 0 for success + */ +static int fcoe_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_shost_config:error on scsi_add_host\n"); + return rc; + } + sprintf(fc_host_symbolic_name(lp->host), "%s v%s over %s", + FCOE_NAME, FCOE_VERSION, + fcoe_netdev(lp)->name); + + return 0; +} + +/** + * fcoe_em_config() - allocates em for this lport + * @lp: the port that em is to allocated for + * + * Returns : 0 on success + */ +static inline int fcoe_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_if_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_if_destroy(struct net_device *netdev) +{ + struct fc_lport *lp = NULL; + struct fcoe_softc *fc; + u8 flogi_maddr[ETH_ALEN]; + + BUG_ON(!netdev); + + printk(KERN_DEBUG "fcoe_if_destroy:interface on %s\n", + netdev->name); + + lp = fcoe_hostlist_lookup(netdev); + if (!lp) + return -ENODEV; + + fc = lport_priv(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 */ + fc_lport_free_stats(lp); + + /* Release the net_device and Scsi_Host */ + dev_put(fc->real_dev); + scsi_host_put(lp->host); + + return 0; +} + +/* + * fcoe_ddp_setup - calls LLD's ddp_setup through net_device + * @lp: the corresponding fc_lport + * @xid: the exchange id for this ddp transfer + * @sgl: the scatterlist describing this transfer + * @sgc: number of sg items + * + * Returns : 0 no ddp + */ +static int fcoe_ddp_setup(struct fc_lport *lp, u16 xid, + struct scatterlist *sgl, unsigned int sgc) +{ + struct net_device *n = fcoe_netdev(lp); + + if (n->netdev_ops && n->netdev_ops->ndo_fcoe_ddp_setup) + return n->netdev_ops->ndo_fcoe_ddp_setup(n, xid, sgl, sgc); + + return 0; +} + +/* + * fcoe_ddp_done - calls LLD's ddp_done through net_device + * @lp: the corresponding fc_lport + * @xid: the exchange id for this ddp transfer + * + * Returns : the length of data that have been completed by ddp + */ +static int fcoe_ddp_done(struct fc_lport *lp, u16 xid) +{ + struct net_device *n = fcoe_netdev(lp); + + if (n->netdev_ops && n->netdev_ops->ndo_fcoe_ddp_done) + return n->netdev_ops->ndo_fcoe_ddp_done(n, xid); + return 0; +} + +static struct libfc_function_template fcoe_libfc_fcn_templ = { + .frame_send = fcoe_xmit, + .ddp_setup = fcoe_ddp_setup, + .ddp_done = fcoe_ddp_done, +}; + +/** + * fcoe_if_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_if_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_if_create:interface on %s\n", + netdev->name); + + lp = fcoe_hostlist_lookup(netdev); + if (lp) + return -EEXIST; + + shost = fcoe_host_alloc(&fcoe_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_lport_config(lp); + if (rc) { + FC_DBG("Could not configure lport\n"); + goto out_host_put; + } + + /* configure lport network properties */ + rc = fcoe_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_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_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_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_if_init() - attach to scsi transport + * + * Returns : 0 on success + */ +static int __init fcoe_if_init(void) +{ + /* attach to scsi transport */ + scsi_transport_fcoe_sw = + fc_attach_transport(&fcoe_transport_function); + + if (!scsi_transport_fcoe_sw) { + printk(KERN_ERR "fcoe_init:fc_attach_transport() failed\n"); + return -ENODEV; + } + + return 0; +} + +/** + * fcoe_percpu_thread_create() - Create a receive thread for an online cpu + * @cpu: cpu index for the online cpu + */ +static void fcoe_percpu_thread_create(unsigned int cpu) +{ + struct fcoe_percpu_s *p; + struct task_struct *thread; + + p = &per_cpu(fcoe_percpu, cpu); + + thread = kthread_create(fcoe_percpu_receive_thread, + (void *)p, "fcoethread/%d", cpu); + + if (likely(!IS_ERR(p->thread))) { + kthread_bind(thread, cpu); + wake_up_process(thread); + + spin_lock_bh(&p->fcoe_rx_list.lock); + p->thread = thread; + spin_unlock_bh(&p->fcoe_rx_list.lock); + } +} + +/** + * fcoe_percpu_thread_destroy() - removes the rx thread for the given cpu + * @cpu: cpu index the rx thread is to be removed + * + * Destroys a per-CPU Rx thread. Any pending skbs are moved to the + * current CPU's Rx thread. If the thread being destroyed is bound to + * the CPU processing this context the skbs will be freed. + */ +static void fcoe_percpu_thread_destroy(unsigned int cpu) +{ + struct fcoe_percpu_s *p; + struct task_struct *thread; + struct page *crc_eof; + struct sk_buff *skb; +#ifdef CONFIG_SMP + struct fcoe_percpu_s *p0; + unsigned targ_cpu = smp_processor_id(); +#endif /* CONFIG_SMP */ + + printk(KERN_DEBUG "fcoe: Destroying receive thread for CPU %d\n", cpu); + + /* Prevent any new skbs from being queued for this CPU. */ + p = &per_cpu(fcoe_percpu, cpu); + spin_lock_bh(&p->fcoe_rx_list.lock); + thread = p->thread; + p->thread = NULL; + crc_eof = p->crc_eof_page; + p->crc_eof_page = NULL; + p->crc_eof_offset = 0; + spin_unlock_bh(&p->fcoe_rx_list.lock); + +#ifdef CONFIG_SMP + /* + * Don't bother moving the skb's if this context is running + * on the same CPU that is having its thread destroyed. This + * can easily happen when the module is removed. + */ + if (cpu != targ_cpu) { + p0 = &per_cpu(fcoe_percpu, targ_cpu); + spin_lock_bh(&p0->fcoe_rx_list.lock); + if (p0->thread) { + FC_DBG("Moving frames from CPU %d to CPU %d\n", + cpu, targ_cpu); + + while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) + __skb_queue_tail(&p0->fcoe_rx_list, skb); + spin_unlock_bh(&p0->fcoe_rx_list.lock); + } else { + /* + * The targeted CPU is not initialized and cannot accept + * new skbs. Unlock the targeted CPU and drop the skbs + * on the CPU that is going offline. + */ + while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) + kfree_skb(skb); + spin_unlock_bh(&p0->fcoe_rx_list.lock); + } + } else { + /* + * This scenario occurs when the module is being removed + * and all threads are being destroyed. skbs will continue + * to be shifted from the CPU thread that is being removed + * to the CPU thread associated with the CPU that is processing + * the module removal. Once there is only one CPU Rx thread it + * will reach this case and we will drop all skbs and later + * stop the thread. + */ + spin_lock_bh(&p->fcoe_rx_list.lock); + while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) + kfree_skb(skb); + spin_unlock_bh(&p->fcoe_rx_list.lock); + } +#else + /* + * This a non-SMP scenario where the singluar Rx thread is + * being removed. Free all skbs and stop the thread. + */ + spin_lock_bh(&p->fcoe_rx_list.lock); + while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) + kfree_skb(skb); + spin_unlock_bh(&p->fcoe_rx_list.lock); +#endif + + if (thread) + kthread_stop(thread); + + if (crc_eof) + put_page(crc_eof); +} + +/** + * 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 cpu = (unsigned long)hcpu; + + switch (action) { + case CPU_ONLINE: + case CPU_ONLINE_FROZEN: + FC_DBG("CPU %x online: Create Rx thread\n", cpu); + fcoe_percpu_thread_create(cpu); + break; + case CPU_DEAD: + case CPU_DEAD_FROZEN: + FC_DBG("CPU %x offline: Remove Rx thread\n", cpu); + fcoe_percpu_thread_destroy(cpu); + break; + default: + break; + } + return NOTIFY_OK; +} + +static struct notifier_block fcoe_cpu_notifier = { + .notifier_call = fcoe_cpu_callback, +}; + +/** + * fcoe_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 + * + * Returns: 0 for success + */ +int fcoe_rcv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *ptype, struct net_device *olddev) +{ + struct fc_lport *lp; + struct fcoe_rcv_info *fr; + struct fcoe_softc *fc; + struct fc_frame_header *fh; + struct fcoe_percpu_s *fps; + unsigned short oxid; + unsigned int cpu = 0; + + fc = container_of(ptype, struct fcoe_softc, fcoe_packet_type); + lp = fc->lp; + if (unlikely(lp == NULL)) { + FC_DBG("cannot find hba structure"); + goto err2; + } + + if (unlikely(debug_fcoe)) { + FC_DBG("skb_info: len:%d data_len:%d head:%p data:%p tail:%p " + "end:%p sum:%d dev:%s", skb->len, skb->data_len, + skb->head, skb->data, skb_tail_pointer(skb), + skb_end_pointer(skb), skb->csum, + skb->dev ? skb->dev->name : "<NULL>"); + + } + + /* check for FCOE packet type */ + if (unlikely(eth_hdr(skb)->h_proto != htons(ETH_P_FCOE))) { + FC_DBG("wrong FC type frame"); + goto err; + } + + /* + * Check for minimum frame length, and make sure required FCoE + * and FC headers are pulled into the linear data area. + */ + if (unlikely((skb->len < FCOE_MIN_FRAME) || + !pskb_may_pull(skb, FCOE_HEADER_LEN))) + goto err; + + skb_set_transport_header(skb, sizeof(struct fcoe_hdr)); + fh = (struct fc_frame_header *) skb_transport_header(skb); + + oxid = ntohs(fh->fh_ox_id); + + fr = fcoe_dev_from_skb(skb); + fr->fr_dev = lp; + fr->ptype = ptype; + +#ifdef CONFIG_SMP + /* + * The incoming frame exchange id(oxid) is ANDed with num of online + * cpu bits to get cpu and then this cpu is used for selecting + * a per cpu kernel thread from fcoe_percpu. + */ + cpu = oxid & (num_online_cpus() - 1); +#endif + + fps = &per_cpu(fcoe_percpu, cpu); + spin_lock_bh(&fps->fcoe_rx_list.lock); + if (unlikely(!fps->thread)) { + /* + * The targeted CPU is not ready, let's target + * the first CPU now. For non-SMP systems this + * will check the same CPU twice. + */ + FC_DBG("CPU is online, but no receive thread ready " + "for incoming skb- using first online CPU.\n"); + + spin_unlock_bh(&fps->fcoe_rx_list.lock); + cpu = first_cpu(cpu_online_map); + fps = &per_cpu(fcoe_percpu, cpu); + spin_lock_bh(&fps->fcoe_rx_list.lock); + if (!fps->thread) { + spin_unlock_bh(&fps->fcoe_rx_list.lock); + goto err; + } + } + + /* + * We now have a valid CPU that we're targeting for + * this skb. We also have this receive thread locked, + * so we're free to queue skbs into it's queue. + */ + __skb_queue_tail(&fps->fcoe_rx_list, skb); + if (fps->fcoe_rx_list.qlen == 1) + wake_up_process(fps->thread); + + spin_unlock_bh(&fps->fcoe_rx_list.lock); + + return 0; +err: + fc_lport_get_stats(lp)->ErrorFrames++; + +err2: + kfree_skb(skb); + return -1; +} +EXPORT_SYMBOL_GPL(fcoe_rcv); + +/** + * fcoe_start_io() - pass to netdev to start xmit for fcoe + * @skb: the skb to be xmitted + * + * Returns: 0 for success + */ +static inline int fcoe_start_io(struct sk_buff *skb) +{ + int rc; + + skb_get(skb); + rc = dev_queue_xmit(skb); + if (rc != 0) + return rc; + kfree_skb(skb); + return 0; +} + +/** + * fcoe_get_paged_crc_eof() - in case we need alloc a page for crc_eof + * @skb: the skb to be xmitted + * @tlen: total len + * + * Returns: 0 for success + */ +static int fcoe_get_paged_crc_eof(struct sk_buff *skb, int tlen) +{ + struct fcoe_percpu_s *fps; + struct page *page; + + fps = &get_cpu_var(fcoe_percpu); + page = fps->crc_eof_page; + if (!page) { + page = alloc_page(GFP_ATOMIC); + if (!page) { + put_cpu_var(fcoe_percpu); + return -ENOMEM; + } + fps->crc_eof_page = page; + fps->crc_eof_offset = 0; + } + + get_page(page); + skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page, + fps->crc_eof_offset, tlen); + skb->len += tlen; + skb->data_len += tlen; + skb->truesize += tlen; + fps->crc_eof_offset += sizeof(struct fcoe_crc_eof); + + if (fps->crc_eof_offset >= PAGE_SIZE) { + fps->crc_eof_page = NULL; + fps->crc_eof_offset = 0; + put_page(page); + } + put_cpu_var(fcoe_percpu); + return 0; +} + +/** + * fcoe_fc_crc() - calculates FC CRC in this fcoe skb + * @fp: the fc_frame containg data to be checksummed + * + * This uses crc32() to calculate the crc for fc frame + * Return : 32 bit crc + */ +u32 fcoe_fc_crc(struct fc_frame *fp) +{ + struct sk_buff *skb = fp_skb(fp); + struct skb_frag_struct *frag; + unsigned char *data; + unsigned long off, len, clen; + u32 crc; + unsigned i; + + crc = crc32(~0, skb->data, skb_headlen(skb)); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + frag = &skb_shinfo(skb)->frags[i]; + off = frag->page_offset; + len = frag->size; + while (len > 0) { + clen = min(len, PAGE_SIZE - (off & ~PAGE_MASK)); + data = kmap_atomic(frag->page + (off >> PAGE_SHIFT), + KM_SKB_DATA_SOFTIRQ); + crc = crc32(crc, data + (off & ~PAGE_MASK), clen); + kunmap_atomic(data, KM_SKB_DATA_SOFTIRQ); + off += clen; + len -= clen; + } + } + return crc; +} +EXPORT_SYMBOL_GPL(fcoe_fc_crc); + +/** + * fcoe_xmit() - FCoE frame transmit function + * @lp: the associated local port + * @fp: the fc_frame to be transmitted + * + * Return : 0 for success + */ +int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) +{ + int wlen, rc = 0; + u32 crc; + struct ethhdr *eh; + struct fcoe_crc_eof *cp; + struct sk_buff *skb; + struct fcoe_dev_stats *stats; + struct fc_frame_header *fh; + unsigned int hlen; /* header length implies the version */ + unsigned int tlen; /* trailer length */ + unsigned int elen; /* eth header, may include vlan */ + int flogi_in_progress = 0; + struct fcoe_softc *fc; + u8 sof, eof; + struct fcoe_hdr *hp; + + WARN_ON((fr_len(fp) % sizeof(u32)) != 0); + + fc = lport_priv(lp); + /* + * if it is a flogi then we need to learn gw-addr + * and my own fcid + */ + fh = fc_frame_header_get(fp); + if (unlikely(fh->fh_r_ctl == FC_RCTL_ELS_REQ)) { + if (fc_frame_payload_op(fp) == ELS_FLOGI) { + fc->flogi_oxid = ntohs(fh->fh_ox_id); + fc->address_mode = FCOE_FCOUI_ADDR_MODE; + fc->flogi_progress = 1; + flogi_in_progress = 1; + } else if (fc->flogi_progress && ntoh24(fh->fh_s_id) != 0) { + /* + * Here we must've gotten an SID by accepting an FLOGI + * from a point-to-point connection. Switch to using + * the source mac based on the SID. The destination + * MAC in this case would have been set by receving the + * FLOGI. + */ + fc_fcoe_set_mac(fc->data_src_addr, fh->fh_s_id); + fc->flogi_progress = 0; + } + } + + skb = fp_skb(fp); + sof = fr_sof(fp); + eof = fr_eof(fp); + + elen = (fc->real_dev->priv_flags & IFF_802_1Q_VLAN) ? + sizeof(struct vlan_ethhdr) : sizeof(struct ethhdr); + hlen = sizeof(struct fcoe_hdr); + tlen = sizeof(struct fcoe_crc_eof); + wlen = (skb->len - tlen + sizeof(crc)) / FCOE_WORD_TO_BYTE; + + /* crc offload */ + if (likely(lp->crc_offload)) { + skb->ip_summed = CHECKSUM_PARTIAL; + skb->csum_start = skb_headroom(skb); + skb->csum_offset = skb->len; + crc = 0; + } else { + skb->ip_summed = CHECKSUM_NONE; + crc = fcoe_fc_crc(fp); + } + + /* copy fc crc and eof to the skb buff */ + if (skb_is_nonlinear(skb)) { + skb_frag_t *frag; + if (fcoe_get_paged_crc_eof(skb, tlen)) { + kfree_skb(skb); + return -ENOMEM; + } + frag = &skb_shinfo(skb)->frags[skb_shinfo(skb)->nr_frags - 1]; + cp = kmap_atomic(frag->page, KM_SKB_DATA_SOFTIRQ) + + frag->page_offset; + } else { + cp = (struct fcoe_crc_eof *)skb_put(skb, tlen); + } + + memset(cp, 0, sizeof(*cp)); + cp->fcoe_eof = eof; + cp->fcoe_crc32 = cpu_to_le32(~crc); + + if (skb_is_nonlinear(skb)) { + kunmap_atomic(cp, KM_SKB_DATA_SOFTIRQ); + cp = NULL; + } + + /* adjust skb netowrk/transport offsets to match mac/fcoe/fc */ + skb_push(skb, elen + hlen); + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb->mac_len = elen; + skb->protocol = htons(ETH_P_FCOE); + skb->dev = fc->real_dev; + + /* fill up mac and fcoe headers */ + eh = eth_hdr(skb); + eh->h_proto = htons(ETH_P_FCOE); + if (fc->address_mode == FCOE_FCOUI_ADDR_MODE) + fc_fcoe_set_mac(eh->h_dest, fh->fh_d_id); + else + /* insert GW address */ + memcpy(eh->h_dest, fc->dest_addr, ETH_ALEN); + + if (unlikely(flogi_in_progress)) + memcpy(eh->h_source, fc->ctl_src_addr, ETH_ALEN); + else + memcpy(eh->h_source, fc->data_src_addr, ETH_ALEN); + + hp = (struct fcoe_hdr *)(eh + 1); + memset(hp, 0, sizeof(*hp)); + if (FC_FCOE_VER) + FC_FCOE_ENCAPS_VER(hp, FC_FCOE_VER); + hp->fcoe_sof = sof; + +#ifdef NETIF_F_FSO + /* fcoe lso, mss is in max_payload which is non-zero for FCP data */ + if (lp->seq_offload && fr_max_payload(fp)) { + skb_shinfo(skb)->gso_type = SKB_GSO_FCOE; + skb_shinfo(skb)->gso_size = fr_max_payload(fp); + } else { + skb_shinfo(skb)->gso_type = 0; + skb_shinfo(skb)->gso_size = 0; + } +#endif + /* update tx stats: regardless if LLD fails */ + stats = fc_lport_get_stats(lp); + stats->TxFrames++; + stats->TxWords += wlen; + + /* send down to lld */ + fr_dev(fp) = lp; + if (fc->fcoe_pending_queue.qlen) + rc = fcoe_check_wait_queue(lp); + + if (rc == 0) + rc = fcoe_start_io(skb); + + if (rc) { + spin_lock_bh(&fc->fcoe_pending_queue.lock); + __skb_queue_tail(&fc->fcoe_pending_queue, skb); + spin_unlock_bh(&fc->fcoe_pending_queue.lock); + if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH) + lp->qfull = 1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_xmit); + +/** + * fcoe_percpu_receive_thread() - recv thread per cpu + * @arg: ptr to the fcoe per cpu struct + * + * Return: 0 for success + */ +int fcoe_percpu_receive_thread(void *arg) +{ + struct fcoe_percpu_s *p = arg; + u32 fr_len; + struct fc_lport *lp; + struct fcoe_rcv_info *fr; + struct fcoe_dev_stats *stats; + struct fc_frame_header *fh; + struct sk_buff *skb; + struct fcoe_crc_eof crc_eof; + struct fc_frame *fp; + u8 *mac = NULL; + struct fcoe_softc *fc; + struct fcoe_hdr *hp; + + set_user_nice(current, -20); + + while (!kthread_should_stop()) { + + spin_lock_bh(&p->fcoe_rx_list.lock); + while ((skb = __skb_dequeue(&p->fcoe_rx_list)) == NULL) { + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_bh(&p->fcoe_rx_list.lock); + schedule(); + set_current_state(TASK_RUNNING); + if (kthread_should_stop()) + return 0; + spin_lock_bh(&p->fcoe_rx_list.lock); + } + spin_unlock_bh(&p->fcoe_rx_list.lock); + fr = fcoe_dev_from_skb(skb); + lp = fr->fr_dev; + if (unlikely(lp == NULL)) { + FC_DBG("invalid HBA Structure"); + kfree_skb(skb); + continue; + } + + if (unlikely(debug_fcoe)) { + FC_DBG("skb_info: len:%d data_len:%d head:%p data:%p " + "tail:%p end:%p sum:%d dev:%s", + skb->len, skb->data_len, + skb->head, skb->data, skb_tail_pointer(skb), + skb_end_pointer(skb), skb->csum, + skb->dev ? skb->dev->name : "<NULL>"); + } + + /* + * Save source MAC address before discarding header. + */ + fc = lport_priv(lp); + if (unlikely(fc->flogi_progress)) + mac = eth_hdr(skb)->h_source; + + if (skb_is_nonlinear(skb)) + skb_linearize(skb); /* not ideal */ + + /* + * Frame length checks and setting up the header pointers + * was done in fcoe_rcv already. + */ + hp = (struct fcoe_hdr *) skb_network_header(skb); + fh = (struct fc_frame_header *) skb_transport_header(skb); + + stats = fc_lport_get_stats(lp); + if (unlikely(FC_FCOE_DECAPS_VER(hp) != FC_FCOE_VER)) { + if (stats->ErrorFrames < 5) + printk(KERN_WARNING "FCoE version " + "mismatch: The frame has " + "version %x, but the " + "initiator supports version " + "%x\n", FC_FCOE_DECAPS_VER(hp), + FC_FCOE_VER); + stats->ErrorFrames++; + kfree_skb(skb); + continue; + } + + skb_pull(skb, sizeof(struct fcoe_hdr)); + fr_len = skb->len - sizeof(struct fcoe_crc_eof); + + stats->RxFrames++; + stats->RxWords += fr_len / FCOE_WORD_TO_BYTE; + + fp = (struct fc_frame *)skb; + fc_frame_init(fp); + fr_dev(fp) = lp; + fr_sof(fp) = hp->fcoe_sof; + + /* Copy out the CRC and EOF trailer for access */ + if (skb_copy_bits(skb, fr_len, &crc_eof, sizeof(crc_eof))) { + kfree_skb(skb); + continue; + } + fr_eof(fp) = crc_eof.fcoe_eof; + fr_crc(fp) = crc_eof.fcoe_crc32; + if (pskb_trim(skb, fr_len)) { + kfree_skb(skb); + continue; + } + + /* + * We only check CRC if no offload is available and if it is + * it's solicited data, in which case, the FCP layer would + * check it during the copy. + */ + if (lp->crc_offload && skb->ip_summed == CHECKSUM_UNNECESSARY) + fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; + else + fr_flags(fp) |= FCPHF_CRC_UNCHECKED; + + fh = fc_frame_header_get(fp); + if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA && + fh->fh_type == FC_TYPE_FCP) { + fc_exch_recv(lp, lp->emp, fp); + continue; + } + if (fr_flags(fp) & FCPHF_CRC_UNCHECKED) { + if (le32_to_cpu(fr_crc(fp)) != + ~crc32(~0, skb->data, fr_len)) { + if (debug_fcoe || stats->InvalidCRCCount < 5) + printk(KERN_WARNING "fcoe: dropping " + "frame with CRC error\n"); + stats->InvalidCRCCount++; + stats->ErrorFrames++; + fc_frame_free(fp); + continue; + } + fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; + } + /* non flogi and non data exchanges are handled here */ + if (unlikely(fc->flogi_progress)) + fcoe_recv_flogi(fc, fp, mac); + fc_exch_recv(lp, lp->emp, fp); + } + return 0; +} + +/** + * fcoe_recv_flogi() - flogi receive function + * @fc: associated fcoe_softc + * @fp: the recieved frame + * @sa: the source address of this flogi + * + * This is responsible to parse the flogi response and sets the corresponding + * mac address for the initiator, eitehr OUI based or GW based. + * + * Returns: none + */ +static void fcoe_recv_flogi(struct fcoe_softc *fc, struct fc_frame *fp, u8 *sa) +{ + struct fc_frame_header *fh; + u8 op; + + fh = fc_frame_header_get(fp); + if (fh->fh_type != FC_TYPE_ELS) + return; + op = fc_frame_payload_op(fp); + if (op == ELS_LS_ACC && fh->fh_r_ctl == FC_RCTL_ELS_REP && + fc->flogi_oxid == ntohs(fh->fh_ox_id)) { + /* + * FLOGI accepted. + * If the src mac addr is FC_OUI-based, then we mark the + * address_mode flag to use FC_OUI-based Ethernet DA. + * Otherwise we use the FCoE gateway addr + */ + if (!compare_ether_addr(sa, (u8[6]) FC_FCOE_FLOGI_MAC)) { + fc->address_mode = FCOE_FCOUI_ADDR_MODE; + } else { + memcpy(fc->dest_addr, sa, ETH_ALEN); + fc->address_mode = FCOE_GW_ADDR_MODE; + } + + /* + * Remove any previously-set unicast MAC filter. + * Add secondary FCoE MAC address filter for our OUI. + */ + rtnl_lock(); + if (compare_ether_addr(fc->data_src_addr, (u8[6]) { 0 })) + dev_unicast_delete(fc->real_dev, fc->data_src_addr, + ETH_ALEN); + fc_fcoe_set_mac(fc->data_src_addr, fh->fh_d_id); + dev_unicast_add(fc->real_dev, fc->data_src_addr, ETH_ALEN); + rtnl_unlock(); + + fc->flogi_progress = 0; + } else if (op == ELS_FLOGI && fh->fh_r_ctl == FC_RCTL_ELS_REQ && sa) { + /* + * Save source MAC for point-to-point responses. + */ + memcpy(fc->dest_addr, sa, ETH_ALEN); + fc->address_mode = FCOE_GW_ADDR_MODE; + } +} + +/** + * fcoe_watchdog() - fcoe timer callback + * @vp: + * + * This checks the pending queue length for fcoe and set lport qfull + * if the FCOE_MAX_QUEUE_DEPTH is reached. This is done for all fc_lport on the + * fcoe_hostlist. + * + * Returns: 0 for success + */ +void fcoe_watchdog(ulong vp) +{ + struct fcoe_softc *fc; + + read_lock(&fcoe_hostlist_lock); + list_for_each_entry(fc, &fcoe_hostlist, list) { + if (fc->lp) + fcoe_check_wait_queue(fc->lp); + } + read_unlock(&fcoe_hostlist_lock); + + fcoe_timer.expires = jiffies + (1 * HZ); + add_timer(&fcoe_timer); +} + + +/** + * fcoe_check_wait_queue() - put the skb into fcoe pending xmit queue + * @lp: the fc_port for this skb + * @skb: the associated skb to be xmitted + * + * This empties the wait_queue, dequeue the head of the wait_queue queue + * and calls fcoe_start_io() for each packet, if all skb have been + * transmitted, return qlen or -1 if a error occurs, then restore + * wait_queue and try again later. + * + * The wait_queue is used when the skb transmit fails. skb will go + * in the wait_queue which will be emptied by the time function OR + * by the next skb transmit. + * + * Returns: 0 for success + */ +static int fcoe_check_wait_queue(struct fc_lport *lp) +{ + struct fcoe_softc *fc = lport_priv(lp); + struct sk_buff *skb; + int rc = -1; + + spin_lock_bh(&fc->fcoe_pending_queue.lock); + if (fc->fcoe_pending_queue_active) + goto out; + fc->fcoe_pending_queue_active = 1; + + while (fc->fcoe_pending_queue.qlen) { + /* keep qlen > 0 until fcoe_start_io succeeds */ + fc->fcoe_pending_queue.qlen++; + skb = __skb_dequeue(&fc->fcoe_pending_queue); + + spin_unlock_bh(&fc->fcoe_pending_queue.lock); + rc = fcoe_start_io(skb); + spin_lock_bh(&fc->fcoe_pending_queue.lock); + + if (rc) { + __skb_queue_head(&fc->fcoe_pending_queue, skb); + /* undo temporary increment above */ + fc->fcoe_pending_queue.qlen--; + break; + } + /* undo temporary increment above */ + fc->fcoe_pending_queue.qlen--; + } + + if (fc->fcoe_pending_queue.qlen < FCOE_LOW_QUEUE_DEPTH) + lp->qfull = 0; + fc->fcoe_pending_queue_active = 0; + rc = fc->fcoe_pending_queue.qlen; +out: + spin_unlock_bh(&fc->fcoe_pending_queue.lock); + return rc; +} + +/** + * fcoe_dev_setup() - setup link change notification interface + */ +static void fcoe_dev_setup() +{ + /* + * here setup a interface specific wd time to + * monitor the link state + */ + register_netdevice_notifier(&fcoe_notifier); +} + +/** + * fcoe_dev_setup() - cleanup link change notification interface + */ +static void fcoe_dev_cleanup(void) +{ + unregister_netdevice_notifier(&fcoe_notifier); +} + +/** + * fcoe_device_notification() - netdev event notification callback + * @notifier: context of the notification + * @event: type of event + * @ptr: fixed array for output parsed ifname + * + * This function is called by the ethernet driver in case of link change event + * + * Returns: 0 for success + */ +static int fcoe_device_notification(struct notifier_block *notifier, + ulong event, void *ptr) +{ + struct fc_lport *lp = NULL; + struct net_device *real_dev = ptr; + struct fcoe_softc *fc; + struct fcoe_dev_stats *stats; + u32 new_link_up; + u32 mfs; + int rc = NOTIFY_OK; + + read_lock(&fcoe_hostlist_lock); + list_for_each_entry(fc, &fcoe_hostlist, list) { + if (fc->real_dev == real_dev) { + lp = fc->lp; + break; + } + } + read_unlock(&fcoe_hostlist_lock); + if (lp == NULL) { + rc = NOTIFY_DONE; + goto out; + } + + new_link_up = lp->link_up; + switch (event) { + case NETDEV_DOWN: + case NETDEV_GOING_DOWN: + new_link_up = 0; + break; + case NETDEV_UP: + case NETDEV_CHANGE: + new_link_up = !fcoe_link_ok(lp); + break; + case NETDEV_CHANGEMTU: + mfs = fc->real_dev->mtu - + (sizeof(struct fcoe_hdr) + + sizeof(struct fcoe_crc_eof)); + if (mfs >= FC_MIN_MAX_FRAME) + fc_set_mfs(lp, mfs); + new_link_up = !fcoe_link_ok(lp); + break; + case NETDEV_REGISTER: + break; + default: + FC_DBG("unknown event %ld call", event); + } + if (lp->link_up != new_link_up) { + if (new_link_up) + fc_linkup(lp); + else { + stats = fc_lport_get_stats(lp); + stats->LinkFailureCount++; + fc_linkdown(lp); + fcoe_clean_pending_queue(lp); + } + } +out: + return rc; +} + +/** + * fcoe_if_to_netdev() - parse a name buffer to get netdev + * @ifname: fixed array for output parsed ifname + * @buffer: incoming buffer to be copied + * + * Returns: NULL or ptr to netdeive + */ +static struct net_device *fcoe_if_to_netdev(const char *buffer) +{ + char *cp; + char ifname[IFNAMSIZ + 2]; + + if (buffer) { + strlcpy(ifname, buffer, IFNAMSIZ); + cp = ifname + strlen(ifname); + while (--cp >= ifname && *cp == '\n') + *cp = '\0'; + return dev_get_by_name(&init_net, ifname); + } + return NULL; +} + +/** + * fcoe_netdev_to_module_owner() - finds out the nic drive moddule of the netdev + * @netdev: the target netdev + * + * Returns: ptr to the struct module, NULL for failure + */ +static struct module * +fcoe_netdev_to_module_owner(const struct net_device *netdev) +{ + struct device *dev; + + if (!netdev) + return NULL; + + dev = netdev->dev.parent; + if (!dev) + return NULL; + + if (!dev->driver) + return NULL; + + return dev->driver->owner; +} + +/** + * fcoe_ethdrv_get() - Hold the Ethernet driver + * @netdev: the target netdev + * + * Holds the Ethernet driver module by try_module_get() for + * the corresponding netdev. + * + * Returns: 0 for succsss + */ +static int fcoe_ethdrv_get(const struct net_device *netdev) +{ + struct module *owner; + + owner = fcoe_netdev_to_module_owner(netdev); + if (owner) { + printk(KERN_DEBUG "fcoe:hold driver module %s for %s\n", + module_name(owner), netdev->name); + return try_module_get(owner); + } + return -ENODEV; +} + +/** + * fcoe_ethdrv_put() - Release the Ethernet driver + * @netdev: the target netdev + * + * Releases the Ethernet driver module by module_put for + * the corresponding netdev. + * + * Returns: 0 for succsss + */ +static int fcoe_ethdrv_put(const struct net_device *netdev) +{ + struct module *owner; + + owner = fcoe_netdev_to_module_owner(netdev); + if (owner) { + printk(KERN_DEBUG "fcoe:release driver module %s for %s\n", + module_name(owner), netdev->name); + module_put(owner); + return 0; + } + return -ENODEV; +} + +/** + * fcoe_destroy() - handles the destroy from sysfs + * @buffer: expcted to be a eth if name + * @kp: associated kernel param + * + * Returns: 0 for success + */ +static int fcoe_destroy(const char *buffer, struct kernel_param *kp) +{ + int rc; + struct net_device *netdev; + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) { + rc = -ENODEV; + goto out_nodev; + } + /* look for existing lport */ + if (!fcoe_hostlist_lookup(netdev)) { + rc = -ENODEV; + goto out_putdev; + } + rc = fcoe_if_destroy(netdev); + if (rc) { + printk(KERN_ERR "fcoe: fcoe_if_destroy(%s) failed\n", + netdev->name); + rc = -EIO; + goto out_putdev; + } + fcoe_ethdrv_put(netdev); + rc = 0; +out_putdev: + dev_put(netdev); +out_nodev: + return rc; +} + +/** + * fcoe_create() - Handles the create call from sysfs + * @buffer: expcted to be a eth if name + * @kp: associated kernel param + * + * Returns: 0 for success + */ +static int fcoe_create(const char *buffer, struct kernel_param *kp) +{ + int rc; + struct net_device *netdev; + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) { + rc = -ENODEV; + goto out_nodev; + } + /* look for existing lport */ + if (fcoe_hostlist_lookup(netdev)) { + rc = -EEXIST; + goto out_putdev; + } + fcoe_ethdrv_get(netdev); + + rc = fcoe_if_create(netdev); + if (rc) { + printk(KERN_ERR "fcoe: fcoe_if_create(%s) failed\n", + netdev->name); + fcoe_ethdrv_put(netdev); + rc = -EIO; + goto out_putdev; + } + rc = 0; +out_putdev: + dev_put(netdev); +out_nodev: + return rc; +} + +module_param_call(create, fcoe_create, NULL, NULL, S_IWUSR); +__MODULE_PARM_TYPE(create, "string"); +MODULE_PARM_DESC(create, "Create fcoe port using net device passed in."); +module_param_call(destroy, fcoe_destroy, NULL, NULL, S_IWUSR); +__MODULE_PARM_TYPE(destroy, "string"); +MODULE_PARM_DESC(destroy, "Destroy fcoe port"); + +/** + * fcoe_link_ok() - Check if link is ok for the fc_lport + * @lp: ptr to the fc_lport + * + * Any permanently-disqualifying conditions have been previously checked. + * This also updates the speed setting, which may change with link for 100/1000. + * + * This function should probably be checking for PAUSE support at some point + * in the future. Currently Per-priority-pause is not determinable using + * ethtool, so we shouldn't be restrictive until that problem is resolved. + * + * Returns: 0 if link is OK for use by FCoE. + * + */ +int fcoe_link_ok(struct fc_lport *lp) +{ + struct fcoe_softc *fc = lport_priv(lp); + struct net_device *dev = fc->real_dev; + struct ethtool_cmd ecmd = { ETHTOOL_GSET }; + int rc = 0; + + if ((dev->flags & IFF_UP) && netif_carrier_ok(dev)) { + dev = fc->phys_dev; + if (dev->ethtool_ops->get_settings) { + dev->ethtool_ops->get_settings(dev, &ecmd); + lp->link_supported_speeds &= + ~(FC_PORTSPEED_1GBIT | FC_PORTSPEED_10GBIT); + if (ecmd.supported & (SUPPORTED_1000baseT_Half | + SUPPORTED_1000baseT_Full)) + lp->link_supported_speeds |= FC_PORTSPEED_1GBIT; + if (ecmd.supported & SUPPORTED_10000baseT_Full) + lp->link_supported_speeds |= + FC_PORTSPEED_10GBIT; + if (ecmd.speed == SPEED_1000) + lp->link_speed = FC_PORTSPEED_1GBIT; + if (ecmd.speed == SPEED_10000) + lp->link_speed = FC_PORTSPEED_10GBIT; + } + } else + rc = -1; + + return rc; +} +EXPORT_SYMBOL_GPL(fcoe_link_ok); + +/** + * fcoe_percpu_clean() - Clear the pending skbs for an lport + * @lp: the fc_lport + */ +void fcoe_percpu_clean(struct fc_lport *lp) +{ + struct fcoe_percpu_s *pp; + struct fcoe_rcv_info *fr; + struct sk_buff_head *list; + struct sk_buff *skb, *next; + struct sk_buff *head; + unsigned int cpu; + + for_each_possible_cpu(cpu) { + pp = &per_cpu(fcoe_percpu, cpu); + spin_lock_bh(&pp->fcoe_rx_list.lock); + list = &pp->fcoe_rx_list; + head = list->next; + for (skb = head; skb != (struct sk_buff *)list; + skb = next) { + next = skb->next; + fr = fcoe_dev_from_skb(skb); + if (fr->fr_dev == lp) { + __skb_unlink(skb, list); + kfree_skb(skb); + } + } + spin_unlock_bh(&pp->fcoe_rx_list.lock); + } +} +EXPORT_SYMBOL_GPL(fcoe_percpu_clean); + +/** + * fcoe_clean_pending_queue() - Dequeue a skb and free it + * @lp: the corresponding fc_lport + * + * Returns: none + */ +void fcoe_clean_pending_queue(struct fc_lport *lp) +{ + struct fcoe_softc *fc = lport_priv(lp); + struct sk_buff *skb; + + spin_lock_bh(&fc->fcoe_pending_queue.lock); + while ((skb = __skb_dequeue(&fc->fcoe_pending_queue)) != NULL) { + spin_unlock_bh(&fc->fcoe_pending_queue.lock); + kfree_skb(skb); + spin_lock_bh(&fc->fcoe_pending_queue.lock); + } + spin_unlock_bh(&fc->fcoe_pending_queue.lock); +} +EXPORT_SYMBOL_GPL(fcoe_clean_pending_queue); + +/** + * libfc_host_alloc() - Allocate a Scsi_Host with room for the fc_lport + * @sht: ptr to the scsi host templ + * @priv_size: size of private data after fc_lport + * + * Returns: ptr to Scsi_Host + * TODO: to libfc? + */ +static inline struct Scsi_Host * +libfc_host_alloc(struct scsi_host_template *sht, int priv_size) +{ + return scsi_host_alloc(sht, sizeof(struct fc_lport) + priv_size); +} + +/** + * fcoe_host_alloc() - Allocate a Scsi_Host with room for the fcoe_softc + * @sht: ptr to the scsi host templ + * @priv_size: size of private data after fc_lport + * + * Returns: ptr to Scsi_Host + */ +struct Scsi_Host *fcoe_host_alloc(struct scsi_host_template *sht, int priv_size) +{ + return libfc_host_alloc(sht, sizeof(struct fcoe_softc) + priv_size); +} +EXPORT_SYMBOL_GPL(fcoe_host_alloc); + +/** + * fcoe_reset() - Resets the fcoe + * @shost: shost the reset is from + * + * Returns: always 0 + */ +int fcoe_reset(struct Scsi_Host *shost) +{ + struct fc_lport *lport = shost_priv(shost); + fc_lport_reset(lport); + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_reset); + +/** + * fcoe_wwn_from_mac() - Converts 48-bit IEEE MAC address to 64-bit FC WWN. + * @mac: mac address + * @scheme: check port + * @port: port indicator for converting + * + * Returns: u64 fc world wide name + */ +u64 fcoe_wwn_from_mac(unsigned char mac[MAX_ADDR_LEN], + unsigned int scheme, unsigned int port) +{ + u64 wwn; + u64 host_mac; + + /* The MAC is in NO, so flip only the low 48 bits */ + host_mac = ((u64) mac[0] << 40) | + ((u64) mac[1] << 32) | + ((u64) mac[2] << 24) | + ((u64) mac[3] << 16) | + ((u64) mac[4] << 8) | + (u64) mac[5]; + + WARN_ON(host_mac >= (1ULL << 48)); + wwn = host_mac | ((u64) scheme << 60); + switch (scheme) { + case 1: + WARN_ON(port != 0); + break; + case 2: + WARN_ON(port >= 0xfff); + wwn |= (u64) port << 48; + break; + default: + WARN_ON(1); + break; + } + + return wwn; +} +EXPORT_SYMBOL_GPL(fcoe_wwn_from_mac); + +/** + * fcoe_hostlist_lookup_softc() - find the corresponding lport by a given device + * @device: this is currently ptr to net_device + * + * Returns: NULL or the located fcoe_softc + */ +static struct fcoe_softc * +fcoe_hostlist_lookup_softc(const struct net_device *dev) +{ + struct fcoe_softc *fc; + + read_lock(&fcoe_hostlist_lock); + list_for_each_entry(fc, &fcoe_hostlist, list) { + if (fc->real_dev == dev) { + read_unlock(&fcoe_hostlist_lock); + return fc; + } + } + read_unlock(&fcoe_hostlist_lock); + return NULL; +} + +/** + * fcoe_hostlist_lookup() - Find the corresponding lport by netdev + * @netdev: ptr to net_device + * + * Returns: 0 for success + */ +struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev) +{ + struct fcoe_softc *fc; + + fc = fcoe_hostlist_lookup_softc(netdev); + + return (fc) ? fc->lp : NULL; +} +EXPORT_SYMBOL_GPL(fcoe_hostlist_lookup); + +/** + * fcoe_hostlist_add() - Add a lport to lports list + * @lp: ptr to the fc_lport to badded + * + * Returns: 0 for success + */ +int fcoe_hostlist_add(const struct fc_lport *lp) +{ + struct fcoe_softc *fc; + + fc = fcoe_hostlist_lookup_softc(fcoe_netdev(lp)); + if (!fc) { + fc = lport_priv(lp); + write_lock_bh(&fcoe_hostlist_lock); + list_add_tail(&fc->list, &fcoe_hostlist); + write_unlock_bh(&fcoe_hostlist_lock); + } + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_hostlist_add); + +/** + * fcoe_hostlist_remove() - remove a lport from lports list + * @lp: ptr to the fc_lport to badded + * + * Returns: 0 for success + */ +int fcoe_hostlist_remove(const struct fc_lport *lp) +{ + struct fcoe_softc *fc; + + fc = fcoe_hostlist_lookup_softc(fcoe_netdev(lp)); + BUG_ON(!fc); + write_lock_bh(&fcoe_hostlist_lock); + list_del(&fc->list); + write_unlock_bh(&fcoe_hostlist_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_hostlist_remove); + +/** + * fcoe_libfc_config() - sets up libfc related properties for lport + * @lp: ptr to the fc_lport + * @tt: libfc function template + * + * Returns : 0 for success + */ +int fcoe_libfc_config(struct fc_lport *lp, struct libfc_function_template *tt) +{ + /* Set the function pointers set by the LLDD */ + memcpy(&lp->tt, tt, sizeof(*tt)); + if (fc_fcp_init(lp)) + return -ENOMEM; + fc_exch_init(lp); + fc_elsct_init(lp); + fc_lport_init(lp); + fc_rport_init(lp); + fc_disc_init(lp); + + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_libfc_config); + +/** + * fcoe_init() - fcoe module loading initialization + * + * Returns 0 on success, negative on failure + */ +static int __init fcoe_init(void) +{ + unsigned int cpu; + int rc = 0; + struct fcoe_percpu_s *p; + + INIT_LIST_HEAD(&fcoe_hostlist); + rwlock_init(&fcoe_hostlist_lock); + + for_each_possible_cpu(cpu) { + p = &per_cpu(fcoe_percpu, cpu); + skb_queue_head_init(&p->fcoe_rx_list); + } + + for_each_online_cpu(cpu) + fcoe_percpu_thread_create(cpu); + + /* Initialize per CPU interrupt thread */ + rc = register_hotcpu_notifier(&fcoe_cpu_notifier); + if (rc) + goto out_free; + + /* Setup link change notification */ + fcoe_dev_setup(); + + setup_timer(&fcoe_timer, fcoe_watchdog, 0); + + mod_timer(&fcoe_timer, jiffies + (10 * HZ)); + + fcoe_if_init(); + + return 0; + +out_free: + for_each_online_cpu(cpu) { + fcoe_percpu_thread_destroy(cpu); + } + + return rc; +} +module_init(fcoe_init); + +/** + * fcoe_exit() - fcoe module unloading cleanup + * + * Returns 0 on success, negative on failure + */ +static void __exit fcoe_exit(void) +{ + unsigned int cpu; + struct fcoe_softc *fc, *tmp; + + fcoe_dev_cleanup(); + + /* Stop the timer */ + del_timer_sync(&fcoe_timer); + + /* releases the associated fcoe hosts */ + list_for_each_entry_safe(fc, tmp, &fcoe_hostlist, list) + fcoe_if_destroy(fc->real_dev); + + unregister_hotcpu_notifier(&fcoe_cpu_notifier); + + for_each_online_cpu(cpu) { + fcoe_percpu_thread_destroy(cpu); + } +} +module_exit(fcoe_exit); diff --git a/drivers/scsi/fcoe/libfcoe.c b/drivers/scsi/fcoe/libfcoe.c deleted file mode 100644 index cbebbf7..0000000 --- a/drivers/scsi/fcoe/libfcoe.c +++ /dev/null @@ -1,1972 +0,0 @@ -/* - * 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> - -static int debug_fcoe; - -#define FCOE_MAX_QUEUE_DEPTH 256 -#define FCOE_LOW_QUEUE_DEPTH 32 - -/* destination address mode */ -#define FCOE_GW_ADDR_MODE 0x00 -#define FCOE_FCOUI_ADDR_MODE 0x01 - -#define FCOE_WORD_TO_BYTE 4 - -#define FCOE_VERSION "0.1" -#define FCOE_NAME "fcoe" -#define FCOE_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 */ - -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); -DEFINE_PER_CPU(struct fcoe_percpu_s, fcoe_percpu); - - -/* Function Prototyes */ -static int fcoe_check_wait_queue(struct fc_lport *); -static void fcoe_recv_flogi(struct fcoe_softc *, struct fc_frame *, u8 *); -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, -}; - -static struct scsi_transport_template *scsi_transport_fcoe_sw; - -struct fc_function_template fcoe_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_shost_template = { - .module = THIS_MODULE, - .name = "FCoE Driver", - .proc_name = FCOE_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_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_lport_config(struct fc_lport *lp) -{ - lp->link_up = 0; - lp->qfull = 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); - - fc_lport_init_stats(lp); - - /* lport fc_lport related configuration */ - fc_lport_config(lp); - - /* offload related configuration */ - lp->crc_offload = 0; - lp->seq_offload = 0; - lp->lro_enabled = 0; - lp->lro_xid = 0; - lp->lso_max = 0; - - return 0; -} - -/** - * fcoe_netdev_config() - Set up netdev for SW FCoE - * @lp : ptr to the fc_lport - * @netdev : ptr to the associated netdevice struct - * - * Must be called after fcoe_lport_config() as it will use lport mutex - * - * Returns : 0 for success - */ -static int fcoe_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; - - if (!fcoe_link_ok(lp)) - lp->link_up = 1; - - /* offload features support */ - if (fc->real_dev->features & NETIF_F_SG) - lp->sg_supp = 1; - -#ifdef NETIF_F_FCOE_CRC - if (netdev->features & NETIF_F_FCOE_CRC) { - lp->crc_offload = 1; - printk(KERN_DEBUG "fcoe:%s supports FCCRC offload\n", - netdev->name); - } -#endif -#ifdef NETIF_F_FSO - if (netdev->features & NETIF_F_FSO) { - lp->seq_offload = 1; - lp->lso_max = netdev->gso_max_size; - printk(KERN_DEBUG "fcoe:%s supports LSO for max len 0x%x\n", - netdev->name, lp->lso_max); - } -#endif - if (netdev->fcoe_ddp_xid) { - lp->lro_enabled = 1; - lp->lro_xid = netdev->fcoe_ddp_xid; - printk(KERN_DEBUG "fcoe:%s supports LRO for max xid 0x%x\n", - netdev->name, lp->lro_xid); - } - skb_queue_head_init(&fc->fcoe_pending_queue); - fc->fcoe_pending_queue_active = 0; - - /* 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_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_lport_config() and fcoe_netdev_config() - * - * Returns : 0 for success - */ -static int fcoe_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_shost_config:error on scsi_add_host\n"); - return rc; - } - sprintf(fc_host_symbolic_name(lp->host), "%s v%s over %s", - FCOE_NAME, FCOE_VERSION, - fcoe_netdev(lp)->name); - - return 0; -} - -/** - * fcoe_em_config() - allocates em for this lport - * @lp: the port that em is to allocated for - * - * Returns : 0 on success - */ -static inline int fcoe_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_if_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_if_destroy(struct net_device *netdev) -{ - struct fc_lport *lp = NULL; - struct fcoe_softc *fc; - u8 flogi_maddr[ETH_ALEN]; - - BUG_ON(!netdev); - - printk(KERN_DEBUG "fcoe_if_destroy:interface on %s\n", - netdev->name); - - lp = fcoe_hostlist_lookup(netdev); - if (!lp) - return -ENODEV; - - fc = lport_priv(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 */ - fc_lport_free_stats(lp); - - /* Release the net_device and Scsi_Host */ - dev_put(fc->real_dev); - scsi_host_put(lp->host); - - return 0; -} - -/* - * fcoe_ddp_setup - calls LLD's ddp_setup through net_device - * @lp: the corresponding fc_lport - * @xid: the exchange id for this ddp transfer - * @sgl: the scatterlist describing this transfer - * @sgc: number of sg items - * - * Returns : 0 no ddp - */ -static int fcoe_ddp_setup(struct fc_lport *lp, u16 xid, - struct scatterlist *sgl, unsigned int sgc) -{ - struct net_device *n = fcoe_netdev(lp); - - if (n->netdev_ops && n->netdev_ops->ndo_fcoe_ddp_setup) - return n->netdev_ops->ndo_fcoe_ddp_setup(n, xid, sgl, sgc); - - return 0; -} - -/* - * fcoe_ddp_done - calls LLD's ddp_done through net_device - * @lp: the corresponding fc_lport - * @xid: the exchange id for this ddp transfer - * - * Returns : the length of data that have been completed by ddp - */ -static int fcoe_ddp_done(struct fc_lport *lp, u16 xid) -{ - struct net_device *n = fcoe_netdev(lp); - - if (n->netdev_ops && n->netdev_ops->ndo_fcoe_ddp_done) - return n->netdev_ops->ndo_fcoe_ddp_done(n, xid); - return 0; -} - -static struct libfc_function_template fcoe_libfc_fcn_templ = { - .frame_send = fcoe_xmit, - .ddp_setup = fcoe_ddp_setup, - .ddp_done = fcoe_ddp_done, -}; - -/** - * fcoe_if_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_if_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_if_create:interface on %s\n", - netdev->name); - - lp = fcoe_hostlist_lookup(netdev); - if (lp) - return -EEXIST; - - shost = fcoe_host_alloc(&fcoe_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_lport_config(lp); - if (rc) { - FC_DBG("Could not configure lport\n"); - goto out_host_put; - } - - /* configure lport network properties */ - rc = fcoe_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_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_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_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_if_init() - attach to scsi transport - * - * Returns : 0 on success - */ -static int __init fcoe_if_init(void) -{ - /* attach to scsi transport */ - scsi_transport_fcoe_sw = - fc_attach_transport(&fcoe_transport_function); - - if (!scsi_transport_fcoe_sw) { - printk(KERN_ERR "fcoe_init:fc_attach_transport() failed\n"); - return -ENODEV; - } - - return 0; -} - -/** - * fcoe_percpu_thread_create() - Create a receive thread for an online cpu - * @cpu: cpu index for the online cpu - */ -static void fcoe_percpu_thread_create(unsigned int cpu) -{ - struct fcoe_percpu_s *p; - struct task_struct *thread; - - p = &per_cpu(fcoe_percpu, cpu); - - thread = kthread_create(fcoe_percpu_receive_thread, - (void *)p, "fcoethread/%d", cpu); - - if (likely(!IS_ERR(p->thread))) { - kthread_bind(thread, cpu); - wake_up_process(thread); - - spin_lock_bh(&p->fcoe_rx_list.lock); - p->thread = thread; - spin_unlock_bh(&p->fcoe_rx_list.lock); - } -} - -/** - * fcoe_percpu_thread_destroy() - removes the rx thread for the given cpu - * @cpu: cpu index the rx thread is to be removed - * - * Destroys a per-CPU Rx thread. Any pending skbs are moved to the - * current CPU's Rx thread. If the thread being destroyed is bound to - * the CPU processing this context the skbs will be freed. - */ -static void fcoe_percpu_thread_destroy(unsigned int cpu) -{ - struct fcoe_percpu_s *p; - struct task_struct *thread; - struct page *crc_eof; - struct sk_buff *skb; -#ifdef CONFIG_SMP - struct fcoe_percpu_s *p0; - unsigned targ_cpu = smp_processor_id(); -#endif /* CONFIG_SMP */ - - printk(KERN_DEBUG "fcoe: Destroying receive thread for CPU %d\n", cpu); - - /* Prevent any new skbs from being queued for this CPU. */ - p = &per_cpu(fcoe_percpu, cpu); - spin_lock_bh(&p->fcoe_rx_list.lock); - thread = p->thread; - p->thread = NULL; - crc_eof = p->crc_eof_page; - p->crc_eof_page = NULL; - p->crc_eof_offset = 0; - spin_unlock_bh(&p->fcoe_rx_list.lock); - -#ifdef CONFIG_SMP - /* - * Don't bother moving the skb's if this context is running - * on the same CPU that is having its thread destroyed. This - * can easily happen when the module is removed. - */ - if (cpu != targ_cpu) { - p0 = &per_cpu(fcoe_percpu, targ_cpu); - spin_lock_bh(&p0->fcoe_rx_list.lock); - if (p0->thread) { - FC_DBG("Moving frames from CPU %d to CPU %d\n", - cpu, targ_cpu); - - while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) - __skb_queue_tail(&p0->fcoe_rx_list, skb); - spin_unlock_bh(&p0->fcoe_rx_list.lock); - } else { - /* - * The targeted CPU is not initialized and cannot accept - * new skbs. Unlock the targeted CPU and drop the skbs - * on the CPU that is going offline. - */ - while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) - kfree_skb(skb); - spin_unlock_bh(&p0->fcoe_rx_list.lock); - } - } else { - /* - * This scenario occurs when the module is being removed - * and all threads are being destroyed. skbs will continue - * to be shifted from the CPU thread that is being removed - * to the CPU thread associated with the CPU that is processing - * the module removal. Once there is only one CPU Rx thread it - * will reach this case and we will drop all skbs and later - * stop the thread. - */ - spin_lock_bh(&p->fcoe_rx_list.lock); - while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) - kfree_skb(skb); - spin_unlock_bh(&p->fcoe_rx_list.lock); - } -#else - /* - * This a non-SMP scenario where the singluar Rx thread is - * being removed. Free all skbs and stop the thread. - */ - spin_lock_bh(&p->fcoe_rx_list.lock); - while ((skb = __skb_dequeue(&p->fcoe_rx_list)) != NULL) - kfree_skb(skb); - spin_unlock_bh(&p->fcoe_rx_list.lock); -#endif - - if (thread) - kthread_stop(thread); - - if (crc_eof) - put_page(crc_eof); -} - -/** - * 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 cpu = (unsigned long)hcpu; - - switch (action) { - case CPU_ONLINE: - case CPU_ONLINE_FROZEN: - FC_DBG("CPU %x online: Create Rx thread\n", cpu); - fcoe_percpu_thread_create(cpu); - break; - case CPU_DEAD: - case CPU_DEAD_FROZEN: - FC_DBG("CPU %x offline: Remove Rx thread\n", cpu); - fcoe_percpu_thread_destroy(cpu); - break; - default: - break; - } - return NOTIFY_OK; -} - -static struct notifier_block fcoe_cpu_notifier = { - .notifier_call = fcoe_cpu_callback, -}; - -/** - * fcoe_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 - * - * Returns: 0 for success - */ -int fcoe_rcv(struct sk_buff *skb, struct net_device *dev, - struct packet_type *ptype, struct net_device *olddev) -{ - struct fc_lport *lp; - struct fcoe_rcv_info *fr; - struct fcoe_softc *fc; - struct fc_frame_header *fh; - struct fcoe_percpu_s *fps; - unsigned short oxid; - unsigned int cpu = 0; - - fc = container_of(ptype, struct fcoe_softc, fcoe_packet_type); - lp = fc->lp; - if (unlikely(lp == NULL)) { - FC_DBG("cannot find hba structure"); - goto err2; - } - - if (unlikely(debug_fcoe)) { - FC_DBG("skb_info: len:%d data_len:%d head:%p data:%p tail:%p " - "end:%p sum:%d dev:%s", skb->len, skb->data_len, - skb->head, skb->data, skb_tail_pointer(skb), - skb_end_pointer(skb), skb->csum, - skb->dev ? skb->dev->name : "<NULL>"); - - } - - /* check for FCOE packet type */ - if (unlikely(eth_hdr(skb)->h_proto != htons(ETH_P_FCOE))) { - FC_DBG("wrong FC type frame"); - goto err; - } - - /* - * Check for minimum frame length, and make sure required FCoE - * and FC headers are pulled into the linear data area. - */ - if (unlikely((skb->len < FCOE_MIN_FRAME) || - !pskb_may_pull(skb, FCOE_HEADER_LEN))) - goto err; - - skb_set_transport_header(skb, sizeof(struct fcoe_hdr)); - fh = (struct fc_frame_header *) skb_transport_header(skb); - - oxid = ntohs(fh->fh_ox_id); - - fr = fcoe_dev_from_skb(skb); - fr->fr_dev = lp; - fr->ptype = ptype; - -#ifdef CONFIG_SMP - /* - * The incoming frame exchange id(oxid) is ANDed with num of online - * cpu bits to get cpu and then this cpu is used for selecting - * a per cpu kernel thread from fcoe_percpu. - */ - cpu = oxid & (num_online_cpus() - 1); -#endif - - fps = &per_cpu(fcoe_percpu, cpu); - spin_lock_bh(&fps->fcoe_rx_list.lock); - if (unlikely(!fps->thread)) { - /* - * The targeted CPU is not ready, let's target - * the first CPU now. For non-SMP systems this - * will check the same CPU twice. - */ - FC_DBG("CPU is online, but no receive thread ready " - "for incoming skb- using first online CPU.\n"); - - spin_unlock_bh(&fps->fcoe_rx_list.lock); - cpu = first_cpu(cpu_online_map); - fps = &per_cpu(fcoe_percpu, cpu); - spin_lock_bh(&fps->fcoe_rx_list.lock); - if (!fps->thread) { - spin_unlock_bh(&fps->fcoe_rx_list.lock); - goto err; - } - } - - /* - * We now have a valid CPU that we're targeting for - * this skb. We also have this receive thread locked, - * so we're free to queue skbs into it's queue. - */ - __skb_queue_tail(&fps->fcoe_rx_list, skb); - if (fps->fcoe_rx_list.qlen == 1) - wake_up_process(fps->thread); - - spin_unlock_bh(&fps->fcoe_rx_list.lock); - - return 0; -err: - fc_lport_get_stats(lp)->ErrorFrames++; - -err2: - kfree_skb(skb); - return -1; -} -EXPORT_SYMBOL_GPL(fcoe_rcv); - -/** - * fcoe_start_io() - pass to netdev to start xmit for fcoe - * @skb: the skb to be xmitted - * - * Returns: 0 for success - */ -static inline int fcoe_start_io(struct sk_buff *skb) -{ - int rc; - - skb_get(skb); - rc = dev_queue_xmit(skb); - if (rc != 0) - return rc; - kfree_skb(skb); - return 0; -} - -/** - * fcoe_get_paged_crc_eof() - in case we need alloc a page for crc_eof - * @skb: the skb to be xmitted - * @tlen: total len - * - * Returns: 0 for success - */ -static int fcoe_get_paged_crc_eof(struct sk_buff *skb, int tlen) -{ - struct fcoe_percpu_s *fps; - struct page *page; - - fps = &get_cpu_var(fcoe_percpu); - page = fps->crc_eof_page; - if (!page) { - page = alloc_page(GFP_ATOMIC); - if (!page) { - put_cpu_var(fcoe_percpu); - return -ENOMEM; - } - fps->crc_eof_page = page; - fps->crc_eof_offset = 0; - } - - get_page(page); - skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page, - fps->crc_eof_offset, tlen); - skb->len += tlen; - skb->data_len += tlen; - skb->truesize += tlen; - fps->crc_eof_offset += sizeof(struct fcoe_crc_eof); - - if (fps->crc_eof_offset >= PAGE_SIZE) { - fps->crc_eof_page = NULL; - fps->crc_eof_offset = 0; - put_page(page); - } - put_cpu_var(fcoe_percpu); - return 0; -} - -/** - * fcoe_fc_crc() - calculates FC CRC in this fcoe skb - * @fp: the fc_frame containg data to be checksummed - * - * This uses crc32() to calculate the crc for fc frame - * Return : 32 bit crc - */ -u32 fcoe_fc_crc(struct fc_frame *fp) -{ - struct sk_buff *skb = fp_skb(fp); - struct skb_frag_struct *frag; - unsigned char *data; - unsigned long off, len, clen; - u32 crc; - unsigned i; - - crc = crc32(~0, skb->data, skb_headlen(skb)); - - for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { - frag = &skb_shinfo(skb)->frags[i]; - off = frag->page_offset; - len = frag->size; - while (len > 0) { - clen = min(len, PAGE_SIZE - (off & ~PAGE_MASK)); - data = kmap_atomic(frag->page + (off >> PAGE_SHIFT), - KM_SKB_DATA_SOFTIRQ); - crc = crc32(crc, data + (off & ~PAGE_MASK), clen); - kunmap_atomic(data, KM_SKB_DATA_SOFTIRQ); - off += clen; - len -= clen; - } - } - return crc; -} -EXPORT_SYMBOL_GPL(fcoe_fc_crc); - -/** - * fcoe_xmit() - FCoE frame transmit function - * @lp: the associated local port - * @fp: the fc_frame to be transmitted - * - * Return : 0 for success - */ -int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) -{ - int wlen, rc = 0; - u32 crc; - struct ethhdr *eh; - struct fcoe_crc_eof *cp; - struct sk_buff *skb; - struct fcoe_dev_stats *stats; - struct fc_frame_header *fh; - unsigned int hlen; /* header length implies the version */ - unsigned int tlen; /* trailer length */ - unsigned int elen; /* eth header, may include vlan */ - int flogi_in_progress = 0; - struct fcoe_softc *fc; - u8 sof, eof; - struct fcoe_hdr *hp; - - WARN_ON((fr_len(fp) % sizeof(u32)) != 0); - - fc = lport_priv(lp); - /* - * if it is a flogi then we need to learn gw-addr - * and my own fcid - */ - fh = fc_frame_header_get(fp); - if (unlikely(fh->fh_r_ctl == FC_RCTL_ELS_REQ)) { - if (fc_frame_payload_op(fp) == ELS_FLOGI) { - fc->flogi_oxid = ntohs(fh->fh_ox_id); - fc->address_mode = FCOE_FCOUI_ADDR_MODE; - fc->flogi_progress = 1; - flogi_in_progress = 1; - } else if (fc->flogi_progress && ntoh24(fh->fh_s_id) != 0) { - /* - * Here we must've gotten an SID by accepting an FLOGI - * from a point-to-point connection. Switch to using - * the source mac based on the SID. The destination - * MAC in this case would have been set by receving the - * FLOGI. - */ - fc_fcoe_set_mac(fc->data_src_addr, fh->fh_s_id); - fc->flogi_progress = 0; - } - } - - skb = fp_skb(fp); - sof = fr_sof(fp); - eof = fr_eof(fp); - - elen = (fc->real_dev->priv_flags & IFF_802_1Q_VLAN) ? - sizeof(struct vlan_ethhdr) : sizeof(struct ethhdr); - hlen = sizeof(struct fcoe_hdr); - tlen = sizeof(struct fcoe_crc_eof); - wlen = (skb->len - tlen + sizeof(crc)) / FCOE_WORD_TO_BYTE; - - /* crc offload */ - if (likely(lp->crc_offload)) { - skb->ip_summed = CHECKSUM_PARTIAL; - skb->csum_start = skb_headroom(skb); - skb->csum_offset = skb->len; - crc = 0; - } else { - skb->ip_summed = CHECKSUM_NONE; - crc = fcoe_fc_crc(fp); - } - - /* copy fc crc and eof to the skb buff */ - if (skb_is_nonlinear(skb)) { - skb_frag_t *frag; - if (fcoe_get_paged_crc_eof(skb, tlen)) { - kfree_skb(skb); - return -ENOMEM; - } - frag = &skb_shinfo(skb)->frags[skb_shinfo(skb)->nr_frags - 1]; - cp = kmap_atomic(frag->page, KM_SKB_DATA_SOFTIRQ) - + frag->page_offset; - } else { - cp = (struct fcoe_crc_eof *)skb_put(skb, tlen); - } - - memset(cp, 0, sizeof(*cp)); - cp->fcoe_eof = eof; - cp->fcoe_crc32 = cpu_to_le32(~crc); - - if (skb_is_nonlinear(skb)) { - kunmap_atomic(cp, KM_SKB_DATA_SOFTIRQ); - cp = NULL; - } - - /* adjust skb netowrk/transport offsets to match mac/fcoe/fc */ - skb_push(skb, elen + hlen); - skb_reset_mac_header(skb); - skb_reset_network_header(skb); - skb->mac_len = elen; - skb->protocol = htons(ETH_P_FCOE); - skb->dev = fc->real_dev; - - /* fill up mac and fcoe headers */ - eh = eth_hdr(skb); - eh->h_proto = htons(ETH_P_FCOE); - if (fc->address_mode == FCOE_FCOUI_ADDR_MODE) - fc_fcoe_set_mac(eh->h_dest, fh->fh_d_id); - else - /* insert GW address */ - memcpy(eh->h_dest, fc->dest_addr, ETH_ALEN); - - if (unlikely(flogi_in_progress)) - memcpy(eh->h_source, fc->ctl_src_addr, ETH_ALEN); - else - memcpy(eh->h_source, fc->data_src_addr, ETH_ALEN); - - hp = (struct fcoe_hdr *)(eh + 1); - memset(hp, 0, sizeof(*hp)); - if (FC_FCOE_VER) - FC_FCOE_ENCAPS_VER(hp, FC_FCOE_VER); - hp->fcoe_sof = sof; - -#ifdef NETIF_F_FSO - /* fcoe lso, mss is in max_payload which is non-zero for FCP data */ - if (lp->seq_offload && fr_max_payload(fp)) { - skb_shinfo(skb)->gso_type = SKB_GSO_FCOE; - skb_shinfo(skb)->gso_size = fr_max_payload(fp); - } else { - skb_shinfo(skb)->gso_type = 0; - skb_shinfo(skb)->gso_size = 0; - } -#endif - /* update tx stats: regardless if LLD fails */ - stats = fc_lport_get_stats(lp); - stats->TxFrames++; - stats->TxWords += wlen; - - /* send down to lld */ - fr_dev(fp) = lp; - if (fc->fcoe_pending_queue.qlen) - rc = fcoe_check_wait_queue(lp); - - if (rc == 0) - rc = fcoe_start_io(skb); - - if (rc) { - spin_lock_bh(&fc->fcoe_pending_queue.lock); - __skb_queue_tail(&fc->fcoe_pending_queue, skb); - spin_unlock_bh(&fc->fcoe_pending_queue.lock); - if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH) - lp->qfull = 1; - } - - return 0; -} -EXPORT_SYMBOL_GPL(fcoe_xmit); - -/** - * fcoe_percpu_receive_thread() - recv thread per cpu - * @arg: ptr to the fcoe per cpu struct - * - * Return: 0 for success - */ -int fcoe_percpu_receive_thread(void *arg) -{ - struct fcoe_percpu_s *p = arg; - u32 fr_len; - struct fc_lport *lp; - struct fcoe_rcv_info *fr; - struct fcoe_dev_stats *stats; - struct fc_frame_header *fh; - struct sk_buff *skb; - struct fcoe_crc_eof crc_eof; - struct fc_frame *fp; - u8 *mac = NULL; - struct fcoe_softc *fc; - struct fcoe_hdr *hp; - - set_user_nice(current, -20); - - while (!kthread_should_stop()) { - - spin_lock_bh(&p->fcoe_rx_list.lock); - while ((skb = __skb_dequeue(&p->fcoe_rx_list)) == NULL) { - set_current_state(TASK_INTERRUPTIBLE); - spin_unlock_bh(&p->fcoe_rx_list.lock); - schedule(); - set_current_state(TASK_RUNNING); - if (kthread_should_stop()) - return 0; - spin_lock_bh(&p->fcoe_rx_list.lock); - } - spin_unlock_bh(&p->fcoe_rx_list.lock); - fr = fcoe_dev_from_skb(skb); - lp = fr->fr_dev; - if (unlikely(lp == NULL)) { - FC_DBG("invalid HBA Structure"); - kfree_skb(skb); - continue; - } - - if (unlikely(debug_fcoe)) { - FC_DBG("skb_info: len:%d data_len:%d head:%p data:%p " - "tail:%p end:%p sum:%d dev:%s", - skb->len, skb->data_len, - skb->head, skb->data, skb_tail_pointer(skb), - skb_end_pointer(skb), skb->csum, - skb->dev ? skb->dev->name : "<NULL>"); - } - - /* - * Save source MAC address before discarding header. - */ - fc = lport_priv(lp); - if (unlikely(fc->flogi_progress)) - mac = eth_hdr(skb)->h_source; - - if (skb_is_nonlinear(skb)) - skb_linearize(skb); /* not ideal */ - - /* - * Frame length checks and setting up the header pointers - * was done in fcoe_rcv already. - */ - hp = (struct fcoe_hdr *) skb_network_header(skb); - fh = (struct fc_frame_header *) skb_transport_header(skb); - - stats = fc_lport_get_stats(lp); - if (unlikely(FC_FCOE_DECAPS_VER(hp) != FC_FCOE_VER)) { - if (stats->ErrorFrames < 5) - printk(KERN_WARNING "FCoE version " - "mismatch: The frame has " - "version %x, but the " - "initiator supports version " - "%x\n", FC_FCOE_DECAPS_VER(hp), - FC_FCOE_VER); - stats->ErrorFrames++; - kfree_skb(skb); - continue; - } - - skb_pull(skb, sizeof(struct fcoe_hdr)); - fr_len = skb->len - sizeof(struct fcoe_crc_eof); - - stats->RxFrames++; - stats->RxWords += fr_len / FCOE_WORD_TO_BYTE; - - fp = (struct fc_frame *)skb; - fc_frame_init(fp); - fr_dev(fp) = lp; - fr_sof(fp) = hp->fcoe_sof; - - /* Copy out the CRC and EOF trailer for access */ - if (skb_copy_bits(skb, fr_len, &crc_eof, sizeof(crc_eof))) { - kfree_skb(skb); - continue; - } - fr_eof(fp) = crc_eof.fcoe_eof; - fr_crc(fp) = crc_eof.fcoe_crc32; - if (pskb_trim(skb, fr_len)) { - kfree_skb(skb); - continue; - } - - /* - * We only check CRC if no offload is available and if it is - * it's solicited data, in which case, the FCP layer would - * check it during the copy. - */ - if (lp->crc_offload && skb->ip_summed == CHECKSUM_UNNECESSARY) - fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; - else - fr_flags(fp) |= FCPHF_CRC_UNCHECKED; - - fh = fc_frame_header_get(fp); - if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA && - fh->fh_type == FC_TYPE_FCP) { - fc_exch_recv(lp, lp->emp, fp); - continue; - } - if (fr_flags(fp) & FCPHF_CRC_UNCHECKED) { - if (le32_to_cpu(fr_crc(fp)) != - ~crc32(~0, skb->data, fr_len)) { - if (debug_fcoe || stats->InvalidCRCCount < 5) - printk(KERN_WARNING "fcoe: dropping " - "frame with CRC error\n"); - stats->InvalidCRCCount++; - stats->ErrorFrames++; - fc_frame_free(fp); - continue; - } - fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; - } - /* non flogi and non data exchanges are handled here */ - if (unlikely(fc->flogi_progress)) - fcoe_recv_flogi(fc, fp, mac); - fc_exch_recv(lp, lp->emp, fp); - } - return 0; -} - -/** - * fcoe_recv_flogi() - flogi receive function - * @fc: associated fcoe_softc - * @fp: the recieved frame - * @sa: the source address of this flogi - * - * This is responsible to parse the flogi response and sets the corresponding - * mac address for the initiator, eitehr OUI based or GW based. - * - * Returns: none - */ -static void fcoe_recv_flogi(struct fcoe_softc *fc, struct fc_frame *fp, u8 *sa) -{ - struct fc_frame_header *fh; - u8 op; - - fh = fc_frame_header_get(fp); - if (fh->fh_type != FC_TYPE_ELS) - return; - op = fc_frame_payload_op(fp); - if (op == ELS_LS_ACC && fh->fh_r_ctl == FC_RCTL_ELS_REP && - fc->flogi_oxid == ntohs(fh->fh_ox_id)) { - /* - * FLOGI accepted. - * If the src mac addr is FC_OUI-based, then we mark the - * address_mode flag to use FC_OUI-based Ethernet DA. - * Otherwise we use the FCoE gateway addr - */ - if (!compare_ether_addr(sa, (u8[6]) FC_FCOE_FLOGI_MAC)) { - fc->address_mode = FCOE_FCOUI_ADDR_MODE; - } else { - memcpy(fc->dest_addr, sa, ETH_ALEN); - fc->address_mode = FCOE_GW_ADDR_MODE; - } - - /* - * Remove any previously-set unicast MAC filter. - * Add secondary FCoE MAC address filter for our OUI. - */ - rtnl_lock(); - if (compare_ether_addr(fc->data_src_addr, (u8[6]) { 0 })) - dev_unicast_delete(fc->real_dev, fc->data_src_addr, - ETH_ALEN); - fc_fcoe_set_mac(fc->data_src_addr, fh->fh_d_id); - dev_unicast_add(fc->real_dev, fc->data_src_addr, ETH_ALEN); - rtnl_unlock(); - - fc->flogi_progress = 0; - } else if (op == ELS_FLOGI && fh->fh_r_ctl == FC_RCTL_ELS_REQ && sa) { - /* - * Save source MAC for point-to-point responses. - */ - memcpy(fc->dest_addr, sa, ETH_ALEN); - fc->address_mode = FCOE_GW_ADDR_MODE; - } -} - -/** - * fcoe_watchdog() - fcoe timer callback - * @vp: - * - * This checks the pending queue length for fcoe and set lport qfull - * if the FCOE_MAX_QUEUE_DEPTH is reached. This is done for all fc_lport on the - * fcoe_hostlist. - * - * Returns: 0 for success - */ -void fcoe_watchdog(ulong vp) -{ - struct fcoe_softc *fc; - - read_lock(&fcoe_hostlist_lock); - list_for_each_entry(fc, &fcoe_hostlist, list) { - if (fc->lp) - fcoe_check_wait_queue(fc->lp); - } - read_unlock(&fcoe_hostlist_lock); - - fcoe_timer.expires = jiffies + (1 * HZ); - add_timer(&fcoe_timer); -} - - -/** - * fcoe_check_wait_queue() - put the skb into fcoe pending xmit queue - * @lp: the fc_port for this skb - * @skb: the associated skb to be xmitted - * - * This empties the wait_queue, dequeue the head of the wait_queue queue - * and calls fcoe_start_io() for each packet, if all skb have been - * transmitted, return qlen or -1 if a error occurs, then restore - * wait_queue and try again later. - * - * The wait_queue is used when the skb transmit fails. skb will go - * in the wait_queue which will be emptied by the time function OR - * by the next skb transmit. - * - * Returns: 0 for success - */ -static int fcoe_check_wait_queue(struct fc_lport *lp) -{ - struct fcoe_softc *fc = lport_priv(lp); - struct sk_buff *skb; - int rc = -1; - - spin_lock_bh(&fc->fcoe_pending_queue.lock); - if (fc->fcoe_pending_queue_active) - goto out; - fc->fcoe_pending_queue_active = 1; - - while (fc->fcoe_pending_queue.qlen) { - /* keep qlen > 0 until fcoe_start_io succeeds */ - fc->fcoe_pending_queue.qlen++; - skb = __skb_dequeue(&fc->fcoe_pending_queue); - - spin_unlock_bh(&fc->fcoe_pending_queue.lock); - rc = fcoe_start_io(skb); - spin_lock_bh(&fc->fcoe_pending_queue.lock); - - if (rc) { - __skb_queue_head(&fc->fcoe_pending_queue, skb); - /* undo temporary increment above */ - fc->fcoe_pending_queue.qlen--; - break; - } - /* undo temporary increment above */ - fc->fcoe_pending_queue.qlen--; - } - - if (fc->fcoe_pending_queue.qlen < FCOE_LOW_QUEUE_DEPTH) - lp->qfull = 0; - fc->fcoe_pending_queue_active = 0; - rc = fc->fcoe_pending_queue.qlen; -out: - spin_unlock_bh(&fc->fcoe_pending_queue.lock); - return rc; -} - -/** - * fcoe_dev_setup() - setup link change notification interface - */ -static void fcoe_dev_setup() -{ - /* - * here setup a interface specific wd time to - * monitor the link state - */ - register_netdevice_notifier(&fcoe_notifier); -} - -/** - * fcoe_dev_setup() - cleanup link change notification interface - */ -static void fcoe_dev_cleanup(void) -{ - unregister_netdevice_notifier(&fcoe_notifier); -} - -/** - * fcoe_device_notification() - netdev event notification callback - * @notifier: context of the notification - * @event: type of event - * @ptr: fixed array for output parsed ifname - * - * This function is called by the ethernet driver in case of link change event - * - * Returns: 0 for success - */ -static int fcoe_device_notification(struct notifier_block *notifier, - ulong event, void *ptr) -{ - struct fc_lport *lp = NULL; - struct net_device *real_dev = ptr; - struct fcoe_softc *fc; - struct fcoe_dev_stats *stats; - u32 new_link_up; - u32 mfs; - int rc = NOTIFY_OK; - - read_lock(&fcoe_hostlist_lock); - list_for_each_entry(fc, &fcoe_hostlist, list) { - if (fc->real_dev == real_dev) { - lp = fc->lp; - break; - } - } - read_unlock(&fcoe_hostlist_lock); - if (lp == NULL) { - rc = NOTIFY_DONE; - goto out; - } - - new_link_up = lp->link_up; - switch (event) { - case NETDEV_DOWN: - case NETDEV_GOING_DOWN: - new_link_up = 0; - break; - case NETDEV_UP: - case NETDEV_CHANGE: - new_link_up = !fcoe_link_ok(lp); - break; - case NETDEV_CHANGEMTU: - mfs = fc->real_dev->mtu - - (sizeof(struct fcoe_hdr) + - sizeof(struct fcoe_crc_eof)); - if (mfs >= FC_MIN_MAX_FRAME) - fc_set_mfs(lp, mfs); - new_link_up = !fcoe_link_ok(lp); - break; - case NETDEV_REGISTER: - break; - default: - FC_DBG("unknown event %ld call", event); - } - if (lp->link_up != new_link_up) { - if (new_link_up) - fc_linkup(lp); - else { - stats = fc_lport_get_stats(lp); - stats->LinkFailureCount++; - fc_linkdown(lp); - fcoe_clean_pending_queue(lp); - } - } -out: - return rc; -} - -/** - * fcoe_if_to_netdev() - parse a name buffer to get netdev - * @ifname: fixed array for output parsed ifname - * @buffer: incoming buffer to be copied - * - * Returns: NULL or ptr to netdeive - */ -static struct net_device *fcoe_if_to_netdev(const char *buffer) -{ - char *cp; - char ifname[IFNAMSIZ + 2]; - - if (buffer) { - strlcpy(ifname, buffer, IFNAMSIZ); - cp = ifname + strlen(ifname); - while (--cp >= ifname && *cp == '\n') - *cp = '\0'; - return dev_get_by_name(&init_net, ifname); - } - return NULL; -} - -/** - * fcoe_netdev_to_module_owner() - finds out the nic drive moddule of the netdev - * @netdev: the target netdev - * - * Returns: ptr to the struct module, NULL for failure - */ -static struct module * -fcoe_netdev_to_module_owner(const struct net_device *netdev) -{ - struct device *dev; - - if (!netdev) - return NULL; - - dev = netdev->dev.parent; - if (!dev) - return NULL; - - if (!dev->driver) - return NULL; - - return dev->driver->owner; -} - -/** - * fcoe_ethdrv_get() - Hold the Ethernet driver - * @netdev: the target netdev - * - * Holds the Ethernet driver module by try_module_get() for - * the corresponding netdev. - * - * Returns: 0 for succsss - */ -static int fcoe_ethdrv_get(const struct net_device *netdev) -{ - struct module *owner; - - owner = fcoe_netdev_to_module_owner(netdev); - if (owner) { - printk(KERN_DEBUG "fcoe:hold driver module %s for %s\n", - module_name(owner), netdev->name); - return try_module_get(owner); - } - return -ENODEV; -} - -/** - * fcoe_ethdrv_put() - Release the Ethernet driver - * @netdev: the target netdev - * - * Releases the Ethernet driver module by module_put for - * the corresponding netdev. - * - * Returns: 0 for succsss - */ -static int fcoe_ethdrv_put(const struct net_device *netdev) -{ - struct module *owner; - - owner = fcoe_netdev_to_module_owner(netdev); - if (owner) { - printk(KERN_DEBUG "fcoe:release driver module %s for %s\n", - module_name(owner), netdev->name); - module_put(owner); - return 0; - } - return -ENODEV; -} - -/** - * fcoe_destroy() - handles the destroy from sysfs - * @buffer: expcted to be a eth if name - * @kp: associated kernel param - * - * Returns: 0 for success - */ -static int fcoe_destroy(const char *buffer, struct kernel_param *kp) -{ - int rc; - struct net_device *netdev; - - netdev = fcoe_if_to_netdev(buffer); - if (!netdev) { - rc = -ENODEV; - goto out_nodev; - } - /* look for existing lport */ - if (!fcoe_hostlist_lookup(netdev)) { - rc = -ENODEV; - goto out_putdev; - } - rc = fcoe_if_destroy(netdev); - if (rc) { - printk(KERN_ERR "fcoe: fcoe_if_destroy(%s) failed\n", - netdev->name); - rc = -EIO; - goto out_putdev; - } - fcoe_ethdrv_put(netdev); - rc = 0; -out_putdev: - dev_put(netdev); -out_nodev: - return rc; -} - -/** - * fcoe_create() - Handles the create call from sysfs - * @buffer: expcted to be a eth if name - * @kp: associated kernel param - * - * Returns: 0 for success - */ -static int fcoe_create(const char *buffer, struct kernel_param *kp) -{ - int rc; - struct net_device *netdev; - - netdev = fcoe_if_to_netdev(buffer); - if (!netdev) { - rc = -ENODEV; - goto out_nodev; - } - /* look for existing lport */ - if (fcoe_hostlist_lookup(netdev)) { - rc = -EEXIST; - goto out_putdev; - } - fcoe_ethdrv_get(netdev); - - rc = fcoe_if_create(netdev); - if (rc) { - printk(KERN_ERR "fcoe: fcoe_if_create(%s) failed\n", - netdev->name); - fcoe_ethdrv_put(netdev); - rc = -EIO; - goto out_putdev; - } - rc = 0; -out_putdev: - dev_put(netdev); -out_nodev: - return rc; -} - -module_param_call(create, fcoe_create, NULL, NULL, S_IWUSR); -__MODULE_PARM_TYPE(create, "string"); -MODULE_PARM_DESC(create, "Create fcoe port using net device passed in."); -module_param_call(destroy, fcoe_destroy, NULL, NULL, S_IWUSR); -__MODULE_PARM_TYPE(destroy, "string"); -MODULE_PARM_DESC(destroy, "Destroy fcoe port"); - -/** - * fcoe_link_ok() - Check if link is ok for the fc_lport - * @lp: ptr to the fc_lport - * - * Any permanently-disqualifying conditions have been previously checked. - * This also updates the speed setting, which may change with link for 100/1000. - * - * This function should probably be checking for PAUSE support at some point - * in the future. Currently Per-priority-pause is not determinable using - * ethtool, so we shouldn't be restrictive until that problem is resolved. - * - * Returns: 0 if link is OK for use by FCoE. - * - */ -int fcoe_link_ok(struct fc_lport *lp) -{ - struct fcoe_softc *fc = lport_priv(lp); - struct net_device *dev = fc->real_dev; - struct ethtool_cmd ecmd = { ETHTOOL_GSET }; - int rc = 0; - - if ((dev->flags & IFF_UP) && netif_carrier_ok(dev)) { - dev = fc->phys_dev; - if (dev->ethtool_ops->get_settings) { - dev->ethtool_ops->get_settings(dev, &ecmd); - lp->link_supported_speeds &= - ~(FC_PORTSPEED_1GBIT | FC_PORTSPEED_10GBIT); - if (ecmd.supported & (SUPPORTED_1000baseT_Half | - SUPPORTED_1000baseT_Full)) - lp->link_supported_speeds |= FC_PORTSPEED_1GBIT; - if (ecmd.supported & SUPPORTED_10000baseT_Full) - lp->link_supported_speeds |= - FC_PORTSPEED_10GBIT; - if (ecmd.speed == SPEED_1000) - lp->link_speed = FC_PORTSPEED_1GBIT; - if (ecmd.speed == SPEED_10000) - lp->link_speed = FC_PORTSPEED_10GBIT; - } - } else - rc = -1; - - return rc; -} -EXPORT_SYMBOL_GPL(fcoe_link_ok); - -/** - * fcoe_percpu_clean() - Clear the pending skbs for an lport - * @lp: the fc_lport - */ -void fcoe_percpu_clean(struct fc_lport *lp) -{ - struct fcoe_percpu_s *pp; - struct fcoe_rcv_info *fr; - struct sk_buff_head *list; - struct sk_buff *skb, *next; - struct sk_buff *head; - unsigned int cpu; - - for_each_possible_cpu(cpu) { - pp = &per_cpu(fcoe_percpu, cpu); - spin_lock_bh(&pp->fcoe_rx_list.lock); - list = &pp->fcoe_rx_list; - head = list->next; - for (skb = head; skb != (struct sk_buff *)list; - skb = next) { - next = skb->next; - fr = fcoe_dev_from_skb(skb); - if (fr->fr_dev == lp) { - __skb_unlink(skb, list); - kfree_skb(skb); - } - } - spin_unlock_bh(&pp->fcoe_rx_list.lock); - } -} -EXPORT_SYMBOL_GPL(fcoe_percpu_clean); - -/** - * fcoe_clean_pending_queue() - Dequeue a skb and free it - * @lp: the corresponding fc_lport - * - * Returns: none - */ -void fcoe_clean_pending_queue(struct fc_lport *lp) -{ - struct fcoe_softc *fc = lport_priv(lp); - struct sk_buff *skb; - - spin_lock_bh(&fc->fcoe_pending_queue.lock); - while ((skb = __skb_dequeue(&fc->fcoe_pending_queue)) != NULL) { - spin_unlock_bh(&fc->fcoe_pending_queue.lock); - kfree_skb(skb); - spin_lock_bh(&fc->fcoe_pending_queue.lock); - } - spin_unlock_bh(&fc->fcoe_pending_queue.lock); -} -EXPORT_SYMBOL_GPL(fcoe_clean_pending_queue); - -/** - * libfc_host_alloc() - Allocate a Scsi_Host with room for the fc_lport - * @sht: ptr to the scsi host templ - * @priv_size: size of private data after fc_lport - * - * Returns: ptr to Scsi_Host - * TODO: to libfc? - */ -static inline struct Scsi_Host * -libfc_host_alloc(struct scsi_host_template *sht, int priv_size) -{ - return scsi_host_alloc(sht, sizeof(struct fc_lport) + priv_size); -} - -/** - * fcoe_host_alloc() - Allocate a Scsi_Host with room for the fcoe_softc - * @sht: ptr to the scsi host templ - * @priv_size: size of private data after fc_lport - * - * Returns: ptr to Scsi_Host - */ -struct Scsi_Host *fcoe_host_alloc(struct scsi_host_template *sht, int priv_size) -{ - return libfc_host_alloc(sht, sizeof(struct fcoe_softc) + priv_size); -} -EXPORT_SYMBOL_GPL(fcoe_host_alloc); - -/** - * fcoe_reset() - Resets the fcoe - * @shost: shost the reset is from - * - * Returns: always 0 - */ -int fcoe_reset(struct Scsi_Host *shost) -{ - struct fc_lport *lport = shost_priv(shost); - fc_lport_reset(lport); - return 0; -} -EXPORT_SYMBOL_GPL(fcoe_reset); - -/** - * fcoe_wwn_from_mac() - Converts 48-bit IEEE MAC address to 64-bit FC WWN. - * @mac: mac address - * @scheme: check port - * @port: port indicator for converting - * - * Returns: u64 fc world wide name - */ -u64 fcoe_wwn_from_mac(unsigned char mac[MAX_ADDR_LEN], - unsigned int scheme, unsigned int port) -{ - u64 wwn; - u64 host_mac; - - /* The MAC is in NO, so flip only the low 48 bits */ - host_mac = ((u64) mac[0] << 40) | - ((u64) mac[1] << 32) | - ((u64) mac[2] << 24) | - ((u64) mac[3] << 16) | - ((u64) mac[4] << 8) | - (u64) mac[5]; - - WARN_ON(host_mac >= (1ULL << 48)); - wwn = host_mac | ((u64) scheme << 60); - switch (scheme) { - case 1: - WARN_ON(port != 0); - break; - case 2: - WARN_ON(port >= 0xfff); - wwn |= (u64) port << 48; - break; - default: - WARN_ON(1); - break; - } - - return wwn; -} -EXPORT_SYMBOL_GPL(fcoe_wwn_from_mac); - -/** - * fcoe_hostlist_lookup_softc() - find the corresponding lport by a given device - * @device: this is currently ptr to net_device - * - * Returns: NULL or the located fcoe_softc - */ -static struct fcoe_softc * -fcoe_hostlist_lookup_softc(const struct net_device *dev) -{ - struct fcoe_softc *fc; - - read_lock(&fcoe_hostlist_lock); - list_for_each_entry(fc, &fcoe_hostlist, list) { - if (fc->real_dev == dev) { - read_unlock(&fcoe_hostlist_lock); - return fc; - } - } - read_unlock(&fcoe_hostlist_lock); - return NULL; -} - -/** - * fcoe_hostlist_lookup() - Find the corresponding lport by netdev - * @netdev: ptr to net_device - * - * Returns: 0 for success - */ -struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev) -{ - struct fcoe_softc *fc; - - fc = fcoe_hostlist_lookup_softc(netdev); - - return (fc) ? fc->lp : NULL; -} -EXPORT_SYMBOL_GPL(fcoe_hostlist_lookup); - -/** - * fcoe_hostlist_add() - Add a lport to lports list - * @lp: ptr to the fc_lport to badded - * - * Returns: 0 for success - */ -int fcoe_hostlist_add(const struct fc_lport *lp) -{ - struct fcoe_softc *fc; - - fc = fcoe_hostlist_lookup_softc(fcoe_netdev(lp)); - if (!fc) { - fc = lport_priv(lp); - write_lock_bh(&fcoe_hostlist_lock); - list_add_tail(&fc->list, &fcoe_hostlist); - write_unlock_bh(&fcoe_hostlist_lock); - } - return 0; -} -EXPORT_SYMBOL_GPL(fcoe_hostlist_add); - -/** - * fcoe_hostlist_remove() - remove a lport from lports list - * @lp: ptr to the fc_lport to badded - * - * Returns: 0 for success - */ -int fcoe_hostlist_remove(const struct fc_lport *lp) -{ - struct fcoe_softc *fc; - - fc = fcoe_hostlist_lookup_softc(fcoe_netdev(lp)); - BUG_ON(!fc); - write_lock_bh(&fcoe_hostlist_lock); - list_del(&fc->list); - write_unlock_bh(&fcoe_hostlist_lock); - - return 0; -} -EXPORT_SYMBOL_GPL(fcoe_hostlist_remove); - -/** - * fcoe_libfc_config() - sets up libfc related properties for lport - * @lp: ptr to the fc_lport - * @tt: libfc function template - * - * Returns : 0 for success - */ -int fcoe_libfc_config(struct fc_lport *lp, struct libfc_function_template *tt) -{ - /* Set the function pointers set by the LLDD */ - memcpy(&lp->tt, tt, sizeof(*tt)); - if (fc_fcp_init(lp)) - return -ENOMEM; - fc_exch_init(lp); - fc_elsct_init(lp); - fc_lport_init(lp); - fc_rport_init(lp); - fc_disc_init(lp); - - return 0; -} -EXPORT_SYMBOL_GPL(fcoe_libfc_config); - -/** - * fcoe_init() - fcoe module loading initialization - * - * Returns 0 on success, negative on failure - */ -static int __init fcoe_init(void) -{ - unsigned int cpu; - int rc = 0; - struct fcoe_percpu_s *p; - - INIT_LIST_HEAD(&fcoe_hostlist); - rwlock_init(&fcoe_hostlist_lock); - - for_each_possible_cpu(cpu) { - p = &per_cpu(fcoe_percpu, cpu); - skb_queue_head_init(&p->fcoe_rx_list); - } - - for_each_online_cpu(cpu) - fcoe_percpu_thread_create(cpu); - - /* Initialize per CPU interrupt thread */ - rc = register_hotcpu_notifier(&fcoe_cpu_notifier); - if (rc) - goto out_free; - - /* Setup link change notification */ - fcoe_dev_setup(); - - setup_timer(&fcoe_timer, fcoe_watchdog, 0); - - mod_timer(&fcoe_timer, jiffies + (10 * HZ)); - - fcoe_if_init(); - - return 0; - -out_free: - for_each_online_cpu(cpu) { - fcoe_percpu_thread_destroy(cpu); - } - - return rc; -} -module_init(fcoe_init); - -/** - * fcoe_exit() - fcoe module unloading cleanup - * - * Returns 0 on success, negative on failure - */ -static void __exit fcoe_exit(void) -{ - unsigned int cpu; - struct fcoe_softc *fc, *tmp; - - fcoe_dev_cleanup(); - - /* Stop the timer */ - del_timer_sync(&fcoe_timer); - - /* releases the associated fcoe hosts */ - list_for_each_entry_safe(fc, tmp, &fcoe_hostlist, list) - fcoe_if_destroy(fc->real_dev); - - unregister_hotcpu_notifier(&fcoe_cpu_notifier); - - for_each_online_cpu(cpu) { - fcoe_percpu_thread_destroy(cpu); - } -} -module_exit(fcoe_exit); -- 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