From: Hannes Reinecke Date: Wed, 17 Sep 2008 16:25:23 +0200 Subject: fcoe: Fibre Channel over Ethernet driver References: FATE#303913 Encapsulation protocol for running Fibre Channel over Ethernet interfaces. Creates virtual Fibre Channel host adapters using libfc. Signed-off-by: Robert Love Signed-off-by: Chris Leech Signed-off-by: Vasu Dev Signed-off-by: Yi Zou Signed-off-by: Steve Ma Signed-off-by: Hannes Reinecke --- drivers/scsi/Kconfig | 6 + drivers/scsi/Makefile | 1 + drivers/scsi/fcoe/Makefile | 8 + drivers/scsi/fcoe/fcoe_def.h | 100 +++++++ drivers/scsi/fcoe/fcoe_dev.c | 633 ++++++++++++++++++++++++++++++++++++++++++ drivers/scsi/fcoe/fcoe_if.c | 497 +++++++++++++++++++++++++++++++++ drivers/scsi/fcoe/fcoeinit.c | 440 +++++++++++++++++++++++++++++ 7 files changed, 1685 insertions(+), 0 deletions(-) create mode 100644 drivers/scsi/fcoe/Makefile create mode 100644 drivers/scsi/fcoe/fcoe_def.h create mode 100644 drivers/scsi/fcoe/fcoe_dev.c create mode 100644 drivers/scsi/fcoe/fcoe_if.c create mode 100644 drivers/scsi/fcoe/fcoeinit.c diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index bd480d2..f382eea 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -334,6 +334,12 @@ config LIBFC ---help--- Fibre Channel library module +config FCOE + tristate "FCoE module" + depends on LIBFC + ---help--- + Fibre Channel over Ethernet module + config ISCSI_TCP tristate "iSCSI Initiator over TCP/IP" depends on SCSI && INET diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 9158dc6..22c01e5 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_SCSI_SRP_ATTRS) += scsi_transport_srp.o obj-$(CONFIG_SCSI_DH) += device_handler/ obj-$(CONFIG_LIBFC) += libfc/ +obj-$(CONFIG_FCOE) += fcoe/ obj-$(CONFIG_ISCSI_TCP) += libiscsi.o iscsi_tcp.o obj-$(CONFIG_INFINIBAND_ISER) += libiscsi.o obj-$(CONFIG_SCSI_A4000T) += 53c700.o a4000t.o diff --git a/drivers/scsi/fcoe/Makefile b/drivers/scsi/fcoe/Makefile new file mode 100644 index 0000000..342e2ad --- /dev/null +++ b/drivers/scsi/fcoe/Makefile @@ -0,0 +1,8 @@ +# $Id: Makefile + +obj-$(CONFIG_FCOE) += fcoe.o + +fcoe-y := \ + fcoe_dev.o \ + fcoe_if.o \ + fcoeinit.o diff --git a/drivers/scsi/fcoe/fcoe_def.h b/drivers/scsi/fcoe/fcoe_def.h new file mode 100644 index 0000000..12bf69c --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_def.h @@ -0,0 +1,100 @@ +/* + * Copyright(c) 2007 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 + */ + +#ifndef _FCOE_DEF_H_ +#define _FCOE_DEF_H_ + +#include +#include + +#include + +#include + +#define FCOE_DRIVER_NAME "fcoe" /* driver name for ioctls */ +#define FCOE_DRIVER_VENDOR "Open-FC.org" /* vendor name for ioctls */ + +#define FCOE_MIN_FRAME 36 +#define FCOE_WORD_TO_BYTE 4 + +/* + * this is the main common structure across all instance of fcoe driver. + * There is one to one mapping between hba struct and ethernet nic. + * list of hbas contains pointer to the hba struct, these structures are + * stored in this array using there corresponding if_index. + */ + +struct fcoe_percpu_s { + int cpu; + struct task_struct *thread; + struct sk_buff_head fcoe_rx_list; + struct page *crc_eof_page; + int crc_eof_offset; +}; + +struct fcoe_info { + struct timer_list timer; + /* + * fcoe host list is protected by the following read/write lock + */ + rwlock_t fcoe_hostlist_lock; + struct list_head fcoe_hostlist; + + struct fcoe_percpu_s *fcoe_percpu[NR_CPUS]; +}; + +struct fcoe_softc { + struct list_head list; + struct fc_lport *lp; + struct net_device *real_dev; + struct net_device *phys_dev; /* device with ethtool_ops */ + struct packet_type fcoe_packet_type; + struct sk_buff_head fcoe_pending_queue; + u16 user_mfs; /* configured max frame size */ + + u8 dest_addr[ETH_ALEN]; + u8 ctl_src_addr[ETH_ALEN]; + u8 data_src_addr[ETH_ALEN]; + /* + * fcoe protocol address learning related stuff + */ + u16 flogi_oxid; + u8 flogi_progress; + u8 address_mode; +}; + +extern int debug_fcoe; +extern struct fcoe_percpu_s *fcoe_percpu[]; +extern struct scsi_transport_template *fcoe_transport_template; +int fcoe_percpu_receive_thread(void *arg); + +/* + * HBA transport ops prototypes + */ +extern struct fcoe_info fcoei; + +void fcoe_clean_pending_queue(struct fc_lport *fd); +void fcoe_watchdog(ulong vp); +int fcoe_destroy_interface(const char *ifname); +int fcoe_create_interface(const char *ifname); +int fcoe_xmit(struct fc_lport *, struct fc_frame *); +int fcoe_rcv(struct sk_buff *, struct net_device *, + struct packet_type *, struct net_device *); +int fcoe_link_ok(struct fc_lport *); +#endif /* _FCOE_DEF_H_ */ diff --git a/drivers/scsi/fcoe/fcoe_dev.c b/drivers/scsi/fcoe/fcoe_dev.c new file mode 100644 index 0000000..d5a354f --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_dev.c @@ -0,0 +1,633 @@ +/* + * Copyright(c) 2007 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 + */ + +/* + * FCOE protocol file + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include "fcoe_def.h" + +#define FCOE_MAX_QUEUE_DEPTH 256 + +/* destination address mode */ +#define FCOE_GW_ADDR_MODE 0x00 +#define FCOE_FCOUI_ADDR_MODE 0x01 + +/* Function Prototyes */ +static int fcoe_check_wait_queue(struct fc_lport *); +static void fcoe_insert_wait_queue_head(struct fc_lport *, struct sk_buff *); +static void fcoe_insert_wait_queue(struct fc_lport *, struct sk_buff *); +static void fcoe_recv_flogi(struct fcoe_softc *, struct fc_frame *, u8 *); + +/* + * this is the fcoe receive function + * called by NET_RX_SOFTIRQ + * this function will receive the packet and + * build fc frame and pass it up + */ +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 fcoe_dev_stats *stats; + u8 *data; + struct fc_frame_header *fh; + unsigned short oxid; + int cpu_idx; + struct fcoe_percpu_s *fps; + struct fcoe_info *fci = &fcoei; + + 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 : ""); + + } + + /* check for FCOE packet type */ + if (unlikely(eth_hdr(skb)->h_proto != htons(ETH_P_FCOE))) { + FC_DBG("wrong FC type frame"); + goto err; + } + data = skb->data; + data += sizeof(struct fcoe_hdr); + fh = (struct fc_frame_header *)data; + oxid = ntohs(fh->fh_ox_id); + + fr = fcoe_dev_from_skb(skb); + fr->fr_dev = lp; + fr->ptype = ptype; + cpu_idx = 0; +#ifdef CONFIG_SMP + /* + * The exchange ID are ANDed with num of online CPUs, + * so that will have the least lock contention in + * handling the exchange. if there is no thread + * for a given idx then use first online cpu. + */ + cpu_idx = oxid & (num_online_cpus() >> 1); + if (fci->fcoe_percpu[cpu_idx] == NULL) + cpu_idx = first_cpu(cpu_online_map); +#endif + fps = fci->fcoe_percpu[cpu_idx]; + + spin_lock_bh(&fps->fcoe_rx_list.lock); + __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: +#ifdef CONFIG_SMP + stats = lp->dev_stats[smp_processor_id()]; +#else + stats = lp->dev_stats[0]; +#endif + stats->ErrorFrames++; + +err2: + kfree_skb(skb); + return -1; +} + +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; +} + +static int fcoe_get_paged_crc_eof(struct sk_buff *skb, int tlen) +{ + struct fcoe_info *fci = &fcoei; + struct fcoe_percpu_s *fps; + struct page *page; + int cpu_idx; + + cpu_idx = get_cpu(); + fps = fci->fcoe_percpu[cpu_idx]; + page = fps->crc_eof_page; + if (!page) { + page = alloc_page(GFP_ATOMIC); + if (!page) { + put_cpu(); + return -ENOMEM; + } + fps->crc_eof_page = page; + WARN_ON(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(); + return 0; +} + +/* + * this is the frame xmit routine + */ +int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp) +{ + int indx; + 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 */ + int flogi_in_progress = 0; + struct fcoe_softc *fc; + void *data; + u8 sof, eof; + struct fcoe_hdr *hp; + + WARN_ON((fr_len(fp) % sizeof(u32)) != 0); + + fc = (struct fcoe_softc *)lp->drv_priv; + /* + * 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); + + crc = ~0; + crc = crc32(crc, skb->data, skb_headlen(skb)); + + for (indx = 0; indx < skb_shinfo(skb)->nr_frags; indx++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[indx]; + unsigned long off = frag->page_offset; + unsigned long len = frag->size; + + while (len > 0) { + unsigned long clen; + + 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; + } + } + + /* + * Get header and trailer lengths. + * This is temporary code until we get rid of the old protocol. + * Both versions have essentially the same trailer layout but T11 + * has padding afterwards. + */ + hlen = sizeof(struct fcoe_hdr); + tlen = sizeof(struct fcoe_crc_eof); + + /* + * copy fc crc and eof to the skb buff + * Use utility buffer in the fc_frame part of the sk_buff for the + * trailer. + * We don't do a get_page for this frag, since that page may not be + * managed that way. So that skb_free() doesn't do that either, we + * setup the destructor to remove this frag. + */ + if (skb_is_nonlinear(skb)) { + skb_frag_t *frag; + if (fcoe_get_paged_crc_eof(skb, tlen)) { + kfree(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); + } + + cp->fcoe_eof = eof; + cp->fcoe_crc32 = cpu_to_le32(~crc); + if (tlen == sizeof(*cp)) + memset(cp->fcoe_resvd, 0, sizeof(cp->fcoe_resvd)); + wlen = (skb->len - tlen + sizeof(crc)) / FCOE_WORD_TO_BYTE; + + if (skb_is_nonlinear(skb)) { + kunmap_atomic(cp, KM_SKB_DATA_SOFTIRQ); + cp = NULL; + } + + /* + * Fill in the control structures + */ + skb->ip_summed = CHECKSUM_NONE; + eh = (struct ethhdr *)skb_push(skb, hlen + sizeof(struct ethhdr)); + 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); + + eh->h_proto = htons(ETH_P_FCOE); + skb->protocol = htons(ETH_P_802_3); + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + + 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; + + stats = lp->dev_stats[smp_processor_id()]; + stats->TxFrames++; + stats->TxWords += wlen; + skb->dev = fc->real_dev; + + 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) { + fcoe_insert_wait_queue(lp, skb); + if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH) + fc_pause(lp); + } + + return 0; +} + +int fcoe_percpu_receive_thread(void *arg) +{ + struct fcoe_percpu_s *p = arg; + u32 fr_len; + unsigned int hlen; + unsigned int tlen; + 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 *cp; + enum fc_sof sof; + struct fc_frame *fp; + u8 *mac = NULL; + struct fcoe_softc *fc; + struct fcoe_hdr *hp; + + set_user_nice(current, 19); + + 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; + } + + stats = lp->dev_stats[smp_processor_id()]; + + 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 : ""); + } + + /* + * Save source MAC address before discarding header. + */ + fc = lp->drv_priv; + if (unlikely(fc->flogi_progress)) + mac = eth_hdr(skb)->h_source; + + if (skb_is_nonlinear(skb)) + skb_linearize(skb); /* not ideal */ + + /* + * Check the header and pull it off. + */ + hlen = sizeof(struct fcoe_hdr); + + hp = (struct fcoe_hdr *)skb->data; + if (unlikely(FC_FCOE_DECAPS_VER(hp) != FC_FCOE_VER)) { + if (stats->ErrorFrames < 5) + FC_DBG("unknown FCoE version %x", + FC_FCOE_DECAPS_VER(hp)); + stats->ErrorFrames++; + kfree_skb(skb); + continue; + } + sof = hp->fcoe_sof; + skb_pull(skb, sizeof(*hp)); + fr_len = skb->len - sizeof(struct fcoe_crc_eof); + skb_trim(skb, fr_len); + tlen = sizeof(struct fcoe_crc_eof); + + if (unlikely(fr_len > skb->len)) { + if (stats->ErrorFrames < 5) + FC_DBG("length error fr_len 0x%x skb->len 0x%x", + fr_len, skb->len); + stats->ErrorFrames++; + kfree_skb(skb); + continue; + } + stats->RxFrames++; + stats->RxWords += fr_len / FCOE_WORD_TO_BYTE; + + fp = (struct fc_frame *) skb; + fc_frame_init(fp); + cp = (struct fcoe_crc_eof *)(skb->data + fr_len); + fr_eof(fp) = cp->fcoe_eof; + fr_sof(fp) = sof; + fr_dev(fp) = lp; + + /* + * Check the CRC here, unless it's solicited data for SCSI. + * In that case, the SCSI layer can check it during the copy, + * and it'll be more cache-efficient. + */ + fh = fc_frame_header_get(fp); + if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA && + fh->fh_type == FC_TYPE_FCP) { + fr_flags(fp) |= FCPHF_CRC_UNCHECKED; + fc_exch_recv(lp, lp->emp, fp); + } else if (le32_to_cpu(cp->fcoe_crc32) == + ~crc32(~0, skb->data, fr_len)) { + if (unlikely(fc->flogi_progress)) + fcoe_recv_flogi(fc, fp, mac); + fc_exch_recv(lp, lp->emp, fp); + } else { + if (debug_fcoe || stats->InvalidCRCCount < 5) { + printk(KERN_WARNING \ + "fcoe: dropping frame with CRC error"); + } + stats->InvalidCRCCount++; + stats->ErrorFrames++; + fc_frame_free(fp); + } + } + return 0; +} + +/* + * Snoop potential response to FLOGI or even incoming FLOGI. + */ +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; + } +} + +void fcoe_watchdog(ulong vp) +{ + struct fc_lport *lp; + struct fcoe_softc *fc; + struct fcoe_info *fci = &fcoei; + int paused = 0; + + read_lock(&fci->fcoe_hostlist_lock); + list_for_each_entry(fc, &fci->fcoe_hostlist, list) { + lp = fc->lp; + if (lp) { + if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH) + paused = 1; + if (fcoe_check_wait_queue(lp) < FCOE_MAX_QUEUE_DEPTH) { + if (paused) + fc_unpause(lp); + } + } + } + read_unlock(&fci->fcoe_hostlist_lock); + + fci->timer.expires = jiffies + (1 * HZ); + add_timer(&fci->timer); +} + +/* + * 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. + * + */ + +/* + * Function name : fcoe_check_wait_queue() + * + * Return Values : 0 or error + * + * Description : 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 0 + * if a error occurs, then restore wait_queue and try again + * later + * + */ + +static int fcoe_check_wait_queue(struct fc_lport *lp) +{ + int rc, unpause = 0; + int paused = 0; + struct sk_buff *skb; + struct fcoe_softc *fc; + + fc = (struct fcoe_softc *)lp->drv_priv; + spin_lock_bh(&fc->fcoe_pending_queue.lock); + + /* + * is this interface paused? + */ + if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH) + paused = 1; + if (fc->fcoe_pending_queue.qlen) { + while ((skb = __skb_dequeue(&fc->fcoe_pending_queue)) != NULL) { + spin_unlock_bh(&fc->fcoe_pending_queue.lock); + rc = fcoe_start_io(skb); + if (rc) { + fcoe_insert_wait_queue_head(lp, skb); + return rc; + } + spin_lock_bh(&fc->fcoe_pending_queue.lock); + } + if (fc->fcoe_pending_queue.qlen < FCOE_MAX_QUEUE_DEPTH) + unpause = 1; + } + spin_unlock_bh(&fc->fcoe_pending_queue.lock); + if ((unpause) && (paused)) + fc_unpause(lp); + return fc->fcoe_pending_queue.qlen; +} + +static void fcoe_insert_wait_queue_head(struct fc_lport *lp, + struct sk_buff *skb) +{ + struct fcoe_softc *fc; + + fc = (struct fcoe_softc *)lp->drv_priv; + spin_lock_bh(&fc->fcoe_pending_queue.lock); + __skb_queue_head(&fc->fcoe_pending_queue, skb); + spin_unlock_bh(&fc->fcoe_pending_queue.lock); +} + +static void fcoe_insert_wait_queue(struct fc_lport *lp, + struct sk_buff *skb) +{ + struct fcoe_softc *fc; + + fc = (struct fcoe_softc *)lp->drv_priv; + spin_lock_bh(&fc->fcoe_pending_queue.lock); + __skb_queue_tail(&fc->fcoe_pending_queue, skb); + spin_unlock_bh(&fc->fcoe_pending_queue.lock); +} diff --git a/drivers/scsi/fcoe/fcoe_if.c b/drivers/scsi/fcoe/fcoe_if.c new file mode 100644 index 0000000..7f983e2 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_if.c @@ -0,0 +1,497 @@ +/* + * Copyright(c) 2007 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 + */ + +/* + * FCOE protocol file + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include "fcoe_def.h" + +#define FCOE_VERSION "0.1" + +#define FCOE_MAX_LUN 255 +#define FCOE_MAX_FCP_TARGET 256 + +#define FCOE_MIN_XID 0x0004 +#define FCOE_MAX_XID 0x07ef + +int debug_fcoe; + +struct fcoe_info fcoei = { + .fcoe_hostlist = LIST_HEAD_INIT(fcoei.fcoe_hostlist), +}; + +static struct fcoe_softc *fcoe_find_fc_lport(const char *name) +{ + struct fcoe_softc *fc; + struct fc_lport *lp; + struct fcoe_info *fci = &fcoei; + + read_lock(&fci->fcoe_hostlist_lock); + list_for_each_entry(fc, &fci->fcoe_hostlist, list) { + lp = fc->lp; + if (!strncmp(name, lp->ifname, IFNAMSIZ)) { + read_unlock(&fci->fcoe_hostlist_lock); + return fc; + } + } + read_unlock(&fci->fcoe_hostlist_lock); + return NULL; +} + +/* + * Convert 48-bit IEEE MAC address to 64-bit FC WWN. + */ +static 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; +} + +static struct scsi_host_template fcoe_driver_template = { + .module = THIS_MODULE, + .name = "FCoE Driver", + .proc_name = FCOE_DRIVER_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 = FC_MAX_OUTSTANDING_COMMANDS, + .use_clustering = ENABLE_CLUSTERING, + .sg_tablesize = 4, + .max_sectors = 0xffff, +}; + +int fcoe_destroy_interface(const char *ifname) +{ + int cpu, idx; + struct fcoe_dev_stats *p; + struct fcoe_percpu_s *pp; + struct fcoe_softc *fc; + struct fcoe_rcv_info *fr; + struct fcoe_info *fci = &fcoei; + struct sk_buff_head *list; + struct sk_buff *skb, *next; + struct sk_buff *head; + struct fc_lport *lp; + u8 flogi_maddr[ETH_ALEN]; + + fc = fcoe_find_fc_lport(ifname); + if (!fc) + return -ENODEV; + + lp = fc->lp; + + /* Remove the instance from fcoe's list */ + write_lock_bh(&fci->fcoe_hostlist_lock); + list_del(&fc->list); + write_unlock_bh(&fci->fcoe_hostlist_lock); + + /* Cleanup the fc_lport */ + fc_lport_destroy(lp); + fc_fcp_destroy(lp); + if (lp->emp) + fc_exch_mgr_free(lp->emp); + + /* Detach from the scsi-ml */ + fc_remove_host(lp->host); + scsi_remove_host(lp->host); + + /* Don't listen for Ethernet packets anymore */ + dev_remove_pack(&fc->fcoe_packet_type); + + /* 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 */ + for (idx = 0; idx < NR_CPUS; idx++) { + if (fci->fcoe_percpu[idx]) { + pp = fci->fcoe_percpu[idx]; + 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 == fc->lp) { + __skb_unlink(skb, list); + kfree_skb(skb); + } + } + spin_unlock_bh(&pp->fcoe_rx_list.lock); + } + } + + /* Free existing skbs */ + fcoe_clean_pending_queue(lp); + + /* Free memory used by statistical counters */ + for_each_online_cpu(cpu) { + p = lp->dev_stats[cpu]; + if (p) { + lp->dev_stats[cpu] = NULL; + kfree(p); + } + } + + /* Release the net_device and Scsi_Host */ + dev_put(fc->real_dev); + scsi_host_put(lp->host); + return 0; +} + +/* + * Return zero if link is OK for use by FCoE. + * 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. + */ +int fcoe_link_ok(struct fc_lport *lp) +{ + struct fcoe_softc *fc = (struct fcoe_softc *)lp->drv_priv; + 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; +} + +static struct libfc_function_template fcoe_libfc_fcn_templ = { + .frame_send = fcoe_xmit, +}; + +static int lport_config(struct fc_lport *lp, struct Scsi_Host *shost) +{ + int i = 0; + struct fcoe_dev_stats *p; + + lp->host = shost; + lp->drv_priv = (void *)(lp + 1); + + lp->emp = fc_exch_mgr_alloc(lp, FC_CLASS_3, + FCOE_MIN_XID, FCOE_MAX_XID); + if (!lp->emp) + return -ENOMEM; + + lp->link_status = 0; + lp->max_retry_count = 3; + lp->e_d_tov = 2 * 1000; /* FC-FS default */ + lp->r_a_tov = 2 * 2 * 1000; + lp->service_params = (FCP_SPPF_INIT_FCN | FCP_SPPF_RD_XRDY_DIS | + FCP_SPPF_RETRY | FCP_SPPF_CONF_COMPL); + + /* + * allocate per cpu stats block + */ + for_each_online_cpu(i) { + p = kzalloc(sizeof(struct fcoe_dev_stats), GFP_KERNEL); + if (p) + lp->dev_stats[i] = p; + } + + /* Finish fc_lport configuration */ + fc_lport_config(lp); + + return 0; +} + +static int net_config(struct fc_lport *lp) +{ + u32 mfs; + u64 wwnn, wwpn; + struct net_device *net_dev; + struct fcoe_softc *fc = (struct fcoe_softc *)lp->drv_priv; + u8 flogi_maddr[ETH_ALEN]; + + /* Require support for get_pauseparam ethtool op. */ + net_dev = fc->real_dev; + if (!net_dev->ethtool_ops && (net_dev->priv_flags & IFF_802_1Q_VLAN)) + net_dev = vlan_dev_real_dev(net_dev); + if (!net_dev->ethtool_ops || !net_dev->ethtool_ops->get_pauseparam) + return -EOPNOTSUPP; + + fc->phys_dev = net_dev; + + /* 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)); + fc_set_mfs(lp, mfs); + + lp->link_status = ~FC_PAUSE & ~FC_LINK_UP; + if (!fcoe_link_ok(lp)) + lp->link_status |= FC_LINK_UP; + + if (fc->real_dev->features & NETIF_F_SG) + lp->capabilities = TRANS_C_SG; + + + skb_queue_head_init(&fc->fcoe_pending_queue); + + memcpy(lp->ifname, fc->real_dev->name, IFNAMSIZ); + + /* 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; +} + +static void shost_config(struct fc_lport *lp) +{ + lp->host->max_lun = FCOE_MAX_LUN; + lp->host->max_id = FCOE_MAX_FCP_TARGET; + lp->host->max_channel = 0; + lp->host->transportt = fcoe_transport_template; +} + +static int libfc_config(struct fc_lport *lp) +{ + /* Set the function pointers set by the LLDD */ + memcpy(&lp->tt, &fcoe_libfc_fcn_templ, + sizeof(struct libfc_function_template)); + + if (fc_fcp_init(lp)) + return -ENOMEM; + fc_exch_init(lp); + fc_lport_init(lp); + fc_rport_init(lp); + fc_ns_init(lp); + fc_attr_init(lp); + + return 0; +} + +/* + * This function creates the fcoe interface + * create struct fcdev which is a shared structure between opefc + * and transport level protocol. + */ +int fcoe_create_interface(const char *ifname) +{ + struct fc_lport *lp = NULL; + struct fcoe_softc *fc; + struct net_device *net_dev; + struct Scsi_Host *shost; + struct fcoe_info *fci = &fcoei; + int rc = 0; + + net_dev = dev_get_by_name(&init_net, ifname); + if (net_dev == NULL) { + FC_DBG("could not get network device for %s", + ifname); + return -ENODEV; + } + + if (fcoe_find_fc_lport(net_dev->name) != NULL) { + rc = -EEXIST; + goto out_put_dev; + } + + shost = scsi_host_alloc(&fcoe_driver_template, + sizeof(struct fc_lport) + + sizeof(struct fcoe_softc)); + + if (!shost) { + FC_DBG("Could not allocate host structure\n"); + rc = -ENOMEM; + goto out_put_dev; + } + + lp = shost_priv(shost); + rc = lport_config(lp, shost); + if (rc) + goto out_host_put; + + /* Configure the fcoe_softc */ + fc = (struct fcoe_softc *)lp->drv_priv; + fc->lp = lp; + fc->real_dev = net_dev; + shost_config(lp); + + + /* Add the new host to the SCSI-ml */ + rc = scsi_add_host(lp->host, NULL); + if (rc) { + FC_DBG("error on scsi_add_host\n"); + goto out_lp_destroy; + } + + sprintf(fc_host_symbolic_name(lp->host), "%s v%s over %s", + FCOE_DRIVER_NAME, FCOE_VERSION, + ifname); + + /* Configure netdev and networking properties of the lp */ + rc = net_config(lp); + if (rc) + goto out_lp_destroy; + + /* Initialize the library */ + rc = libfc_config(lp); + if (rc) + goto out_lp_destroy; + + write_lock_bh(&fci->fcoe_hostlist_lock); + list_add_tail(&fc->list, &fci->fcoe_hostlist); + write_unlock_bh(&fci->fcoe_hostlist_lock); + + lp->boot_time = jiffies; + + fc_fabric_login(lp); + + return rc; + +out_lp_destroy: + fc_exch_mgr_free(lp->emp); /* Free the EM */ +out_host_put: + scsi_host_put(lp->host); +out_put_dev: + dev_put(net_dev); + return rc; +} + +void fcoe_clean_pending_queue(struct fc_lport *lp) +{ + struct fcoe_softc *fc = lp->drv_priv; + 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); +} diff --git a/drivers/scsi/fcoe/fcoeinit.c b/drivers/scsi/fcoe/fcoeinit.c new file mode 100644 index 0000000..e069835 --- /dev/null +++ b/drivers/scsi/fcoe/fcoeinit.c @@ -0,0 +1,440 @@ +/* + * Copyright(c) 2007 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "fcoe_def.h" + +MODULE_AUTHOR("Open-FCoE.org"); +MODULE_DESCRIPTION("FCoE"); +MODULE_LICENSE("GPL"); + +/* + * Static functions and variables definations + */ +#ifdef CONFIG_HOTPLUG_CPU +static int fcoe_cpu_callback(struct notifier_block *, ulong, void *); +#endif /* CONFIG_HOTPLUG_CPU */ +static int fcoe_device_notification(struct notifier_block *, ulong, void *); +static void fcoe_dev_setup(void); +static void fcoe_dev_cleanup(void); + +struct scsi_transport_template *fcoe_transport_template; + +static int fcoe_reset(struct Scsi_Host *shost) +{ + struct fc_lport *lp = shost_priv(shost); + fc_lport_enter_reset(lp); + return 0; +} + +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, + + .get_host_port_id = fc_get_host_port_id, + .show_host_port_id = 1, + .get_host_speed = fc_get_host_speed, + .show_host_speed = 1, + .get_host_port_type = fc_get_host_port_type, + .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, + + .get_host_fabric_name = fc_get_host_fabric_name, + .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, +}; + +struct fcoe_percpu_s *fcoe_percpu[NR_CPUS]; + +#ifdef CONFIG_HOTPLUG_CPU +static struct notifier_block fcoe_cpu_notifier = { + .notifier_call = fcoe_cpu_callback, +}; +#endif /* CONFIG_HOTPLUG_CPU */ + +/* + * notification function from net device + */ +static struct notifier_block fcoe_notifier = { + .notifier_call = fcoe_device_notification, +}; + +#ifdef CONFIG_HOTPLUG_CPU +/* + * create percpu stats block + * called by cpu add/remove notifier + */ +static void fcoe_create_percpu_data(int cpu) +{ + struct fc_lport *lp; + struct fcoe_softc *fc; + struct fcoe_dev_stats *p; + struct fcoe_info *fci = &fcoei; + + write_lock_bh(&fci->fcoe_hostlist_lock); + list_for_each_entry(fc, &fci->fcoe_hostlist, list) { + lp = fc->lp; + if (lp->dev_stats[cpu] == NULL) { + p = kzalloc(sizeof(struct fcoe_dev_stats), GFP_KERNEL); + if (p) + lp->dev_stats[cpu] = p; + } + } + write_unlock_bh(&fci->fcoe_hostlist_lock); +} + +/* + * destroy percpu stats block + * called by cpu add/remove notifier + */ +static void fcoe_destroy_percpu_data(int cpu) +{ + struct fcoe_dev_stats *p; + struct fc_lport *lp; + struct fcoe_softc *fc; + struct fcoe_info *fci = &fcoei; + + write_lock_bh(&fci->fcoe_hostlist_lock); + list_for_each_entry(fc, &fci->fcoe_hostlist, list) { + lp = fc->lp; + p = lp->dev_stats[cpu]; + if (p != NULL) { + lp->dev_stats[cpu] = NULL; + kfree(p); + } + } + write_unlock_bh(&fci->fcoe_hostlist_lock); +} + +/* + * Get notified when a cpu comes on/off. Be hotplug friendly. + */ +static int fcoe_cpu_callback(struct notifier_block *nfb, unsigned long action, + void *hcpu) +{ + unsigned int cpu = (unsigned long)hcpu; + + switch (action) { + case CPU_ONLINE: + fcoe_create_percpu_data(cpu); + break; + case CPU_DEAD: + fcoe_destroy_percpu_data(cpu); + break; + default: + break; + } + return NOTIFY_OK; +} +#endif /* CONFIG_HOTPLUG_CPU */ + +/* + * function to setup link change notification interface + */ +static void fcoe_dev_setup(void) +{ + /* + * here setup a interface specific wd time to + * monitor the link state + */ + register_netdevice_notifier(&fcoe_notifier); +} + +/* + * function to cleanup link change notification interface + */ +static void fcoe_dev_cleanup(void) +{ + unregister_netdevice_notifier(&fcoe_notifier); +} + +/* + * This function is called by the ethernet driver + * this is called in case of link change event + */ +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; + struct fcoe_info *fci = &fcoei; + u16 new_status; + u32 mfs; + int rc = NOTIFY_OK; + + read_lock(&fci->fcoe_hostlist_lock); + list_for_each_entry(fc, &fci->fcoe_hostlist, list) { + if (fc->real_dev == real_dev) { + lp = fc->lp; + break; + } + } + read_unlock(&fci->fcoe_hostlist_lock); + if (lp == NULL) { + rc = NOTIFY_DONE; + goto out; + } + + new_status = lp->link_status; + switch (event) { + case NETDEV_DOWN: + case NETDEV_GOING_DOWN: + new_status &= ~FC_LINK_UP; + break; + case NETDEV_UP: + case NETDEV_CHANGE: + new_status &= ~FC_LINK_UP; + if (!fcoe_link_ok(lp)) + new_status |= FC_LINK_UP; + break; + case NETDEV_CHANGEMTU: + mfs = fc->real_dev->mtu - + (sizeof(struct fcoe_hdr) + + sizeof(struct fcoe_crc_eof)); + if (fc->user_mfs && fc->user_mfs < mfs) + mfs = fc->user_mfs; + if (mfs >= FC_MIN_MAX_FRAME) + fc_set_mfs(lp, mfs); + new_status &= ~FC_LINK_UP; + if (!fcoe_link_ok(lp)) + new_status |= FC_LINK_UP; + break; + case NETDEV_REGISTER: + break; + default: + FC_DBG("unknown event %ld call", event); + } + if (lp->link_status != new_status) { + if ((new_status & FC_LINK_UP) == FC_LINK_UP) + fc_linkup(lp); + else { + stats = lp->dev_stats[smp_processor_id()]; + stats->LinkFailureCount++; + fc_linkdown(lp); + fcoe_clean_pending_queue(lp); + } + } +out: + return rc; +} + +static void trimstr(char *str, int len) +{ + char *cp = str + len; + while (--cp >= str && *cp == '\n') + *cp = '\0'; +} + +static ssize_t fcoe_destroy(struct kobject *kobj, struct kobj_attribute *attr, + const char *buffer, size_t size) +{ + char ifname[40]; + strcpy(ifname, buffer); + trimstr(ifname, strlen(ifname)); + fcoe_destroy_interface(ifname); + return size; +} + +static ssize_t fcoe_create(struct kobject *kobj, struct kobj_attribute *attr, + const char *buffer, size_t size) +{ + char ifname[40]; + strcpy(ifname, buffer); + trimstr(ifname, strlen(ifname)); + fcoe_create_interface(ifname); + return size; +} + +static const struct kobj_attribute fcoe_destroyattr = \ + __ATTR(destroy, S_IWUSR, NULL, fcoe_destroy); +static const struct kobj_attribute fcoe_createattr = \ + __ATTR(create, S_IWUSR, NULL, fcoe_create); + +/* + * Initialization routine + * 1. Will create fc transport software structure + * 2. initialize the link list of port information structure + */ +static int __init fcoeinit(void) +{ + int rc = 0; + int cpu; + struct fcoe_percpu_s *p; + struct fcoe_info *fci = &fcoei; + + rc = sysfs_create_file(&THIS_MODULE->mkobj.kobj, + &fcoe_destroyattr.attr); + if (!rc) + rc = sysfs_create_file(&THIS_MODULE->mkobj.kobj, + &fcoe_createattr.attr); + + if (rc) + return rc; + + rwlock_init(&fci->fcoe_hostlist_lock); + +#ifdef CONFIG_HOTPLUG_CPU + register_cpu_notifier(&fcoe_cpu_notifier); +#endif /* CONFIG_HOTPLUG_CPU */ + + /* + * initialize per CPU interrupt thread + */ + for_each_online_cpu(cpu) { + p = kzalloc(sizeof(struct fcoe_percpu_s), GFP_KERNEL); + if (p) { + p->thread = kthread_create(fcoe_percpu_receive_thread, + (void *)p, + "fcoethread/%d", cpu); + + /* + * if there is no error then bind the thread to the cpu + * initialize the semaphore and skb queue head + */ + if (likely(!IS_ERR(p->thread))) { + p->cpu = cpu; + fci->fcoe_percpu[cpu] = p; + skb_queue_head_init(&p->fcoe_rx_list); + kthread_bind(p->thread, cpu); + wake_up_process(p->thread); + } else { + fci->fcoe_percpu[cpu] = NULL; + kfree(p); + + } + } + } + if (rc < 0) { + FC_DBG("failed to initialize proc intrerface\n"); + rc = -ENODEV; + goto out_chrdev; + } + + /* + * setup link change notification + */ + fcoe_dev_setup(); + + init_timer(&fci->timer); + fci->timer.data = (ulong) fci; + fci->timer.function = fcoe_watchdog; + fci->timer.expires = (jiffies + (10 * HZ)); + add_timer(&fci->timer); + + fcoe_transport_template = + fc_attach_transport(&fcoe_transport_function); + + if (fcoe_transport_template == NULL) { + FC_DBG("fail to attach fc transport"); + return -1; + } + + return 0; + +out_chrdev: +#ifdef CONFIG_HOTPLUG_CPU + unregister_cpu_notifier(&fcoe_cpu_notifier); +#endif /* CONFIG_HOTPLUG_CPU */ + return rc; +} + +static void __exit fcoe_exit(void) +{ + u32 idx; + struct fcoe_softc *fc, *tmp; + struct fc_lport *lp; + struct fcoe_info *fci = &fcoei; + struct fcoe_percpu_s *p; + struct sk_buff *skb; + + /* + * Stop all call back interfaces + */ +#ifdef CONFIG_HOTPLUG_CPU + unregister_cpu_notifier(&fcoe_cpu_notifier); +#endif /* CONFIG_HOTPLUG_CPU */ + fcoe_dev_cleanup(); + + /* + * stop timer + */ + del_timer_sync(&fci->timer); + + /* + * assuming that at this time there will be no + * ioctl in prograss, therefore we do not need to lock the + * list. + */ + list_for_each_entry_safe(fc, tmp, &fci->fcoe_hostlist, list) { + lp = fc->lp; + fcoe_destroy_interface(lp->ifname); + } + + for (idx = 0; idx < NR_CPUS; idx++) { + if (fci->fcoe_percpu[idx]) { + kthread_stop(fci->fcoe_percpu[idx]->thread); + p = fci->fcoe_percpu[idx]; + 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); + if (fci->fcoe_percpu[idx]->crc_eof_page) + put_page(fci->fcoe_percpu[idx]->crc_eof_page); + kfree(fci->fcoe_percpu[idx]); + } + } + + fc_release_transport(fcoe_transport_template); +} + +module_init(fcoeinit); +module_exit(fcoe_exit); -- 1.5.2.4