/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int debug_fcoe; #define FCOE_MAX_QUEUE_DEPTH 256 /* destination address mode */ #define FCOE_GW_ADDR_MODE 0x00 #define FCOE_FCOUI_ADDR_MODE 0x01 #define FCOE_WORD_TO_BYTE 4 MODULE_AUTHOR("Open-FCoE.org"); MODULE_DESCRIPTION("FCoE"); MODULE_LICENSE("GPL"); /* fcoe host list */ LIST_HEAD(fcoe_hostlist); DEFINE_RWLOCK(fcoe_hostlist_lock); DEFINE_TIMER(fcoe_timer, NULL, 0, 0); struct fcoe_percpu_s *fcoe_percpu[NR_CPUS]; /* Function Prototyes */ static int fcoe_check_wait_queue(struct fc_lport *); static void fcoe_insert_wait_queue_head(struct fc_lport *, struct sk_buff *); static void fcoe_insert_wait_queue(struct fc_lport *, struct sk_buff *); static void fcoe_recv_flogi(struct fcoe_softc *, struct fc_frame *, u8 *); #ifdef CONFIG_HOTPLUG_CPU static int fcoe_cpu_callback(struct notifier_block *, ulong, void *); #endif /* CONFIG_HOTPLUG_CPU */ static int fcoe_device_notification(struct notifier_block *, ulong, void *); static void fcoe_dev_setup(void); static void fcoe_dev_cleanup(void); /* notification function from net device */ static struct notifier_block fcoe_notifier = { .notifier_call = fcoe_device_notification, }; #ifdef CONFIG_HOTPLUG_CPU static struct notifier_block fcoe_cpu_notifier = { .notifier_call = fcoe_cpu_callback, }; /** * fcoe_create_percpu_data - creates the associated cpu data * @cpu: index for the cpu where fcoe cpu data will be created * * create percpu stats block, from cpu add notifier * * Returns: none **/ static void fcoe_create_percpu_data(int cpu) { struct fc_lport *lp; struct fcoe_softc *fc; write_lock_bh(&fcoe_hostlist_lock); list_for_each_entry(fc, &fcoe_hostlist, list) { lp = fc->lp; if (lp->dev_stats[cpu] == NULL) lp->dev_stats[cpu] = kzalloc(sizeof(struct fcoe_dev_stats), GFP_KERNEL); } write_unlock_bh(&fcoe_hostlist_lock); } /** * fcoe_destroy_percpu_data - destroys the associated cpu data * @cpu: index for the cpu where fcoe cpu data will destroyed * * destroy percpu stats block called by cpu add/remove notifier * * Retuns: none **/ static void fcoe_destroy_percpu_data(int cpu) { struct fc_lport *lp; struct fcoe_softc *fc; write_lock_bh(&fcoe_hostlist_lock); list_for_each_entry(fc, &fcoe_hostlist, list) { lp = fc->lp; kfree(lp->dev_stats[cpu]); lp->dev_stats[cpu] = NULL; } write_unlock_bh(&fcoe_hostlist_lock); } /** * fcoe_cpu_callback - fcoe cpu hotplug event callback * @nfb: callback data block * @action: event triggering the callback * @hcpu: index for the cpu of this event * * this creates or destroys per cpu data for fcoe * * Returns NOTIFY_OK always. **/ static int fcoe_cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu) { unsigned int cpu = (unsigned long)hcpu; switch (action) { case CPU_ONLINE: fcoe_create_percpu_data(cpu); break; case CPU_DEAD: fcoe_destroy_percpu_data(cpu); break; default: break; } return NOTIFY_OK; } #endif /* CONFIG_HOTPLUG_CPU */ /** * 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 fcoe_dev_stats *stats; struct fc_frame_header *fh; unsigned short oxid; int cpu_idx; struct fcoe_percpu_s *fps; 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; } /* * 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; cpu_idx = 0; #ifdef CONFIG_SMP /* * The incoming frame exchange id(oxid) is ANDed with num of online * cpu bits to get cpu_idx and then this cpu_idx is used for selecting * a per cpu kernel thread from fcoe_percpu. In case the cpu is * offline or no kernel thread for derived cpu_idx then cpu_idx is * initialize to first online cpu index. */ cpu_idx = oxid & (num_online_cpus() - 1); if (!fcoe_percpu[cpu_idx] || !cpu_online(cpu_idx)) cpu_idx = first_cpu(cpu_online_map); #endif fps = 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 if (stats) stats->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; int cpu_idx; cpu_idx = get_cpu(); fps = 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; } /** * 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 = fcoe_softc(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_COMPLETE; 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); 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_802_3); 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; /* update tx stats: regardless if LLD fails */ stats = lp->dev_stats[smp_processor_id()]; if (stats) { 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) { fcoe_insert_wait_queue(lp, skb); if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH) fc_pause(lp); } 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, 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 = 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); if (unlikely(FC_FCOE_DECAPS_VER(hp) != FC_FCOE_VER)) { if (stats) { if (stats->ErrorFrames < 5) FC_DBG("unknown FCoE version %x", FC_FCOE_DECAPS_VER(hp)); stats->ErrorFrames++; } kfree_skb(skb); continue; } skb_pull(skb, sizeof(struct fcoe_hdr)); fr_len = skb->len - sizeof(struct fcoe_crc_eof); if (stats) { 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) 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 put fcoe to be paused state * 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 fc_lport *lp; struct fcoe_softc *fc; int paused = 0; read_lock(&fcoe_hostlist_lock); list_for_each_entry(fc, &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(&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 0 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) { int rc, unpause = 0; int paused = 0; struct sk_buff *skb; struct fcoe_softc *fc; fc = fcoe_softc(lp); 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; } /** * fcoe_insert_wait_queue_head - puts skb to fcoe pending queue head * @lp: the fc_port for this skb * @skb: the associated skb to be xmitted * * Returns: none **/ static void fcoe_insert_wait_queue_head(struct fc_lport *lp, struct sk_buff *skb) { struct fcoe_softc *fc; fc = fcoe_softc(lp); spin_lock_bh(&fc->fcoe_pending_queue.lock); __skb_queue_head(&fc->fcoe_pending_queue, skb); spin_unlock_bh(&fc->fcoe_pending_queue.lock); } /** * fcoe_insert_wait_queue - put the skb into fcoe pending queue tail * @lp: the fc_port for this skb * @skb: the associated skb to be xmitted * * Returns: none **/ static void fcoe_insert_wait_queue(struct fc_lport *lp, struct sk_buff *skb) { struct fcoe_softc *fc; fc = fcoe_softc(lp); spin_lock_bh(&fc->fcoe_pending_queue.lock); __skb_queue_tail(&fc->fcoe_pending_queue, skb); spin_unlock_bh(&fc->fcoe_pending_queue.lock); } /** * fcoe_dev_setup - 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); } /** * 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; u16 new_status; 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_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 (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()]; if (stats) 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 - holds the nic driver module by try_module_get() for * the corresponding netdev. * @netdev: the target 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_get - releases the nic driver module by module_put for * the corresponding netdev. * @netdev: the target 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; } /* pass to transport */ rc = fcoe_transport_release(netdev); if (rc) { printk(KERN_ERR "fcoe: fcoe_transport_release(%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); /* pass to transport */ rc = fcoe_transport_attach(netdev); if (rc) { printk(KERN_ERR "fcoe: fcoe_transport_attach(%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 = fcoe_softc(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 - frees skb of the corresponding lport from the per * cpu queue. * @lp: the fc_lport */ void fcoe_percpu_clean(struct fc_lport *lp) { int idx; struct fcoe_percpu_s *pp; struct fcoe_rcv_info *fr; struct sk_buff_head *list; struct sk_buff *skb, *next; struct sk_buff *head; for (idx = 0; idx < NR_CPUS; idx++) { if (fcoe_percpu[idx]) { pp = 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 == 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 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 = fcoe_softc(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 * * Initialization routine * 1. Will create fc transport software structure * 2. initialize the link list of port information structure * * Returns 0 on success, negative on failure **/ static int __init fcoe_init(void) { int cpu; struct fcoe_percpu_s *p; INIT_LIST_HEAD(&fcoe_hostlist); rwlock_init(&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; fcoe_percpu[cpu] = p; skb_queue_head_init(&p->fcoe_rx_list); kthread_bind(p->thread, cpu); wake_up_process(p->thread); } else { fcoe_percpu[cpu] = NULL; kfree(p); } } } /* * setup link change notification */ fcoe_dev_setup(); init_timer(&fcoe_timer); fcoe_timer.data = 0; fcoe_timer.function = fcoe_watchdog; fcoe_timer.expires = (jiffies + (10 * HZ)); add_timer(&fcoe_timer); /* initiatlize the fcoe transport */ fcoe_transport_init(); fcoe_sw_init(); return 0; } module_init(fcoe_init); /** * fcoe_exit - fcoe module unloading cleanup * * Returns 0 on success, negative on failure **/ static void __exit fcoe_exit(void) { u32 idx; struct fcoe_softc *fc, *tmp; 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(&fcoe_timer); /* releases the assocaited fcoe transport for each lport */ list_for_each_entry_safe(fc, tmp, &fcoe_hostlist, list) fcoe_transport_release(fc->real_dev); for (idx = 0; idx < NR_CPUS; idx++) { if (fcoe_percpu[idx]) { kthread_stop(fcoe_percpu[idx]->thread); p = 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 (fcoe_percpu[idx]->crc_eof_page) put_page(fcoe_percpu[idx]->crc_eof_page); kfree(fcoe_percpu[idx]); } } /* remove sw trasnport */ fcoe_sw_exit(); /* detach the transport */ fcoe_transport_exit(); } module_exit(fcoe_exit);