mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-05 01:54:09 +08:00
f018b73af6
When the kernel is configured for preemption, using smp_processor_id() when preemption is enabled causes a warning backtrace and is wrong since we could move off of that CPU as soon as we get the ID, and we would be referencing the wrong CPU, and possibly an invalid one if it could be hotswapped out. Remove the fc_lport_get_stats() function and explicitly use per_cpu_ptr() to get the statistics. Where preemption has been disabled by holding a _bh lock continue to use smp_processor_id(), but otherwise use get_cpu()/put_cpu(). In fcoe_recv_frame() also changed the cases where we return in the middle to do a goto to the code which bumps ErrorFrames and does a put_cpu(). Two of these cases didn't bump ErrorFrames before, but doing so is harmless because they "can't happen", due to prior length checks. Also rearranged code in fcoe_recv_frame() to have only one call to fc_exch_recv(). It's just as efficient and saves a call to put_cpu(). In fc_fcp.c, adjusted a FIXME comment for code which doesn't need fixing. Signed-off-by: Joe Eykholt <jeykholt@cisco.com> Signed-off-by: Robert Love <robert.w.love@intel.com> Signed-off-by: James Bottomley <James.Bottomley@suse.de>
1402 lines
38 KiB
C
1402 lines
38 KiB
C
/*
|
|
* Copyright (c) 2008-2009 Cisco Systems, Inc. All rights reserved.
|
|
* Copyright (c) 2009 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/types.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/timer.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/errno.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/slab.h>
|
|
#include <net/rtnetlink.h>
|
|
|
|
#include <scsi/fc/fc_els.h>
|
|
#include <scsi/fc/fc_fs.h>
|
|
#include <scsi/fc/fc_fip.h>
|
|
#include <scsi/fc/fc_encaps.h>
|
|
#include <scsi/fc/fc_fcoe.h>
|
|
|
|
#include <scsi/libfc.h>
|
|
#include <scsi/libfcoe.h>
|
|
|
|
MODULE_AUTHOR("Open-FCoE.org");
|
|
MODULE_DESCRIPTION("FIP discovery protocol support for FCoE HBAs");
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
#define FCOE_CTLR_MIN_FKA 500 /* min keep alive (mS) */
|
|
#define FCOE_CTLR_DEF_FKA FIP_DEF_FKA /* default keep alive (mS) */
|
|
|
|
static void fcoe_ctlr_timeout(unsigned long);
|
|
static void fcoe_ctlr_timer_work(struct work_struct *);
|
|
static void fcoe_ctlr_recv_work(struct work_struct *);
|
|
|
|
static u8 fcoe_all_fcfs[ETH_ALEN] = FIP_ALL_FCF_MACS;
|
|
|
|
unsigned int libfcoe_debug_logging;
|
|
module_param_named(debug_logging, libfcoe_debug_logging, int, S_IRUGO|S_IWUSR);
|
|
MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels");
|
|
|
|
#define LIBFCOE_LOGGING 0x01 /* General logging, not categorized */
|
|
#define LIBFCOE_FIP_LOGGING 0x02 /* FIP logging */
|
|
|
|
#define LIBFCOE_CHECK_LOGGING(LEVEL, CMD) \
|
|
do { \
|
|
if (unlikely(libfcoe_debug_logging & LEVEL)) \
|
|
do { \
|
|
CMD; \
|
|
} while (0); \
|
|
} while (0)
|
|
|
|
#define LIBFCOE_DBG(fmt, args...) \
|
|
LIBFCOE_CHECK_LOGGING(LIBFCOE_LOGGING, \
|
|
printk(KERN_INFO "libfcoe: " fmt, ##args);)
|
|
|
|
#define LIBFCOE_FIP_DBG(fip, fmt, args...) \
|
|
LIBFCOE_CHECK_LOGGING(LIBFCOE_FIP_LOGGING, \
|
|
printk(KERN_INFO "host%d: fip: " fmt, \
|
|
(fip)->lp->host->host_no, ##args);)
|
|
|
|
/**
|
|
* fcoe_ctlr_mtu_valid() - Check if a FCF's MTU is valid
|
|
* @fcf: The FCF to check
|
|
*
|
|
* Return non-zero if FCF fcoe_size has been validated.
|
|
*/
|
|
static inline int fcoe_ctlr_mtu_valid(const struct fcoe_fcf *fcf)
|
|
{
|
|
return (fcf->flags & FIP_FL_SOL) != 0;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_fcf_usable() - Check if a FCF is usable
|
|
* @fcf: The FCF to check
|
|
*
|
|
* Return non-zero if the FCF is usable.
|
|
*/
|
|
static inline int fcoe_ctlr_fcf_usable(struct fcoe_fcf *fcf)
|
|
{
|
|
u16 flags = FIP_FL_SOL | FIP_FL_AVAIL;
|
|
|
|
return (fcf->flags & flags) == flags;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_init() - Initialize the FCoE Controller instance
|
|
* @fip: The FCoE controller to initialize
|
|
*/
|
|
void fcoe_ctlr_init(struct fcoe_ctlr *fip)
|
|
{
|
|
fip->state = FIP_ST_LINK_WAIT;
|
|
fip->mode = FIP_ST_AUTO;
|
|
INIT_LIST_HEAD(&fip->fcfs);
|
|
spin_lock_init(&fip->lock);
|
|
fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
setup_timer(&fip->timer, fcoe_ctlr_timeout, (unsigned long)fip);
|
|
INIT_WORK(&fip->timer_work, fcoe_ctlr_timer_work);
|
|
INIT_WORK(&fip->recv_work, fcoe_ctlr_recv_work);
|
|
skb_queue_head_init(&fip->fip_recv_list);
|
|
}
|
|
EXPORT_SYMBOL(fcoe_ctlr_init);
|
|
|
|
/**
|
|
* fcoe_ctlr_reset_fcfs() - Reset and free all FCFs for a controller
|
|
* @fip: The FCoE controller whose FCFs are to be reset
|
|
*
|
|
* Called with &fcoe_ctlr lock held.
|
|
*/
|
|
static void fcoe_ctlr_reset_fcfs(struct fcoe_ctlr *fip)
|
|
{
|
|
struct fcoe_fcf *fcf;
|
|
struct fcoe_fcf *next;
|
|
|
|
fip->sel_fcf = NULL;
|
|
list_for_each_entry_safe(fcf, next, &fip->fcfs, list) {
|
|
list_del(&fcf->list);
|
|
kfree(fcf);
|
|
}
|
|
fip->fcf_count = 0;
|
|
fip->sel_time = 0;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_destroy() - Disable and tear down a FCoE controller
|
|
* @fip: The FCoE controller to tear down
|
|
*
|
|
* This is called by FCoE drivers before freeing the &fcoe_ctlr.
|
|
*
|
|
* The receive handler will have been deleted before this to guarantee
|
|
* that no more recv_work will be scheduled.
|
|
*
|
|
* The timer routine will simply return once we set FIP_ST_DISABLED.
|
|
* This guarantees that no further timeouts or work will be scheduled.
|
|
*/
|
|
void fcoe_ctlr_destroy(struct fcoe_ctlr *fip)
|
|
{
|
|
cancel_work_sync(&fip->recv_work);
|
|
skb_queue_purge(&fip->fip_recv_list);
|
|
|
|
spin_lock_bh(&fip->lock);
|
|
fip->state = FIP_ST_DISABLED;
|
|
fcoe_ctlr_reset_fcfs(fip);
|
|
spin_unlock_bh(&fip->lock);
|
|
del_timer_sync(&fip->timer);
|
|
cancel_work_sync(&fip->timer_work);
|
|
}
|
|
EXPORT_SYMBOL(fcoe_ctlr_destroy);
|
|
|
|
/**
|
|
* fcoe_ctlr_fcoe_size() - Return the maximum FCoE size required for VN_Port
|
|
* @fip: The FCoE controller to get the maximum FCoE size from
|
|
*
|
|
* Returns the maximum packet size including the FCoE header and trailer,
|
|
* but not including any Ethernet or VLAN headers.
|
|
*/
|
|
static inline u32 fcoe_ctlr_fcoe_size(struct fcoe_ctlr *fip)
|
|
{
|
|
/*
|
|
* Determine the max FCoE frame size allowed, including
|
|
* FCoE header and trailer.
|
|
* Note: lp->mfs is currently the payload size, not the frame size.
|
|
*/
|
|
return fip->lp->mfs + sizeof(struct fc_frame_header) +
|
|
sizeof(struct fcoe_hdr) + sizeof(struct fcoe_crc_eof);
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_solicit() - Send a FIP solicitation
|
|
* @fip: The FCoE controller to send the solicitation on
|
|
* @fcf: The destination FCF (if NULL, a multicast solicitation is sent)
|
|
*/
|
|
static void fcoe_ctlr_solicit(struct fcoe_ctlr *fip, struct fcoe_fcf *fcf)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct fip_sol {
|
|
struct ethhdr eth;
|
|
struct fip_header fip;
|
|
struct {
|
|
struct fip_mac_desc mac;
|
|
struct fip_wwn_desc wwnn;
|
|
struct fip_size_desc size;
|
|
} __attribute__((packed)) desc;
|
|
} __attribute__((packed)) *sol;
|
|
u32 fcoe_size;
|
|
|
|
skb = dev_alloc_skb(sizeof(*sol));
|
|
if (!skb)
|
|
return;
|
|
|
|
sol = (struct fip_sol *)skb->data;
|
|
|
|
memset(sol, 0, sizeof(*sol));
|
|
memcpy(sol->eth.h_dest, fcf ? fcf->fcf_mac : fcoe_all_fcfs, ETH_ALEN);
|
|
memcpy(sol->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
|
|
sol->eth.h_proto = htons(ETH_P_FIP);
|
|
|
|
sol->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
|
|
sol->fip.fip_op = htons(FIP_OP_DISC);
|
|
sol->fip.fip_subcode = FIP_SC_SOL;
|
|
sol->fip.fip_dl_len = htons(sizeof(sol->desc) / FIP_BPW);
|
|
sol->fip.fip_flags = htons(FIP_FL_FPMA);
|
|
if (fip->spma)
|
|
sol->fip.fip_flags |= htons(FIP_FL_SPMA);
|
|
|
|
sol->desc.mac.fd_desc.fip_dtype = FIP_DT_MAC;
|
|
sol->desc.mac.fd_desc.fip_dlen = sizeof(sol->desc.mac) / FIP_BPW;
|
|
memcpy(sol->desc.mac.fd_mac, fip->ctl_src_addr, ETH_ALEN);
|
|
|
|
sol->desc.wwnn.fd_desc.fip_dtype = FIP_DT_NAME;
|
|
sol->desc.wwnn.fd_desc.fip_dlen = sizeof(sol->desc.wwnn) / FIP_BPW;
|
|
put_unaligned_be64(fip->lp->wwnn, &sol->desc.wwnn.fd_wwn);
|
|
|
|
fcoe_size = fcoe_ctlr_fcoe_size(fip);
|
|
sol->desc.size.fd_desc.fip_dtype = FIP_DT_FCOE_SIZE;
|
|
sol->desc.size.fd_desc.fip_dlen = sizeof(sol->desc.size) / FIP_BPW;
|
|
sol->desc.size.fd_size = htons(fcoe_size);
|
|
|
|
skb_put(skb, sizeof(*sol));
|
|
skb->protocol = htons(ETH_P_FIP);
|
|
skb_reset_mac_header(skb);
|
|
skb_reset_network_header(skb);
|
|
fip->send(fip, skb);
|
|
|
|
if (!fcf)
|
|
fip->sol_time = jiffies;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_link_up() - Start FCoE controller
|
|
* @fip: The FCoE controller to start
|
|
*
|
|
* Called from the LLD when the network link is ready.
|
|
*/
|
|
void fcoe_ctlr_link_up(struct fcoe_ctlr *fip)
|
|
{
|
|
spin_lock_bh(&fip->lock);
|
|
if (fip->state == FIP_ST_NON_FIP || fip->state == FIP_ST_AUTO) {
|
|
spin_unlock_bh(&fip->lock);
|
|
fc_linkup(fip->lp);
|
|
} else if (fip->state == FIP_ST_LINK_WAIT) {
|
|
fip->state = fip->mode;
|
|
spin_unlock_bh(&fip->lock);
|
|
if (fip->state == FIP_ST_AUTO)
|
|
LIBFCOE_FIP_DBG(fip, "%s", "setting AUTO mode.\n");
|
|
fc_linkup(fip->lp);
|
|
fcoe_ctlr_solicit(fip, NULL);
|
|
} else
|
|
spin_unlock_bh(&fip->lock);
|
|
}
|
|
EXPORT_SYMBOL(fcoe_ctlr_link_up);
|
|
|
|
/**
|
|
* fcoe_ctlr_reset() - Reset a FCoE controller
|
|
* @fip: The FCoE controller to reset
|
|
*/
|
|
static void fcoe_ctlr_reset(struct fcoe_ctlr *fip)
|
|
{
|
|
fcoe_ctlr_reset_fcfs(fip);
|
|
del_timer(&fip->timer);
|
|
fip->ctlr_ka_time = 0;
|
|
fip->port_ka_time = 0;
|
|
fip->sol_time = 0;
|
|
fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
fip->map_dest = 0;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_link_down() - Stop a FCoE controller
|
|
* @fip: The FCoE controller to be stopped
|
|
*
|
|
* Returns non-zero if the link was up and now isn't.
|
|
*
|
|
* Called from the LLD when the network link is not ready.
|
|
* There may be multiple calls while the link is down.
|
|
*/
|
|
int fcoe_ctlr_link_down(struct fcoe_ctlr *fip)
|
|
{
|
|
int link_dropped;
|
|
|
|
LIBFCOE_FIP_DBG(fip, "link down.\n");
|
|
spin_lock_bh(&fip->lock);
|
|
fcoe_ctlr_reset(fip);
|
|
link_dropped = fip->state != FIP_ST_LINK_WAIT;
|
|
fip->state = FIP_ST_LINK_WAIT;
|
|
spin_unlock_bh(&fip->lock);
|
|
|
|
if (link_dropped)
|
|
fc_linkdown(fip->lp);
|
|
return link_dropped;
|
|
}
|
|
EXPORT_SYMBOL(fcoe_ctlr_link_down);
|
|
|
|
/**
|
|
* fcoe_ctlr_send_keep_alive() - Send a keep-alive to the selected FCF
|
|
* @fip: The FCoE controller to send the FKA on
|
|
* @lport: libfc fc_lport to send from
|
|
* @ports: 0 for controller keep-alive, 1 for port keep-alive
|
|
* @sa: The source MAC address
|
|
*
|
|
* A controller keep-alive is sent every fka_period (typically 8 seconds).
|
|
* The source MAC is the native MAC address.
|
|
*
|
|
* A port keep-alive is sent every 90 seconds while logged in.
|
|
* The source MAC is the assigned mapped source address.
|
|
* The destination is the FCF's F-port.
|
|
*/
|
|
static void fcoe_ctlr_send_keep_alive(struct fcoe_ctlr *fip,
|
|
struct fc_lport *lport,
|
|
int ports, u8 *sa)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct fip_kal {
|
|
struct ethhdr eth;
|
|
struct fip_header fip;
|
|
struct fip_mac_desc mac;
|
|
} __attribute__((packed)) *kal;
|
|
struct fip_vn_desc *vn;
|
|
u32 len;
|
|
struct fc_lport *lp;
|
|
struct fcoe_fcf *fcf;
|
|
|
|
fcf = fip->sel_fcf;
|
|
lp = fip->lp;
|
|
if (!fcf || !fc_host_port_id(lp->host))
|
|
return;
|
|
|
|
len = sizeof(*kal) + ports * sizeof(*vn);
|
|
skb = dev_alloc_skb(len);
|
|
if (!skb)
|
|
return;
|
|
|
|
kal = (struct fip_kal *)skb->data;
|
|
memset(kal, 0, len);
|
|
memcpy(kal->eth.h_dest, fcf->fcf_mac, ETH_ALEN);
|
|
memcpy(kal->eth.h_source, sa, ETH_ALEN);
|
|
kal->eth.h_proto = htons(ETH_P_FIP);
|
|
|
|
kal->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
|
|
kal->fip.fip_op = htons(FIP_OP_CTRL);
|
|
kal->fip.fip_subcode = FIP_SC_KEEP_ALIVE;
|
|
kal->fip.fip_dl_len = htons((sizeof(kal->mac) +
|
|
ports * sizeof(*vn)) / FIP_BPW);
|
|
kal->fip.fip_flags = htons(FIP_FL_FPMA);
|
|
if (fip->spma)
|
|
kal->fip.fip_flags |= htons(FIP_FL_SPMA);
|
|
|
|
kal->mac.fd_desc.fip_dtype = FIP_DT_MAC;
|
|
kal->mac.fd_desc.fip_dlen = sizeof(kal->mac) / FIP_BPW;
|
|
memcpy(kal->mac.fd_mac, fip->ctl_src_addr, ETH_ALEN);
|
|
if (ports) {
|
|
vn = (struct fip_vn_desc *)(kal + 1);
|
|
vn->fd_desc.fip_dtype = FIP_DT_VN_ID;
|
|
vn->fd_desc.fip_dlen = sizeof(*vn) / FIP_BPW;
|
|
memcpy(vn->fd_mac, fip->get_src_addr(lport), ETH_ALEN);
|
|
hton24(vn->fd_fc_id, fc_host_port_id(lp->host));
|
|
put_unaligned_be64(lp->wwpn, &vn->fd_wwpn);
|
|
}
|
|
skb_put(skb, len);
|
|
skb->protocol = htons(ETH_P_FIP);
|
|
skb_reset_mac_header(skb);
|
|
skb_reset_network_header(skb);
|
|
fip->send(fip, skb);
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_encaps() - Encapsulate an ELS frame for FIP, without sending it
|
|
* @fip: The FCoE controller for the ELS frame
|
|
* @dtype: The FIP descriptor type for the frame
|
|
* @skb: The FCoE ELS frame including FC header but no FCoE headers
|
|
*
|
|
* Returns non-zero error code on failure.
|
|
*
|
|
* The caller must check that the length is a multiple of 4.
|
|
*
|
|
* The @skb must have enough headroom (28 bytes) and tailroom (8 bytes).
|
|
* Headroom includes the FIP encapsulation description, FIP header, and
|
|
* Ethernet header. The tailroom is for the FIP MAC descriptor.
|
|
*/
|
|
static int fcoe_ctlr_encaps(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
u8 dtype, struct sk_buff *skb)
|
|
{
|
|
struct fip_encaps_head {
|
|
struct ethhdr eth;
|
|
struct fip_header fip;
|
|
struct fip_encaps encaps;
|
|
} __attribute__((packed)) *cap;
|
|
struct fip_mac_desc *mac;
|
|
struct fcoe_fcf *fcf;
|
|
size_t dlen;
|
|
u16 fip_flags;
|
|
|
|
fcf = fip->sel_fcf;
|
|
if (!fcf)
|
|
return -ENODEV;
|
|
|
|
/* set flags according to both FCF and lport's capability on SPMA */
|
|
fip_flags = fcf->flags;
|
|
fip_flags &= fip->spma ? FIP_FL_SPMA | FIP_FL_FPMA : FIP_FL_FPMA;
|
|
if (!fip_flags)
|
|
return -ENODEV;
|
|
|
|
dlen = sizeof(struct fip_encaps) + skb->len; /* len before push */
|
|
cap = (struct fip_encaps_head *)skb_push(skb, sizeof(*cap));
|
|
|
|
memset(cap, 0, sizeof(*cap));
|
|
memcpy(cap->eth.h_dest, fcf->fcf_mac, ETH_ALEN);
|
|
memcpy(cap->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
|
|
cap->eth.h_proto = htons(ETH_P_FIP);
|
|
|
|
cap->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
|
|
cap->fip.fip_op = htons(FIP_OP_LS);
|
|
cap->fip.fip_subcode = FIP_SC_REQ;
|
|
cap->fip.fip_dl_len = htons((dlen + sizeof(*mac)) / FIP_BPW);
|
|
cap->fip.fip_flags = htons(fip_flags);
|
|
|
|
cap->encaps.fd_desc.fip_dtype = dtype;
|
|
cap->encaps.fd_desc.fip_dlen = dlen / FIP_BPW;
|
|
|
|
mac = (struct fip_mac_desc *)skb_put(skb, sizeof(*mac));
|
|
memset(mac, 0, sizeof(mac));
|
|
mac->fd_desc.fip_dtype = FIP_DT_MAC;
|
|
mac->fd_desc.fip_dlen = sizeof(*mac) / FIP_BPW;
|
|
if (dtype != FIP_DT_FLOGI && dtype != FIP_DT_FDISC)
|
|
memcpy(mac->fd_mac, fip->get_src_addr(lport), ETH_ALEN);
|
|
else if (fip->spma)
|
|
memcpy(mac->fd_mac, fip->ctl_src_addr, ETH_ALEN);
|
|
|
|
skb->protocol = htons(ETH_P_FIP);
|
|
skb_reset_mac_header(skb);
|
|
skb_reset_network_header(skb);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_els_send() - Send an ELS frame encapsulated by FIP if appropriate.
|
|
* @fip: FCoE controller.
|
|
* @lport: libfc fc_lport to send from
|
|
* @skb: FCoE ELS frame including FC header but no FCoE headers.
|
|
*
|
|
* Returns a non-zero error code if the frame should not be sent.
|
|
* Returns zero if the caller should send the frame with FCoE encapsulation.
|
|
*
|
|
* The caller must check that the length is a multiple of 4.
|
|
* The SKB must have enough headroom (28 bytes) and tailroom (8 bytes).
|
|
*/
|
|
int fcoe_ctlr_els_send(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct fc_frame_header *fh;
|
|
u16 old_xid;
|
|
u8 op;
|
|
u8 mac[ETH_ALEN];
|
|
|
|
fh = (struct fc_frame_header *)skb->data;
|
|
op = *(u8 *)(fh + 1);
|
|
|
|
if (op == ELS_FLOGI) {
|
|
old_xid = fip->flogi_oxid;
|
|
fip->flogi_oxid = ntohs(fh->fh_ox_id);
|
|
if (fip->state == FIP_ST_AUTO) {
|
|
if (old_xid == FC_XID_UNKNOWN)
|
|
fip->flogi_count = 0;
|
|
fip->flogi_count++;
|
|
if (fip->flogi_count < 3)
|
|
goto drop;
|
|
fip->map_dest = 1;
|
|
return 0;
|
|
}
|
|
if (fip->state == FIP_ST_NON_FIP)
|
|
fip->map_dest = 1;
|
|
}
|
|
|
|
if (fip->state == FIP_ST_NON_FIP)
|
|
return 0;
|
|
if (!fip->sel_fcf)
|
|
goto drop;
|
|
|
|
switch (op) {
|
|
case ELS_FLOGI:
|
|
op = FIP_DT_FLOGI;
|
|
break;
|
|
case ELS_FDISC:
|
|
if (ntoh24(fh->fh_s_id))
|
|
return 0;
|
|
op = FIP_DT_FDISC;
|
|
break;
|
|
case ELS_LOGO:
|
|
if (fip->state != FIP_ST_ENABLED)
|
|
return 0;
|
|
if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI)
|
|
return 0;
|
|
op = FIP_DT_LOGO;
|
|
break;
|
|
case ELS_LS_ACC:
|
|
if (fip->flogi_oxid == FC_XID_UNKNOWN)
|
|
return 0;
|
|
if (!ntoh24(fh->fh_s_id))
|
|
return 0;
|
|
if (fip->state == FIP_ST_AUTO)
|
|
return 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.
|
|
*/
|
|
fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
fc_fcoe_set_mac(mac, fh->fh_d_id);
|
|
fip->update_mac(lport, mac);
|
|
return 0;
|
|
default:
|
|
if (fip->state != FIP_ST_ENABLED)
|
|
goto drop;
|
|
return 0;
|
|
}
|
|
if (fcoe_ctlr_encaps(fip, lport, op, skb))
|
|
goto drop;
|
|
fip->send(fip, skb);
|
|
return -EINPROGRESS;
|
|
drop:
|
|
kfree_skb(skb);
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL(fcoe_ctlr_els_send);
|
|
|
|
/**
|
|
* fcoe_ctlr_age_fcfs() - Reset and free all old FCFs for a controller
|
|
* @fip: The FCoE controller to free FCFs on
|
|
*
|
|
* Called with lock held and preemption disabled.
|
|
*
|
|
* An FCF is considered old if we have missed three advertisements.
|
|
* That is, there have been no valid advertisement from it for three
|
|
* times its keep-alive period including fuzz.
|
|
*
|
|
* In addition, determine the time when an FCF selection can occur.
|
|
*
|
|
* Also, increment the MissDiscAdvCount when no advertisement is received
|
|
* for the corresponding FCF for 1.5 * FKA_ADV_PERIOD (FC-BB-5 LESB).
|
|
*/
|
|
static void fcoe_ctlr_age_fcfs(struct fcoe_ctlr *fip)
|
|
{
|
|
struct fcoe_fcf *fcf;
|
|
struct fcoe_fcf *next;
|
|
unsigned long sel_time = 0;
|
|
unsigned long mda_time = 0;
|
|
struct fcoe_dev_stats *stats;
|
|
|
|
list_for_each_entry_safe(fcf, next, &fip->fcfs, list) {
|
|
mda_time = fcf->fka_period + (fcf->fka_period >> 1);
|
|
if ((fip->sel_fcf == fcf) &&
|
|
(time_after(jiffies, fcf->time + mda_time))) {
|
|
mod_timer(&fip->timer, jiffies + mda_time);
|
|
stats = per_cpu_ptr(fip->lp->dev_stats,
|
|
smp_processor_id());
|
|
stats->MissDiscAdvCount++;
|
|
printk(KERN_INFO "libfcoe: host%d: Missing Discovery "
|
|
"Advertisement for fab %llx count %lld\n",
|
|
fip->lp->host->host_no, fcf->fabric_name,
|
|
stats->MissDiscAdvCount);
|
|
}
|
|
if (time_after(jiffies, fcf->time + fcf->fka_period * 3 +
|
|
msecs_to_jiffies(FIP_FCF_FUZZ * 3))) {
|
|
if (fip->sel_fcf == fcf)
|
|
fip->sel_fcf = NULL;
|
|
list_del(&fcf->list);
|
|
WARN_ON(!fip->fcf_count);
|
|
fip->fcf_count--;
|
|
kfree(fcf);
|
|
stats = per_cpu_ptr(fip->lp->dev_stats,
|
|
smp_processor_id());
|
|
stats->VLinkFailureCount++;
|
|
} else if (fcoe_ctlr_mtu_valid(fcf) &&
|
|
(!sel_time || time_before(sel_time, fcf->time))) {
|
|
sel_time = fcf->time;
|
|
}
|
|
}
|
|
if (sel_time) {
|
|
sel_time += msecs_to_jiffies(FCOE_CTLR_START_DELAY);
|
|
fip->sel_time = sel_time;
|
|
if (time_before(sel_time, fip->timer.expires))
|
|
mod_timer(&fip->timer, sel_time);
|
|
} else {
|
|
fip->sel_time = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_parse_adv() - Decode a FIP advertisement into a new FCF entry
|
|
* @fip: The FCoE controller receiving the advertisement
|
|
* @skb: The received FIP advertisement frame
|
|
* @fcf: The resulting FCF entry
|
|
*
|
|
* Returns zero on a valid parsed advertisement,
|
|
* otherwise returns non zero value.
|
|
*/
|
|
static int fcoe_ctlr_parse_adv(struct fcoe_ctlr *fip,
|
|
struct sk_buff *skb, struct fcoe_fcf *fcf)
|
|
{
|
|
struct fip_header *fiph;
|
|
struct fip_desc *desc = NULL;
|
|
struct fip_wwn_desc *wwn;
|
|
struct fip_fab_desc *fab;
|
|
struct fip_fka_desc *fka;
|
|
unsigned long t;
|
|
size_t rlen;
|
|
size_t dlen;
|
|
|
|
memset(fcf, 0, sizeof(*fcf));
|
|
fcf->fka_period = msecs_to_jiffies(FCOE_CTLR_DEF_FKA);
|
|
|
|
fiph = (struct fip_header *)skb->data;
|
|
fcf->flags = ntohs(fiph->fip_flags);
|
|
|
|
rlen = ntohs(fiph->fip_dl_len) * 4;
|
|
if (rlen + sizeof(*fiph) > skb->len)
|
|
return -EINVAL;
|
|
|
|
desc = (struct fip_desc *)(fiph + 1);
|
|
while (rlen > 0) {
|
|
dlen = desc->fip_dlen * FIP_BPW;
|
|
if (dlen < sizeof(*desc) || dlen > rlen)
|
|
return -EINVAL;
|
|
switch (desc->fip_dtype) {
|
|
case FIP_DT_PRI:
|
|
if (dlen != sizeof(struct fip_pri_desc))
|
|
goto len_err;
|
|
fcf->pri = ((struct fip_pri_desc *)desc)->fd_pri;
|
|
break;
|
|
case FIP_DT_MAC:
|
|
if (dlen != sizeof(struct fip_mac_desc))
|
|
goto len_err;
|
|
memcpy(fcf->fcf_mac,
|
|
((struct fip_mac_desc *)desc)->fd_mac,
|
|
ETH_ALEN);
|
|
if (!is_valid_ether_addr(fcf->fcf_mac)) {
|
|
LIBFCOE_FIP_DBG(fip, "Invalid MAC address "
|
|
"in FIP adv\n");
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
case FIP_DT_NAME:
|
|
if (dlen != sizeof(struct fip_wwn_desc))
|
|
goto len_err;
|
|
wwn = (struct fip_wwn_desc *)desc;
|
|
fcf->switch_name = get_unaligned_be64(&wwn->fd_wwn);
|
|
break;
|
|
case FIP_DT_FAB:
|
|
if (dlen != sizeof(struct fip_fab_desc))
|
|
goto len_err;
|
|
fab = (struct fip_fab_desc *)desc;
|
|
fcf->fabric_name = get_unaligned_be64(&fab->fd_wwn);
|
|
fcf->vfid = ntohs(fab->fd_vfid);
|
|
fcf->fc_map = ntoh24(fab->fd_map);
|
|
break;
|
|
case FIP_DT_FKA:
|
|
if (dlen != sizeof(struct fip_fka_desc))
|
|
goto len_err;
|
|
fka = (struct fip_fka_desc *)desc;
|
|
if (fka->fd_flags & FIP_FKA_ADV_D)
|
|
fcf->fd_flags = 1;
|
|
t = ntohl(fka->fd_fka_period);
|
|
if (t >= FCOE_CTLR_MIN_FKA)
|
|
fcf->fka_period = msecs_to_jiffies(t);
|
|
break;
|
|
case FIP_DT_MAP_OUI:
|
|
case FIP_DT_FCOE_SIZE:
|
|
case FIP_DT_FLOGI:
|
|
case FIP_DT_FDISC:
|
|
case FIP_DT_LOGO:
|
|
case FIP_DT_ELP:
|
|
default:
|
|
LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x "
|
|
"in FIP adv\n", desc->fip_dtype);
|
|
/* standard says ignore unknown descriptors >= 128 */
|
|
if (desc->fip_dtype < FIP_DT_VENDOR_BASE)
|
|
return -EINVAL;
|
|
continue;
|
|
}
|
|
desc = (struct fip_desc *)((char *)desc + dlen);
|
|
rlen -= dlen;
|
|
}
|
|
if (!fcf->fc_map || (fcf->fc_map & 0x10000))
|
|
return -EINVAL;
|
|
if (!fcf->switch_name || !fcf->fabric_name)
|
|
return -EINVAL;
|
|
return 0;
|
|
|
|
len_err:
|
|
LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n",
|
|
desc->fip_dtype, dlen);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_recv_adv() - Handle an incoming advertisement
|
|
* @fip: The FCoE controller receiving the advertisement
|
|
* @skb: The received FIP packet
|
|
*/
|
|
static void fcoe_ctlr_recv_adv(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
{
|
|
struct fcoe_fcf *fcf;
|
|
struct fcoe_fcf new;
|
|
struct fcoe_fcf *found;
|
|
unsigned long sol_tov = msecs_to_jiffies(FCOE_CTRL_SOL_TOV);
|
|
int first = 0;
|
|
int mtu_valid;
|
|
|
|
if (fcoe_ctlr_parse_adv(fip, skb, &new))
|
|
return;
|
|
|
|
spin_lock_bh(&fip->lock);
|
|
first = list_empty(&fip->fcfs);
|
|
found = NULL;
|
|
list_for_each_entry(fcf, &fip->fcfs, list) {
|
|
if (fcf->switch_name == new.switch_name &&
|
|
fcf->fabric_name == new.fabric_name &&
|
|
fcf->fc_map == new.fc_map &&
|
|
compare_ether_addr(fcf->fcf_mac, new.fcf_mac) == 0) {
|
|
found = fcf;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
if (fip->fcf_count >= FCOE_CTLR_FCF_LIMIT)
|
|
goto out;
|
|
|
|
fcf = kmalloc(sizeof(*fcf), GFP_ATOMIC);
|
|
if (!fcf)
|
|
goto out;
|
|
|
|
fip->fcf_count++;
|
|
memcpy(fcf, &new, sizeof(new));
|
|
list_add(&fcf->list, &fip->fcfs);
|
|
} else {
|
|
/*
|
|
* Flags in advertisements are ignored once the FCF is
|
|
* selected. Flags in unsolicited advertisements are
|
|
* ignored after a usable solicited advertisement
|
|
* has been received.
|
|
*/
|
|
if (fcf == fip->sel_fcf) {
|
|
fip->ctlr_ka_time -= fcf->fka_period;
|
|
fip->ctlr_ka_time += new.fka_period;
|
|
if (time_before(fip->ctlr_ka_time, fip->timer.expires))
|
|
mod_timer(&fip->timer, fip->ctlr_ka_time);
|
|
} else if (!fcoe_ctlr_fcf_usable(fcf))
|
|
fcf->flags = new.flags;
|
|
fcf->fka_period = new.fka_period;
|
|
memcpy(fcf->fcf_mac, new.fcf_mac, ETH_ALEN);
|
|
}
|
|
mtu_valid = fcoe_ctlr_mtu_valid(fcf);
|
|
fcf->time = jiffies;
|
|
if (!found) {
|
|
LIBFCOE_FIP_DBG(fip, "New FCF for fab %llx map %x val %d\n",
|
|
fcf->fabric_name, fcf->fc_map, mtu_valid);
|
|
}
|
|
|
|
/*
|
|
* If this advertisement is not solicited and our max receive size
|
|
* hasn't been verified, send a solicited advertisement.
|
|
*/
|
|
if (!mtu_valid)
|
|
fcoe_ctlr_solicit(fip, fcf);
|
|
|
|
/*
|
|
* If its been a while since we did a solicit, and this is
|
|
* the first advertisement we've received, do a multicast
|
|
* solicitation to gather as many advertisements as we can
|
|
* before selection occurs.
|
|
*/
|
|
if (first && time_after(jiffies, fip->sol_time + sol_tov))
|
|
fcoe_ctlr_solicit(fip, NULL);
|
|
|
|
/*
|
|
* If this is the first validated FCF, note the time and
|
|
* set a timer to trigger selection.
|
|
*/
|
|
if (mtu_valid && !fip->sel_time && fcoe_ctlr_fcf_usable(fcf)) {
|
|
fip->sel_time = jiffies +
|
|
msecs_to_jiffies(FCOE_CTLR_START_DELAY);
|
|
if (!timer_pending(&fip->timer) ||
|
|
time_before(fip->sel_time, fip->timer.expires))
|
|
mod_timer(&fip->timer, fip->sel_time);
|
|
}
|
|
out:
|
|
spin_unlock_bh(&fip->lock);
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_recv_els() - Handle an incoming FIP encapsulated ELS frame
|
|
* @fip: The FCoE controller which received the packet
|
|
* @skb: The received FIP packet
|
|
*/
|
|
static void fcoe_ctlr_recv_els(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
{
|
|
struct fc_lport *lport = fip->lp;
|
|
struct fip_header *fiph;
|
|
struct fc_frame *fp = (struct fc_frame *)skb;
|
|
struct fc_frame_header *fh = NULL;
|
|
struct fip_desc *desc;
|
|
struct fip_encaps *els;
|
|
struct fcoe_dev_stats *stats;
|
|
enum fip_desc_type els_dtype = 0;
|
|
u8 els_op;
|
|
u8 sub;
|
|
u8 granted_mac[ETH_ALEN] = { 0 };
|
|
size_t els_len = 0;
|
|
size_t rlen;
|
|
size_t dlen;
|
|
|
|
fiph = (struct fip_header *)skb->data;
|
|
sub = fiph->fip_subcode;
|
|
if (sub != FIP_SC_REQ && sub != FIP_SC_REP)
|
|
goto drop;
|
|
|
|
rlen = ntohs(fiph->fip_dl_len) * 4;
|
|
if (rlen + sizeof(*fiph) > skb->len)
|
|
goto drop;
|
|
|
|
desc = (struct fip_desc *)(fiph + 1);
|
|
while (rlen > 0) {
|
|
dlen = desc->fip_dlen * FIP_BPW;
|
|
if (dlen < sizeof(*desc) || dlen > rlen)
|
|
goto drop;
|
|
switch (desc->fip_dtype) {
|
|
case FIP_DT_MAC:
|
|
if (dlen != sizeof(struct fip_mac_desc))
|
|
goto len_err;
|
|
memcpy(granted_mac,
|
|
((struct fip_mac_desc *)desc)->fd_mac,
|
|
ETH_ALEN);
|
|
if (!is_valid_ether_addr(granted_mac)) {
|
|
LIBFCOE_FIP_DBG(fip, "Invalid MAC address "
|
|
"in FIP ELS\n");
|
|
goto drop;
|
|
}
|
|
memcpy(fr_cb(fp)->granted_mac, granted_mac, ETH_ALEN);
|
|
break;
|
|
case FIP_DT_FLOGI:
|
|
case FIP_DT_FDISC:
|
|
case FIP_DT_LOGO:
|
|
case FIP_DT_ELP:
|
|
if (fh)
|
|
goto drop;
|
|
if (dlen < sizeof(*els) + sizeof(*fh) + 1)
|
|
goto len_err;
|
|
els_len = dlen - sizeof(*els);
|
|
els = (struct fip_encaps *)desc;
|
|
fh = (struct fc_frame_header *)(els + 1);
|
|
els_dtype = desc->fip_dtype;
|
|
break;
|
|
default:
|
|
LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x "
|
|
"in FIP adv\n", desc->fip_dtype);
|
|
/* standard says ignore unknown descriptors >= 128 */
|
|
if (desc->fip_dtype < FIP_DT_VENDOR_BASE)
|
|
goto drop;
|
|
continue;
|
|
}
|
|
desc = (struct fip_desc *)((char *)desc + dlen);
|
|
rlen -= dlen;
|
|
}
|
|
|
|
if (!fh)
|
|
goto drop;
|
|
els_op = *(u8 *)(fh + 1);
|
|
|
|
if (els_dtype == FIP_DT_FLOGI && sub == FIP_SC_REP &&
|
|
fip->flogi_oxid == ntohs(fh->fh_ox_id) &&
|
|
els_op == ELS_LS_ACC && is_valid_ether_addr(granted_mac))
|
|
fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
|
|
/*
|
|
* Convert skb into an fc_frame containing only the ELS.
|
|
*/
|
|
skb_pull(skb, (u8 *)fh - skb->data);
|
|
skb_trim(skb, els_len);
|
|
fp = (struct fc_frame *)skb;
|
|
fc_frame_init(fp);
|
|
fr_sof(fp) = FC_SOF_I3;
|
|
fr_eof(fp) = FC_EOF_T;
|
|
fr_dev(fp) = lport;
|
|
|
|
stats = per_cpu_ptr(lport->dev_stats, get_cpu());
|
|
stats->RxFrames++;
|
|
stats->RxWords += skb->len / FIP_BPW;
|
|
put_cpu();
|
|
|
|
fc_exch_recv(lport, fp);
|
|
return;
|
|
|
|
len_err:
|
|
LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n",
|
|
desc->fip_dtype, dlen);
|
|
drop:
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_recv_els() - Handle an incoming link reset frame
|
|
* @fip: The FCoE controller that received the frame
|
|
* @fh: The received FIP header
|
|
*
|
|
* There may be multiple VN_Port descriptors.
|
|
* The overall length has already been checked.
|
|
*/
|
|
static void fcoe_ctlr_recv_clr_vlink(struct fcoe_ctlr *fip,
|
|
struct fip_header *fh)
|
|
{
|
|
struct fip_desc *desc;
|
|
struct fip_mac_desc *mp;
|
|
struct fip_wwn_desc *wp;
|
|
struct fip_vn_desc *vp;
|
|
size_t rlen;
|
|
size_t dlen;
|
|
struct fcoe_fcf *fcf = fip->sel_fcf;
|
|
struct fc_lport *lport = fip->lp;
|
|
u32 desc_mask;
|
|
|
|
LIBFCOE_FIP_DBG(fip, "Clear Virtual Link received\n");
|
|
if (!fcf)
|
|
return;
|
|
if (!fcf || !fc_host_port_id(lport->host))
|
|
return;
|
|
|
|
/*
|
|
* mask of required descriptors. Validating each one clears its bit.
|
|
*/
|
|
desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) | BIT(FIP_DT_VN_ID);
|
|
|
|
rlen = ntohs(fh->fip_dl_len) * FIP_BPW;
|
|
desc = (struct fip_desc *)(fh + 1);
|
|
while (rlen >= sizeof(*desc)) {
|
|
dlen = desc->fip_dlen * FIP_BPW;
|
|
if (dlen > rlen)
|
|
return;
|
|
switch (desc->fip_dtype) {
|
|
case FIP_DT_MAC:
|
|
mp = (struct fip_mac_desc *)desc;
|
|
if (dlen < sizeof(*mp))
|
|
return;
|
|
if (compare_ether_addr(mp->fd_mac, fcf->fcf_mac))
|
|
return;
|
|
desc_mask &= ~BIT(FIP_DT_MAC);
|
|
break;
|
|
case FIP_DT_NAME:
|
|
wp = (struct fip_wwn_desc *)desc;
|
|
if (dlen < sizeof(*wp))
|
|
return;
|
|
if (get_unaligned_be64(&wp->fd_wwn) != fcf->switch_name)
|
|
return;
|
|
desc_mask &= ~BIT(FIP_DT_NAME);
|
|
break;
|
|
case FIP_DT_VN_ID:
|
|
vp = (struct fip_vn_desc *)desc;
|
|
if (dlen < sizeof(*vp))
|
|
return;
|
|
if (compare_ether_addr(vp->fd_mac,
|
|
fip->get_src_addr(lport)) == 0 &&
|
|
get_unaligned_be64(&vp->fd_wwpn) == lport->wwpn &&
|
|
ntoh24(vp->fd_fc_id) ==
|
|
fc_host_port_id(lport->host))
|
|
desc_mask &= ~BIT(FIP_DT_VN_ID);
|
|
break;
|
|
default:
|
|
/* standard says ignore unknown descriptors >= 128 */
|
|
if (desc->fip_dtype < FIP_DT_VENDOR_BASE)
|
|
return;
|
|
break;
|
|
}
|
|
desc = (struct fip_desc *)((char *)desc + dlen);
|
|
rlen -= dlen;
|
|
}
|
|
|
|
/*
|
|
* reset only if all required descriptors were present and valid.
|
|
*/
|
|
if (desc_mask) {
|
|
LIBFCOE_FIP_DBG(fip, "missing descriptors mask %x\n",
|
|
desc_mask);
|
|
} else {
|
|
LIBFCOE_FIP_DBG(fip, "performing Clear Virtual Link\n");
|
|
|
|
spin_lock_bh(&fip->lock);
|
|
per_cpu_ptr(lport->dev_stats,
|
|
smp_processor_id())->VLinkFailureCount++;
|
|
fcoe_ctlr_reset(fip);
|
|
spin_unlock_bh(&fip->lock);
|
|
|
|
fc_lport_reset(fip->lp);
|
|
fcoe_ctlr_solicit(fip, NULL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_recv() - Receive a FIP packet
|
|
* @fip: The FCoE controller that received the packet
|
|
* @skb: The received FIP packet
|
|
*
|
|
* This may be called from either NET_RX_SOFTIRQ or IRQ.
|
|
*/
|
|
void fcoe_ctlr_recv(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
{
|
|
skb_queue_tail(&fip->fip_recv_list, skb);
|
|
schedule_work(&fip->recv_work);
|
|
}
|
|
EXPORT_SYMBOL(fcoe_ctlr_recv);
|
|
|
|
/**
|
|
* fcoe_ctlr_recv_handler() - Receive a FIP frame
|
|
* @fip: The FCoE controller that received the frame
|
|
* @skb: The received FIP frame
|
|
*
|
|
* Returns non-zero if the frame is dropped.
|
|
*/
|
|
static int fcoe_ctlr_recv_handler(struct fcoe_ctlr *fip, struct sk_buff *skb)
|
|
{
|
|
struct fip_header *fiph;
|
|
struct ethhdr *eh;
|
|
enum fip_state state;
|
|
u16 op;
|
|
u8 sub;
|
|
|
|
if (skb_linearize(skb))
|
|
goto drop;
|
|
if (skb->len < sizeof(*fiph))
|
|
goto drop;
|
|
eh = eth_hdr(skb);
|
|
if (compare_ether_addr(eh->h_dest, fip->ctl_src_addr) &&
|
|
compare_ether_addr(eh->h_dest, FIP_ALL_ENODE_MACS))
|
|
goto drop;
|
|
fiph = (struct fip_header *)skb->data;
|
|
op = ntohs(fiph->fip_op);
|
|
sub = fiph->fip_subcode;
|
|
|
|
if (FIP_VER_DECAPS(fiph->fip_ver) != FIP_VER)
|
|
goto drop;
|
|
if (ntohs(fiph->fip_dl_len) * FIP_BPW + sizeof(*fiph) > skb->len)
|
|
goto drop;
|
|
|
|
spin_lock_bh(&fip->lock);
|
|
state = fip->state;
|
|
if (state == FIP_ST_AUTO) {
|
|
fip->map_dest = 0;
|
|
fip->state = FIP_ST_ENABLED;
|
|
state = FIP_ST_ENABLED;
|
|
LIBFCOE_FIP_DBG(fip, "Using FIP mode\n");
|
|
}
|
|
spin_unlock_bh(&fip->lock);
|
|
if (state != FIP_ST_ENABLED)
|
|
goto drop;
|
|
|
|
if (op == FIP_OP_LS) {
|
|
fcoe_ctlr_recv_els(fip, skb); /* consumes skb */
|
|
return 0;
|
|
}
|
|
if (op == FIP_OP_DISC && sub == FIP_SC_ADV)
|
|
fcoe_ctlr_recv_adv(fip, skb);
|
|
else if (op == FIP_OP_CTRL && sub == FIP_SC_CLR_VLINK)
|
|
fcoe_ctlr_recv_clr_vlink(fip, fiph);
|
|
kfree_skb(skb);
|
|
return 0;
|
|
drop:
|
|
kfree_skb(skb);
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_select() - Select the best FCF (if possible)
|
|
* @fip: The FCoE controller
|
|
*
|
|
* If there are conflicting advertisements, no FCF can be chosen.
|
|
*
|
|
* Called with lock held.
|
|
*/
|
|
static void fcoe_ctlr_select(struct fcoe_ctlr *fip)
|
|
{
|
|
struct fcoe_fcf *fcf;
|
|
struct fcoe_fcf *best = NULL;
|
|
|
|
list_for_each_entry(fcf, &fip->fcfs, list) {
|
|
LIBFCOE_FIP_DBG(fip, "consider FCF for fab %llx VFID %d map %x "
|
|
"val %d\n", fcf->fabric_name, fcf->vfid,
|
|
fcf->fc_map, fcoe_ctlr_mtu_valid(fcf));
|
|
if (!fcoe_ctlr_fcf_usable(fcf)) {
|
|
LIBFCOE_FIP_DBG(fip, "FCF for fab %llx map %x %svalid "
|
|
"%savailable\n", fcf->fabric_name,
|
|
fcf->fc_map, (fcf->flags & FIP_FL_SOL)
|
|
? "" : "in", (fcf->flags & FIP_FL_AVAIL)
|
|
? "" : "un");
|
|
continue;
|
|
}
|
|
if (!best) {
|
|
best = fcf;
|
|
continue;
|
|
}
|
|
if (fcf->fabric_name != best->fabric_name ||
|
|
fcf->vfid != best->vfid ||
|
|
fcf->fc_map != best->fc_map) {
|
|
LIBFCOE_FIP_DBG(fip, "Conflicting fabric, VFID, "
|
|
"or FC-MAP\n");
|
|
return;
|
|
}
|
|
if (fcf->pri < best->pri)
|
|
best = fcf;
|
|
}
|
|
fip->sel_fcf = best;
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_timeout() - FIP timeout handler
|
|
* @arg: The FCoE controller that timed out
|
|
*
|
|
* Ages FCFs. Triggers FCF selection if possible. Sends keep-alives.
|
|
*/
|
|
static void fcoe_ctlr_timeout(unsigned long arg)
|
|
{
|
|
struct fcoe_ctlr *fip = (struct fcoe_ctlr *)arg;
|
|
struct fcoe_fcf *sel;
|
|
struct fcoe_fcf *fcf;
|
|
unsigned long next_timer = jiffies + msecs_to_jiffies(FIP_VN_KA_PERIOD);
|
|
|
|
spin_lock_bh(&fip->lock);
|
|
if (fip->state == FIP_ST_DISABLED) {
|
|
spin_unlock_bh(&fip->lock);
|
|
return;
|
|
}
|
|
|
|
fcf = fip->sel_fcf;
|
|
fcoe_ctlr_age_fcfs(fip);
|
|
|
|
sel = fip->sel_fcf;
|
|
if (!sel && fip->sel_time && time_after_eq(jiffies, fip->sel_time)) {
|
|
fcoe_ctlr_select(fip);
|
|
sel = fip->sel_fcf;
|
|
fip->sel_time = 0;
|
|
}
|
|
|
|
if (sel != fcf) {
|
|
fcf = sel; /* the old FCF may have been freed */
|
|
if (sel) {
|
|
printk(KERN_INFO "libfcoe: host%d: FIP selected "
|
|
"Fibre-Channel Forwarder MAC %pM\n",
|
|
fip->lp->host->host_no, sel->fcf_mac);
|
|
memcpy(fip->dest_addr, sel->fcf_mac, ETH_ALEN);
|
|
fip->port_ka_time = jiffies +
|
|
msecs_to_jiffies(FIP_VN_KA_PERIOD);
|
|
fip->ctlr_ka_time = jiffies + sel->fka_period;
|
|
} else {
|
|
printk(KERN_NOTICE "libfcoe: host%d: "
|
|
"FIP Fibre-Channel Forwarder timed out. "
|
|
"Starting FCF discovery.\n",
|
|
fip->lp->host->host_no);
|
|
fip->reset_req = 1;
|
|
schedule_work(&fip->timer_work);
|
|
}
|
|
}
|
|
|
|
if (sel && !sel->fd_flags) {
|
|
if (time_after_eq(jiffies, fip->ctlr_ka_time)) {
|
|
fip->ctlr_ka_time = jiffies + sel->fka_period;
|
|
fip->send_ctlr_ka = 1;
|
|
}
|
|
if (time_after(next_timer, fip->ctlr_ka_time))
|
|
next_timer = fip->ctlr_ka_time;
|
|
|
|
if (time_after_eq(jiffies, fip->port_ka_time)) {
|
|
fip->port_ka_time = jiffies +
|
|
msecs_to_jiffies(FIP_VN_KA_PERIOD);
|
|
fip->send_port_ka = 1;
|
|
}
|
|
if (time_after(next_timer, fip->port_ka_time))
|
|
next_timer = fip->port_ka_time;
|
|
mod_timer(&fip->timer, next_timer);
|
|
} else if (fip->sel_time) {
|
|
next_timer = fip->sel_time +
|
|
msecs_to_jiffies(FCOE_CTLR_START_DELAY);
|
|
mod_timer(&fip->timer, next_timer);
|
|
}
|
|
if (fip->send_ctlr_ka || fip->send_port_ka)
|
|
schedule_work(&fip->timer_work);
|
|
spin_unlock_bh(&fip->lock);
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_timer_work() - Worker thread function for timer work
|
|
* @work: Handle to a FCoE controller
|
|
*
|
|
* Sends keep-alives and resets which must not
|
|
* be called from the timer directly, since they use a mutex.
|
|
*/
|
|
static void fcoe_ctlr_timer_work(struct work_struct *work)
|
|
{
|
|
struct fcoe_ctlr *fip;
|
|
struct fc_lport *vport;
|
|
u8 *mac;
|
|
int reset;
|
|
|
|
fip = container_of(work, struct fcoe_ctlr, timer_work);
|
|
spin_lock_bh(&fip->lock);
|
|
reset = fip->reset_req;
|
|
fip->reset_req = 0;
|
|
spin_unlock_bh(&fip->lock);
|
|
|
|
if (reset)
|
|
fc_lport_reset(fip->lp);
|
|
|
|
if (fip->send_ctlr_ka) {
|
|
fip->send_ctlr_ka = 0;
|
|
fcoe_ctlr_send_keep_alive(fip, NULL, 0, fip->ctl_src_addr);
|
|
}
|
|
if (fip->send_port_ka) {
|
|
fip->send_port_ka = 0;
|
|
mutex_lock(&fip->lp->lp_mutex);
|
|
mac = fip->get_src_addr(fip->lp);
|
|
fcoe_ctlr_send_keep_alive(fip, fip->lp, 1, mac);
|
|
list_for_each_entry(vport, &fip->lp->vports, list) {
|
|
mac = fip->get_src_addr(vport);
|
|
fcoe_ctlr_send_keep_alive(fip, vport, 1, mac);
|
|
}
|
|
mutex_unlock(&fip->lp->lp_mutex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_recv_work() - Worker thread function for receiving FIP frames
|
|
* @recv_work: Handle to a FCoE controller
|
|
*/
|
|
static void fcoe_ctlr_recv_work(struct work_struct *recv_work)
|
|
{
|
|
struct fcoe_ctlr *fip;
|
|
struct sk_buff *skb;
|
|
|
|
fip = container_of(recv_work, struct fcoe_ctlr, recv_work);
|
|
while ((skb = skb_dequeue(&fip->fip_recv_list)))
|
|
fcoe_ctlr_recv_handler(fip, skb);
|
|
}
|
|
|
|
/**
|
|
* fcoe_ctlr_recv_flogi() - Snoop pre-FIP receipt of FLOGI response
|
|
* @fip: The FCoE controller
|
|
* @fp: The FC frame to snoop
|
|
*
|
|
* Snoop potential response to FLOGI or even incoming FLOGI.
|
|
*
|
|
* The caller has checked that we are waiting for login as indicated
|
|
* by fip->flogi_oxid != FC_XID_UNKNOWN.
|
|
*
|
|
* The caller is responsible for freeing the frame.
|
|
* Fill in the granted_mac address.
|
|
*
|
|
* Return non-zero if the frame should not be delivered to libfc.
|
|
*/
|
|
int fcoe_ctlr_recv_flogi(struct fcoe_ctlr *fip, struct fc_lport *lport,
|
|
struct fc_frame *fp)
|
|
{
|
|
struct fc_frame_header *fh;
|
|
u8 op;
|
|
u8 *sa;
|
|
|
|
sa = eth_hdr(&fp->skb)->h_source;
|
|
fh = fc_frame_header_get(fp);
|
|
if (fh->fh_type != FC_TYPE_ELS)
|
|
return 0;
|
|
|
|
op = fc_frame_payload_op(fp);
|
|
if (op == ELS_LS_ACC && fh->fh_r_ctl == FC_RCTL_ELS_REP &&
|
|
fip->flogi_oxid == ntohs(fh->fh_ox_id)) {
|
|
|
|
spin_lock_bh(&fip->lock);
|
|
if (fip->state != FIP_ST_AUTO && fip->state != FIP_ST_NON_FIP) {
|
|
spin_unlock_bh(&fip->lock);
|
|
return -EINVAL;
|
|
}
|
|
fip->state = FIP_ST_NON_FIP;
|
|
LIBFCOE_FIP_DBG(fip,
|
|
"received FLOGI LS_ACC using non-FIP mode\n");
|
|
|
|
/*
|
|
* 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)) {
|
|
fip->map_dest = 1;
|
|
} else {
|
|
memcpy(fip->dest_addr, sa, ETH_ALEN);
|
|
fip->map_dest = 0;
|
|
}
|
|
fip->flogi_oxid = FC_XID_UNKNOWN;
|
|
spin_unlock_bh(&fip->lock);
|
|
fc_fcoe_set_mac(fr_cb(fp)->granted_mac, fh->fh_d_id);
|
|
} else if (op == ELS_FLOGI && fh->fh_r_ctl == FC_RCTL_ELS_REQ && sa) {
|
|
/*
|
|
* Save source MAC for point-to-point responses.
|
|
*/
|
|
spin_lock_bh(&fip->lock);
|
|
if (fip->state == FIP_ST_AUTO || fip->state == FIP_ST_NON_FIP) {
|
|
memcpy(fip->dest_addr, sa, ETH_ALEN);
|
|
fip->map_dest = 0;
|
|
if (fip->state == FIP_ST_AUTO)
|
|
LIBFCOE_FIP_DBG(fip, "received non-FIP FLOGI. "
|
|
"Setting non-FIP mode\n");
|
|
fip->state = FIP_ST_NON_FIP;
|
|
}
|
|
spin_unlock_bh(&fip->lock);
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(fcoe_ctlr_recv_flogi);
|
|
|
|
/**
|
|
* fcoe_wwn_from_mac() - Converts a 48-bit IEEE MAC address to a 64-bit FC WWN
|
|
* @mac: The MAC address to convert
|
|
* @scheme: The scheme to use when converting
|
|
* @port: The 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_libfc_config() - Sets up libfc related properties for local port
|
|
* @lp: The local port to configure libfc for
|
|
* @tt: The libfc function template
|
|
*
|
|
* Returns : 0 for success
|
|
*/
|
|
int fcoe_libfc_config(struct fc_lport *lport,
|
|
struct libfc_function_template *tt)
|
|
{
|
|
/* Set the function pointers set by the LLDD */
|
|
memcpy(&lport->tt, tt, sizeof(*tt));
|
|
if (fc_fcp_init(lport))
|
|
return -ENOMEM;
|
|
fc_exch_init(lport);
|
|
fc_elsct_init(lport);
|
|
fc_lport_init(lport);
|
|
fc_rport_init(lport);
|
|
fc_disc_init(lport);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fcoe_libfc_config);
|
|
|