eab6d18d20
Many (but not all) drivers check to see whether there is a vlan group configured before using a tag stored in the skb. There's not much point in this check since it just throws away data that should only be present in the expected circumstances. However, it will soon be legal and expected to get a vlan tag when no vlan group is configured, so remove this check from all drivers to avoid dropping the tags. Signed-off-by: Jesse Gross <jesse@nicira.com> Signed-off-by: David S. Miller <davem@davemloft.net>
3265 lines
78 KiB
C
3265 lines
78 KiB
C
/*
|
|
* Linux network driver for Brocade Converged Network Adapter.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License (GPL) Version 2 as
|
|
* published by the Free Software Foundation
|
|
*
|
|
* This program is distributed in the hope that 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.
|
|
*/
|
|
/*
|
|
* Copyright (c) 2005-2010 Brocade Communications Systems, Inc.
|
|
* All rights reserved
|
|
* www.brocade.com
|
|
*/
|
|
#include <linux/netdevice.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/in.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/ip.h>
|
|
|
|
#include "bnad.h"
|
|
#include "bna.h"
|
|
#include "cna.h"
|
|
|
|
static DEFINE_MUTEX(bnad_fwimg_mutex);
|
|
|
|
/*
|
|
* Module params
|
|
*/
|
|
static uint bnad_msix_disable;
|
|
module_param(bnad_msix_disable, uint, 0444);
|
|
MODULE_PARM_DESC(bnad_msix_disable, "Disable MSIX mode");
|
|
|
|
static uint bnad_ioc_auto_recover = 1;
|
|
module_param(bnad_ioc_auto_recover, uint, 0444);
|
|
MODULE_PARM_DESC(bnad_ioc_auto_recover, "Enable / Disable auto recovery");
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
u32 bnad_rxqs_per_cq = 2;
|
|
|
|
static const u8 bnad_bcast_addr[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
|
|
|
/*
|
|
* Local MACROS
|
|
*/
|
|
#define BNAD_TX_UNMAPQ_DEPTH (bnad->txq_depth * 2)
|
|
|
|
#define BNAD_RX_UNMAPQ_DEPTH (bnad->rxq_depth)
|
|
|
|
#define BNAD_GET_MBOX_IRQ(_bnad) \
|
|
(((_bnad)->cfg_flags & BNAD_CF_MSIX) ? \
|
|
((_bnad)->msix_table[(_bnad)->msix_num - 1].vector) : \
|
|
((_bnad)->pcidev->irq))
|
|
|
|
#define BNAD_FILL_UNMAPQ_MEM_REQ(_res_info, _num, _depth) \
|
|
do { \
|
|
(_res_info)->res_type = BNA_RES_T_MEM; \
|
|
(_res_info)->res_u.mem_info.mem_type = BNA_MEM_T_KVA; \
|
|
(_res_info)->res_u.mem_info.num = (_num); \
|
|
(_res_info)->res_u.mem_info.len = \
|
|
sizeof(struct bnad_unmap_q) + \
|
|
(sizeof(struct bnad_skb_unmap) * ((_depth) - 1)); \
|
|
} while (0)
|
|
|
|
/*
|
|
* Reinitialize completions in CQ, once Rx is taken down
|
|
*/
|
|
static void
|
|
bnad_cq_cmpl_init(struct bnad *bnad, struct bna_ccb *ccb)
|
|
{
|
|
struct bna_cq_entry *cmpl, *next_cmpl;
|
|
unsigned int wi_range, wis = 0, ccb_prod = 0;
|
|
int i;
|
|
|
|
BNA_CQ_QPGE_PTR_GET(ccb_prod, ccb->sw_qpt, cmpl,
|
|
wi_range);
|
|
|
|
for (i = 0; i < ccb->q_depth; i++) {
|
|
wis++;
|
|
if (likely(--wi_range))
|
|
next_cmpl = cmpl + 1;
|
|
else {
|
|
BNA_QE_INDX_ADD(ccb_prod, wis, ccb->q_depth);
|
|
wis = 0;
|
|
BNA_CQ_QPGE_PTR_GET(ccb_prod, ccb->sw_qpt,
|
|
next_cmpl, wi_range);
|
|
}
|
|
cmpl->valid = 0;
|
|
cmpl = next_cmpl;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Frees all pending Tx Bufs
|
|
* At this point no activity is expected on the Q,
|
|
* so DMA unmap & freeing is fine.
|
|
*/
|
|
static void
|
|
bnad_free_all_txbufs(struct bnad *bnad,
|
|
struct bna_tcb *tcb)
|
|
{
|
|
u16 unmap_cons;
|
|
struct bnad_unmap_q *unmap_q = tcb->unmap_q;
|
|
struct bnad_skb_unmap *unmap_array;
|
|
struct sk_buff *skb = NULL;
|
|
int i;
|
|
|
|
unmap_array = unmap_q->unmap_array;
|
|
|
|
unmap_cons = 0;
|
|
while (unmap_cons < unmap_q->q_depth) {
|
|
skb = unmap_array[unmap_cons].skb;
|
|
if (!skb) {
|
|
unmap_cons++;
|
|
continue;
|
|
}
|
|
unmap_array[unmap_cons].skb = NULL;
|
|
|
|
pci_unmap_single(bnad->pcidev,
|
|
pci_unmap_addr(&unmap_array[unmap_cons],
|
|
dma_addr), skb_headlen(skb),
|
|
PCI_DMA_TODEVICE);
|
|
|
|
pci_unmap_addr_set(&unmap_array[unmap_cons], dma_addr, 0);
|
|
unmap_cons++;
|
|
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
|
|
pci_unmap_page(bnad->pcidev,
|
|
pci_unmap_addr(&unmap_array[unmap_cons],
|
|
dma_addr),
|
|
skb_shinfo(skb)->frags[i].size,
|
|
PCI_DMA_TODEVICE);
|
|
pci_unmap_addr_set(&unmap_array[unmap_cons], dma_addr,
|
|
0);
|
|
unmap_cons++;
|
|
}
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
}
|
|
|
|
/* Data Path Handlers */
|
|
|
|
/*
|
|
* bnad_free_txbufs : Frees the Tx bufs on Tx completion
|
|
* Can be called in a) Interrupt context
|
|
* b) Sending context
|
|
* c) Tasklet context
|
|
*/
|
|
static u32
|
|
bnad_free_txbufs(struct bnad *bnad,
|
|
struct bna_tcb *tcb)
|
|
{
|
|
u32 sent_packets = 0, sent_bytes = 0;
|
|
u16 wis, unmap_cons, updated_hw_cons;
|
|
struct bnad_unmap_q *unmap_q = tcb->unmap_q;
|
|
struct bnad_skb_unmap *unmap_array;
|
|
struct sk_buff *skb;
|
|
int i;
|
|
|
|
/*
|
|
* Just return if TX is stopped. This check is useful
|
|
* when bnad_free_txbufs() runs out of a tasklet scheduled
|
|
* before bnad_cb_tx_cleanup() cleared BNAD_RF_TX_STARTED bit
|
|
* but this routine runs actually after the cleanup has been
|
|
* executed.
|
|
*/
|
|
if (!test_bit(BNAD_RF_TX_STARTED, &bnad->run_flags))
|
|
return 0;
|
|
|
|
updated_hw_cons = *(tcb->hw_consumer_index);
|
|
|
|
wis = BNA_Q_INDEX_CHANGE(tcb->consumer_index,
|
|
updated_hw_cons, tcb->q_depth);
|
|
|
|
BUG_ON(!(wis <= BNA_QE_IN_USE_CNT(tcb, tcb->q_depth)));
|
|
|
|
unmap_array = unmap_q->unmap_array;
|
|
unmap_cons = unmap_q->consumer_index;
|
|
|
|
prefetch(&unmap_array[unmap_cons + 1]);
|
|
while (wis) {
|
|
skb = unmap_array[unmap_cons].skb;
|
|
|
|
unmap_array[unmap_cons].skb = NULL;
|
|
|
|
sent_packets++;
|
|
sent_bytes += skb->len;
|
|
wis -= BNA_TXQ_WI_NEEDED(1 + skb_shinfo(skb)->nr_frags);
|
|
|
|
pci_unmap_single(bnad->pcidev,
|
|
pci_unmap_addr(&unmap_array[unmap_cons],
|
|
dma_addr), skb_headlen(skb),
|
|
PCI_DMA_TODEVICE);
|
|
pci_unmap_addr_set(&unmap_array[unmap_cons], dma_addr, 0);
|
|
BNA_QE_INDX_ADD(unmap_cons, 1, unmap_q->q_depth);
|
|
|
|
prefetch(&unmap_array[unmap_cons + 1]);
|
|
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
|
|
prefetch(&unmap_array[unmap_cons + 1]);
|
|
|
|
pci_unmap_page(bnad->pcidev,
|
|
pci_unmap_addr(&unmap_array[unmap_cons],
|
|
dma_addr),
|
|
skb_shinfo(skb)->frags[i].size,
|
|
PCI_DMA_TODEVICE);
|
|
pci_unmap_addr_set(&unmap_array[unmap_cons], dma_addr,
|
|
0);
|
|
BNA_QE_INDX_ADD(unmap_cons, 1, unmap_q->q_depth);
|
|
}
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
|
|
/* Update consumer pointers. */
|
|
tcb->consumer_index = updated_hw_cons;
|
|
unmap_q->consumer_index = unmap_cons;
|
|
|
|
tcb->txq->tx_packets += sent_packets;
|
|
tcb->txq->tx_bytes += sent_bytes;
|
|
|
|
return sent_packets;
|
|
}
|
|
|
|
/* Tx Free Tasklet function */
|
|
/* Frees for all the tcb's in all the Tx's */
|
|
/*
|
|
* Scheduled from sending context, so that
|
|
* the fat Tx lock is not held for too long
|
|
* in the sending context.
|
|
*/
|
|
static void
|
|
bnad_tx_free_tasklet(unsigned long bnad_ptr)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)bnad_ptr;
|
|
struct bna_tcb *tcb;
|
|
u32 acked;
|
|
int i, j;
|
|
|
|
for (i = 0; i < bnad->num_tx; i++) {
|
|
for (j = 0; j < bnad->num_txq_per_tx; j++) {
|
|
tcb = bnad->tx_info[i].tcb[j];
|
|
if (!tcb)
|
|
continue;
|
|
if (((u16) (*tcb->hw_consumer_index) !=
|
|
tcb->consumer_index) &&
|
|
(!test_and_set_bit(BNAD_TXQ_FREE_SENT,
|
|
&tcb->flags))) {
|
|
acked = bnad_free_txbufs(bnad, tcb);
|
|
bna_ib_ack(tcb->i_dbell, acked);
|
|
smp_mb__before_clear_bit();
|
|
clear_bit(BNAD_TXQ_FREE_SENT, &tcb->flags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static u32
|
|
bnad_tx(struct bnad *bnad, struct bna_tcb *tcb)
|
|
{
|
|
struct net_device *netdev = bnad->netdev;
|
|
u32 sent;
|
|
|
|
if (test_and_set_bit(BNAD_TXQ_FREE_SENT, &tcb->flags))
|
|
return 0;
|
|
|
|
sent = bnad_free_txbufs(bnad, tcb);
|
|
if (sent) {
|
|
if (netif_queue_stopped(netdev) &&
|
|
netif_carrier_ok(netdev) &&
|
|
BNA_QE_FREE_CNT(tcb, tcb->q_depth) >=
|
|
BNAD_NETIF_WAKE_THRESHOLD) {
|
|
netif_wake_queue(netdev);
|
|
BNAD_UPDATE_CTR(bnad, netif_queue_wakeup);
|
|
}
|
|
bna_ib_ack(tcb->i_dbell, sent);
|
|
} else
|
|
bna_ib_ack(tcb->i_dbell, 0);
|
|
|
|
smp_mb__before_clear_bit();
|
|
clear_bit(BNAD_TXQ_FREE_SENT, &tcb->flags);
|
|
|
|
return sent;
|
|
}
|
|
|
|
/* MSIX Tx Completion Handler */
|
|
static irqreturn_t
|
|
bnad_msix_tx(int irq, void *data)
|
|
{
|
|
struct bna_tcb *tcb = (struct bna_tcb *)data;
|
|
struct bnad *bnad = tcb->bnad;
|
|
|
|
bnad_tx(bnad, tcb);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void
|
|
bnad_reset_rcb(struct bnad *bnad, struct bna_rcb *rcb)
|
|
{
|
|
struct bnad_unmap_q *unmap_q = rcb->unmap_q;
|
|
|
|
rcb->producer_index = 0;
|
|
rcb->consumer_index = 0;
|
|
|
|
unmap_q->producer_index = 0;
|
|
unmap_q->consumer_index = 0;
|
|
}
|
|
|
|
static void
|
|
bnad_free_rxbufs(struct bnad *bnad, struct bna_rcb *rcb)
|
|
{
|
|
struct bnad_unmap_q *unmap_q;
|
|
struct sk_buff *skb;
|
|
|
|
unmap_q = rcb->unmap_q;
|
|
while (BNA_QE_IN_USE_CNT(unmap_q, unmap_q->q_depth)) {
|
|
skb = unmap_q->unmap_array[unmap_q->consumer_index].skb;
|
|
BUG_ON(!(skb));
|
|
unmap_q->unmap_array[unmap_q->consumer_index].skb = NULL;
|
|
pci_unmap_single(bnad->pcidev, pci_unmap_addr(&unmap_q->
|
|
unmap_array[unmap_q->consumer_index],
|
|
dma_addr), rcb->rxq->buffer_size +
|
|
NET_IP_ALIGN, PCI_DMA_FROMDEVICE);
|
|
dev_kfree_skb(skb);
|
|
BNA_QE_INDX_ADD(unmap_q->consumer_index, 1, unmap_q->q_depth);
|
|
BNA_QE_INDX_ADD(rcb->consumer_index, 1, rcb->q_depth);
|
|
}
|
|
|
|
bnad_reset_rcb(bnad, rcb);
|
|
}
|
|
|
|
static void
|
|
bnad_alloc_n_post_rxbufs(struct bnad *bnad, struct bna_rcb *rcb)
|
|
{
|
|
u16 to_alloc, alloced, unmap_prod, wi_range;
|
|
struct bnad_unmap_q *unmap_q = rcb->unmap_q;
|
|
struct bnad_skb_unmap *unmap_array;
|
|
struct bna_rxq_entry *rxent;
|
|
struct sk_buff *skb;
|
|
dma_addr_t dma_addr;
|
|
|
|
alloced = 0;
|
|
to_alloc =
|
|
BNA_QE_FREE_CNT(unmap_q, unmap_q->q_depth);
|
|
|
|
unmap_array = unmap_q->unmap_array;
|
|
unmap_prod = unmap_q->producer_index;
|
|
|
|
BNA_RXQ_QPGE_PTR_GET(unmap_prod, rcb->sw_qpt, rxent, wi_range);
|
|
|
|
while (to_alloc--) {
|
|
if (!wi_range) {
|
|
BNA_RXQ_QPGE_PTR_GET(unmap_prod, rcb->sw_qpt, rxent,
|
|
wi_range);
|
|
}
|
|
skb = alloc_skb(rcb->rxq->buffer_size + NET_IP_ALIGN,
|
|
GFP_ATOMIC);
|
|
if (unlikely(!skb)) {
|
|
BNAD_UPDATE_CTR(bnad, rxbuf_alloc_failed);
|
|
goto finishing;
|
|
}
|
|
skb->dev = bnad->netdev;
|
|
skb_reserve(skb, NET_IP_ALIGN);
|
|
unmap_array[unmap_prod].skb = skb;
|
|
dma_addr = pci_map_single(bnad->pcidev, skb->data,
|
|
rcb->rxq->buffer_size, PCI_DMA_FROMDEVICE);
|
|
pci_unmap_addr_set(&unmap_array[unmap_prod], dma_addr,
|
|
dma_addr);
|
|
BNA_SET_DMA_ADDR(dma_addr, &rxent->host_addr);
|
|
BNA_QE_INDX_ADD(unmap_prod, 1, unmap_q->q_depth);
|
|
|
|
rxent++;
|
|
wi_range--;
|
|
alloced++;
|
|
}
|
|
|
|
finishing:
|
|
if (likely(alloced)) {
|
|
unmap_q->producer_index = unmap_prod;
|
|
rcb->producer_index = unmap_prod;
|
|
smp_mb();
|
|
bna_rxq_prod_indx_doorbell(rcb);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Locking is required in the enable path
|
|
* because it is called from a napi poll
|
|
* context, where the bna_lock is not held
|
|
* unlike the IRQ context.
|
|
*/
|
|
static void
|
|
bnad_enable_txrx_irqs(struct bnad *bnad)
|
|
{
|
|
struct bna_tcb *tcb;
|
|
struct bna_ccb *ccb;
|
|
int i, j;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
for (i = 0; i < bnad->num_tx; i++) {
|
|
for (j = 0; j < bnad->num_txq_per_tx; j++) {
|
|
tcb = bnad->tx_info[i].tcb[j];
|
|
bna_ib_coalescing_timer_set(tcb->i_dbell,
|
|
tcb->txq->ib->ib_config.coalescing_timeo);
|
|
bna_ib_ack(tcb->i_dbell, 0);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < bnad->num_rx; i++) {
|
|
for (j = 0; j < bnad->num_rxp_per_rx; j++) {
|
|
ccb = bnad->rx_info[i].rx_ctrl[j].ccb;
|
|
bnad_enable_rx_irq_unsafe(ccb);
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
static inline void
|
|
bnad_refill_rxq(struct bnad *bnad, struct bna_rcb *rcb)
|
|
{
|
|
struct bnad_unmap_q *unmap_q = rcb->unmap_q;
|
|
|
|
if (!test_and_set_bit(BNAD_RXQ_REFILL, &rcb->flags)) {
|
|
if (BNA_QE_FREE_CNT(unmap_q, unmap_q->q_depth)
|
|
>> BNAD_RXQ_REFILL_THRESHOLD_SHIFT)
|
|
bnad_alloc_n_post_rxbufs(bnad, rcb);
|
|
smp_mb__before_clear_bit();
|
|
clear_bit(BNAD_RXQ_REFILL, &rcb->flags);
|
|
}
|
|
}
|
|
|
|
static u32
|
|
bnad_poll_cq(struct bnad *bnad, struct bna_ccb *ccb, int budget)
|
|
{
|
|
struct bna_cq_entry *cmpl, *next_cmpl;
|
|
struct bna_rcb *rcb = NULL;
|
|
unsigned int wi_range, packets = 0, wis = 0;
|
|
struct bnad_unmap_q *unmap_q;
|
|
struct sk_buff *skb;
|
|
u32 flags;
|
|
u32 qid0 = ccb->rcb[0]->rxq->rxq_id;
|
|
struct bna_pkt_rate *pkt_rt = &ccb->pkt_rate;
|
|
|
|
prefetch(bnad->netdev);
|
|
BNA_CQ_QPGE_PTR_GET(ccb->producer_index, ccb->sw_qpt, cmpl,
|
|
wi_range);
|
|
BUG_ON(!(wi_range <= ccb->q_depth));
|
|
while (cmpl->valid && packets < budget) {
|
|
packets++;
|
|
BNA_UPDATE_PKT_CNT(pkt_rt, ntohs(cmpl->length));
|
|
|
|
if (qid0 == cmpl->rxq_id)
|
|
rcb = ccb->rcb[0];
|
|
else
|
|
rcb = ccb->rcb[1];
|
|
|
|
unmap_q = rcb->unmap_q;
|
|
|
|
skb = unmap_q->unmap_array[unmap_q->consumer_index].skb;
|
|
BUG_ON(!(skb));
|
|
unmap_q->unmap_array[unmap_q->consumer_index].skb = NULL;
|
|
pci_unmap_single(bnad->pcidev,
|
|
pci_unmap_addr(&unmap_q->
|
|
unmap_array[unmap_q->
|
|
consumer_index],
|
|
dma_addr),
|
|
rcb->rxq->buffer_size,
|
|
PCI_DMA_FROMDEVICE);
|
|
BNA_QE_INDX_ADD(unmap_q->consumer_index, 1, unmap_q->q_depth);
|
|
|
|
/* Should be more efficient ? Performance ? */
|
|
BNA_QE_INDX_ADD(rcb->consumer_index, 1, rcb->q_depth);
|
|
|
|
wis++;
|
|
if (likely(--wi_range))
|
|
next_cmpl = cmpl + 1;
|
|
else {
|
|
BNA_QE_INDX_ADD(ccb->producer_index, wis, ccb->q_depth);
|
|
wis = 0;
|
|
BNA_CQ_QPGE_PTR_GET(ccb->producer_index, ccb->sw_qpt,
|
|
next_cmpl, wi_range);
|
|
BUG_ON(!(wi_range <= ccb->q_depth));
|
|
}
|
|
prefetch(next_cmpl);
|
|
|
|
flags = ntohl(cmpl->flags);
|
|
if (unlikely
|
|
(flags &
|
|
(BNA_CQ_EF_MAC_ERROR | BNA_CQ_EF_FCS_ERROR |
|
|
BNA_CQ_EF_TOO_LONG))) {
|
|
dev_kfree_skb_any(skb);
|
|
rcb->rxq->rx_packets_with_error++;
|
|
goto next;
|
|
}
|
|
|
|
skb_put(skb, ntohs(cmpl->length));
|
|
if (likely
|
|
(bnad->rx_csum &&
|
|
(((flags & BNA_CQ_EF_IPV4) &&
|
|
(flags & BNA_CQ_EF_L3_CKSUM_OK)) ||
|
|
(flags & BNA_CQ_EF_IPV6)) &&
|
|
(flags & (BNA_CQ_EF_TCP | BNA_CQ_EF_UDP)) &&
|
|
(flags & BNA_CQ_EF_L4_CKSUM_OK)))
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
else
|
|
skb_checksum_none_assert(skb);
|
|
|
|
rcb->rxq->rx_packets++;
|
|
rcb->rxq->rx_bytes += skb->len;
|
|
skb->protocol = eth_type_trans(skb, bnad->netdev);
|
|
|
|
if (bnad->vlan_grp && (flags & BNA_CQ_EF_VLAN)) {
|
|
struct bnad_rx_ctrl *rx_ctrl =
|
|
(struct bnad_rx_ctrl *)ccb->ctrl;
|
|
if (skb->ip_summed == CHECKSUM_UNNECESSARY)
|
|
vlan_gro_receive(&rx_ctrl->napi, bnad->vlan_grp,
|
|
ntohs(cmpl->vlan_tag), skb);
|
|
else
|
|
vlan_hwaccel_receive_skb(skb,
|
|
bnad->vlan_grp,
|
|
ntohs(cmpl->vlan_tag));
|
|
|
|
} else { /* Not VLAN tagged/stripped */
|
|
struct bnad_rx_ctrl *rx_ctrl =
|
|
(struct bnad_rx_ctrl *)ccb->ctrl;
|
|
if (skb->ip_summed == CHECKSUM_UNNECESSARY)
|
|
napi_gro_receive(&rx_ctrl->napi, skb);
|
|
else
|
|
netif_receive_skb(skb);
|
|
}
|
|
|
|
next:
|
|
cmpl->valid = 0;
|
|
cmpl = next_cmpl;
|
|
}
|
|
|
|
BNA_QE_INDX_ADD(ccb->producer_index, wis, ccb->q_depth);
|
|
|
|
if (likely(ccb)) {
|
|
bna_ib_ack(ccb->i_dbell, packets);
|
|
bnad_refill_rxq(bnad, ccb->rcb[0]);
|
|
if (ccb->rcb[1])
|
|
bnad_refill_rxq(bnad, ccb->rcb[1]);
|
|
} else
|
|
bna_ib_ack(ccb->i_dbell, 0);
|
|
|
|
return packets;
|
|
}
|
|
|
|
static void
|
|
bnad_disable_rx_irq(struct bnad *bnad, struct bna_ccb *ccb)
|
|
{
|
|
bna_ib_coalescing_timer_set(ccb->i_dbell, 0);
|
|
bna_ib_ack(ccb->i_dbell, 0);
|
|
}
|
|
|
|
static void
|
|
bnad_enable_rx_irq(struct bnad *bnad, struct bna_ccb *ccb)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags); /* Because of polling context */
|
|
bnad_enable_rx_irq_unsafe(ccb);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
static void
|
|
bnad_netif_rx_schedule_poll(struct bnad *bnad, struct bna_ccb *ccb)
|
|
{
|
|
struct bnad_rx_ctrl *rx_ctrl = (struct bnad_rx_ctrl *)(ccb->ctrl);
|
|
if (likely(napi_schedule_prep((&rx_ctrl->napi)))) {
|
|
bnad_disable_rx_irq(bnad, ccb);
|
|
__napi_schedule((&rx_ctrl->napi));
|
|
}
|
|
BNAD_UPDATE_CTR(bnad, netif_rx_schedule);
|
|
}
|
|
|
|
/* MSIX Rx Path Handler */
|
|
static irqreturn_t
|
|
bnad_msix_rx(int irq, void *data)
|
|
{
|
|
struct bna_ccb *ccb = (struct bna_ccb *)data;
|
|
struct bnad *bnad = ccb->bnad;
|
|
|
|
bnad_netif_rx_schedule_poll(bnad, ccb);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* Interrupt handlers */
|
|
|
|
/* Mbox Interrupt Handlers */
|
|
static irqreturn_t
|
|
bnad_msix_mbox_handler(int irq, void *data)
|
|
{
|
|
u32 intr_status;
|
|
unsigned long flags;
|
|
struct net_device *netdev = data;
|
|
struct bnad *bnad;
|
|
|
|
bnad = netdev_priv(netdev);
|
|
|
|
/* BNA_ISR_GET(bnad); Inc Ref count */
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
|
|
bna_intr_status_get(&bnad->bna, intr_status);
|
|
|
|
if (BNA_IS_MBOX_ERR_INTR(intr_status))
|
|
bna_mbox_handler(&bnad->bna, intr_status);
|
|
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
/* BNAD_ISR_PUT(bnad); Dec Ref count */
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t
|
|
bnad_isr(int irq, void *data)
|
|
{
|
|
int i, j;
|
|
u32 intr_status;
|
|
unsigned long flags;
|
|
struct net_device *netdev = data;
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
struct bnad_rx_info *rx_info;
|
|
struct bnad_rx_ctrl *rx_ctrl;
|
|
|
|
if (unlikely(test_bit(BNAD_RF_MBOX_IRQ_DISABLED, &bnad->run_flags)))
|
|
return IRQ_NONE;
|
|
|
|
bna_intr_status_get(&bnad->bna, intr_status);
|
|
|
|
if (unlikely(!intr_status))
|
|
return IRQ_NONE;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
|
|
if (BNA_IS_MBOX_ERR_INTR(intr_status)) {
|
|
bna_mbox_handler(&bnad->bna, intr_status);
|
|
if (!BNA_IS_INTX_DATA_INTR(intr_status)) {
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
goto done;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
/* Process data interrupts */
|
|
for (i = 0; i < bnad->num_rx; i++) {
|
|
rx_info = &bnad->rx_info[i];
|
|
if (!rx_info->rx)
|
|
continue;
|
|
for (j = 0; j < bnad->num_rxp_per_rx; j++) {
|
|
rx_ctrl = &rx_info->rx_ctrl[j];
|
|
if (rx_ctrl->ccb)
|
|
bnad_netif_rx_schedule_poll(bnad,
|
|
rx_ctrl->ccb);
|
|
}
|
|
}
|
|
done:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* Called in interrupt / callback context
|
|
* with bna_lock held, so cfg_flags access is OK
|
|
*/
|
|
static void
|
|
bnad_enable_mbox_irq(struct bnad *bnad)
|
|
{
|
|
int irq = BNAD_GET_MBOX_IRQ(bnad);
|
|
|
|
if (test_and_clear_bit(BNAD_RF_MBOX_IRQ_DISABLED, &bnad->run_flags))
|
|
if (bnad->cfg_flags & BNAD_CF_MSIX)
|
|
enable_irq(irq);
|
|
|
|
BNAD_UPDATE_CTR(bnad, mbox_intr_enabled);
|
|
}
|
|
|
|
/*
|
|
* Called with bnad->bna_lock held b'cos of
|
|
* bnad->cfg_flags access.
|
|
*/
|
|
static void
|
|
bnad_disable_mbox_irq(struct bnad *bnad)
|
|
{
|
|
int irq = BNAD_GET_MBOX_IRQ(bnad);
|
|
|
|
|
|
if (!test_and_set_bit(BNAD_RF_MBOX_IRQ_DISABLED, &bnad->run_flags))
|
|
if (bnad->cfg_flags & BNAD_CF_MSIX)
|
|
disable_irq_nosync(irq);
|
|
|
|
BNAD_UPDATE_CTR(bnad, mbox_intr_disabled);
|
|
}
|
|
|
|
/* Control Path Handlers */
|
|
|
|
/* Callbacks */
|
|
void
|
|
bnad_cb_device_enable_mbox_intr(struct bnad *bnad)
|
|
{
|
|
bnad_enable_mbox_irq(bnad);
|
|
}
|
|
|
|
void
|
|
bnad_cb_device_disable_mbox_intr(struct bnad *bnad)
|
|
{
|
|
bnad_disable_mbox_irq(bnad);
|
|
}
|
|
|
|
void
|
|
bnad_cb_device_enabled(struct bnad *bnad, enum bna_cb_status status)
|
|
{
|
|
complete(&bnad->bnad_completions.ioc_comp);
|
|
bnad->bnad_completions.ioc_comp_status = status;
|
|
}
|
|
|
|
void
|
|
bnad_cb_device_disabled(struct bnad *bnad, enum bna_cb_status status)
|
|
{
|
|
complete(&bnad->bnad_completions.ioc_comp);
|
|
bnad->bnad_completions.ioc_comp_status = status;
|
|
}
|
|
|
|
static void
|
|
bnad_cb_port_disabled(void *arg, enum bna_cb_status status)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)arg;
|
|
|
|
complete(&bnad->bnad_completions.port_comp);
|
|
|
|
netif_carrier_off(bnad->netdev);
|
|
}
|
|
|
|
void
|
|
bnad_cb_port_link_status(struct bnad *bnad,
|
|
enum bna_link_status link_status)
|
|
{
|
|
bool link_up = 0;
|
|
|
|
link_up = (link_status == BNA_LINK_UP) || (link_status == BNA_CEE_UP);
|
|
|
|
if (link_status == BNA_CEE_UP) {
|
|
set_bit(BNAD_RF_CEE_RUNNING, &bnad->run_flags);
|
|
BNAD_UPDATE_CTR(bnad, cee_up);
|
|
} else
|
|
clear_bit(BNAD_RF_CEE_RUNNING, &bnad->run_flags);
|
|
|
|
if (link_up) {
|
|
if (!netif_carrier_ok(bnad->netdev)) {
|
|
pr_warn("bna: %s link up\n",
|
|
bnad->netdev->name);
|
|
netif_carrier_on(bnad->netdev);
|
|
BNAD_UPDATE_CTR(bnad, link_toggle);
|
|
if (test_bit(BNAD_RF_TX_STARTED, &bnad->run_flags)) {
|
|
/* Force an immediate Transmit Schedule */
|
|
pr_info("bna: %s TX_STARTED\n",
|
|
bnad->netdev->name);
|
|
netif_wake_queue(bnad->netdev);
|
|
BNAD_UPDATE_CTR(bnad, netif_queue_wakeup);
|
|
} else {
|
|
netif_stop_queue(bnad->netdev);
|
|
BNAD_UPDATE_CTR(bnad, netif_queue_stop);
|
|
}
|
|
}
|
|
} else {
|
|
if (netif_carrier_ok(bnad->netdev)) {
|
|
pr_warn("bna: %s link down\n",
|
|
bnad->netdev->name);
|
|
netif_carrier_off(bnad->netdev);
|
|
BNAD_UPDATE_CTR(bnad, link_toggle);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
bnad_cb_tx_disabled(void *arg, struct bna_tx *tx,
|
|
enum bna_cb_status status)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)arg;
|
|
|
|
complete(&bnad->bnad_completions.tx_comp);
|
|
}
|
|
|
|
static void
|
|
bnad_cb_tcb_setup(struct bnad *bnad, struct bna_tcb *tcb)
|
|
{
|
|
struct bnad_tx_info *tx_info =
|
|
(struct bnad_tx_info *)tcb->txq->tx->priv;
|
|
struct bnad_unmap_q *unmap_q = tcb->unmap_q;
|
|
|
|
tx_info->tcb[tcb->id] = tcb;
|
|
unmap_q->producer_index = 0;
|
|
unmap_q->consumer_index = 0;
|
|
unmap_q->q_depth = BNAD_TX_UNMAPQ_DEPTH;
|
|
}
|
|
|
|
static void
|
|
bnad_cb_tcb_destroy(struct bnad *bnad, struct bna_tcb *tcb)
|
|
{
|
|
struct bnad_tx_info *tx_info =
|
|
(struct bnad_tx_info *)tcb->txq->tx->priv;
|
|
|
|
tx_info->tcb[tcb->id] = NULL;
|
|
}
|
|
|
|
static void
|
|
bnad_cb_rcb_setup(struct bnad *bnad, struct bna_rcb *rcb)
|
|
{
|
|
struct bnad_unmap_q *unmap_q = rcb->unmap_q;
|
|
|
|
unmap_q->producer_index = 0;
|
|
unmap_q->consumer_index = 0;
|
|
unmap_q->q_depth = BNAD_RX_UNMAPQ_DEPTH;
|
|
}
|
|
|
|
static void
|
|
bnad_cb_ccb_setup(struct bnad *bnad, struct bna_ccb *ccb)
|
|
{
|
|
struct bnad_rx_info *rx_info =
|
|
(struct bnad_rx_info *)ccb->cq->rx->priv;
|
|
|
|
rx_info->rx_ctrl[ccb->id].ccb = ccb;
|
|
ccb->ctrl = &rx_info->rx_ctrl[ccb->id];
|
|
}
|
|
|
|
static void
|
|
bnad_cb_ccb_destroy(struct bnad *bnad, struct bna_ccb *ccb)
|
|
{
|
|
struct bnad_rx_info *rx_info =
|
|
(struct bnad_rx_info *)ccb->cq->rx->priv;
|
|
|
|
rx_info->rx_ctrl[ccb->id].ccb = NULL;
|
|
}
|
|
|
|
static void
|
|
bnad_cb_tx_stall(struct bnad *bnad, struct bna_tcb *tcb)
|
|
{
|
|
struct bnad_tx_info *tx_info =
|
|
(struct bnad_tx_info *)tcb->txq->tx->priv;
|
|
|
|
if (tx_info != &bnad->tx_info[0])
|
|
return;
|
|
|
|
clear_bit(BNAD_RF_TX_STARTED, &bnad->run_flags);
|
|
netif_stop_queue(bnad->netdev);
|
|
pr_info("bna: %s TX_STOPPED\n", bnad->netdev->name);
|
|
}
|
|
|
|
static void
|
|
bnad_cb_tx_resume(struct bnad *bnad, struct bna_tcb *tcb)
|
|
{
|
|
if (test_and_set_bit(BNAD_RF_TX_STARTED, &bnad->run_flags))
|
|
return;
|
|
|
|
if (netif_carrier_ok(bnad->netdev)) {
|
|
pr_info("bna: %s TX_STARTED\n", bnad->netdev->name);
|
|
netif_wake_queue(bnad->netdev);
|
|
BNAD_UPDATE_CTR(bnad, netif_queue_wakeup);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bnad_cb_tx_cleanup(struct bnad *bnad, struct bna_tcb *tcb)
|
|
{
|
|
struct bnad_unmap_q *unmap_q;
|
|
|
|
if (!tcb || (!tcb->unmap_q))
|
|
return;
|
|
|
|
unmap_q = tcb->unmap_q;
|
|
if (!unmap_q->unmap_array)
|
|
return;
|
|
|
|
if (test_and_set_bit(BNAD_TXQ_FREE_SENT, &tcb->flags))
|
|
return;
|
|
|
|
bnad_free_all_txbufs(bnad, tcb);
|
|
|
|
unmap_q->producer_index = 0;
|
|
unmap_q->consumer_index = 0;
|
|
|
|
smp_mb__before_clear_bit();
|
|
clear_bit(BNAD_TXQ_FREE_SENT, &tcb->flags);
|
|
}
|
|
|
|
static void
|
|
bnad_cb_rx_cleanup(struct bnad *bnad,
|
|
struct bna_ccb *ccb)
|
|
{
|
|
bnad_cq_cmpl_init(bnad, ccb);
|
|
|
|
bnad_free_rxbufs(bnad, ccb->rcb[0]);
|
|
clear_bit(BNAD_RXQ_STARTED, &ccb->rcb[0]->flags);
|
|
|
|
if (ccb->rcb[1]) {
|
|
bnad_free_rxbufs(bnad, ccb->rcb[1]);
|
|
clear_bit(BNAD_RXQ_STARTED, &ccb->rcb[1]->flags);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bnad_cb_rx_post(struct bnad *bnad, struct bna_rcb *rcb)
|
|
{
|
|
struct bnad_unmap_q *unmap_q = rcb->unmap_q;
|
|
|
|
set_bit(BNAD_RXQ_STARTED, &rcb->flags);
|
|
|
|
/* Now allocate & post buffers for this RCB */
|
|
/* !!Allocation in callback context */
|
|
if (!test_and_set_bit(BNAD_RXQ_REFILL, &rcb->flags)) {
|
|
if (BNA_QE_FREE_CNT(unmap_q, unmap_q->q_depth)
|
|
>> BNAD_RXQ_REFILL_THRESHOLD_SHIFT)
|
|
bnad_alloc_n_post_rxbufs(bnad, rcb);
|
|
smp_mb__before_clear_bit();
|
|
clear_bit(BNAD_RXQ_REFILL, &rcb->flags);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bnad_cb_rx_disabled(void *arg, struct bna_rx *rx,
|
|
enum bna_cb_status status)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)arg;
|
|
|
|
complete(&bnad->bnad_completions.rx_comp);
|
|
}
|
|
|
|
static void
|
|
bnad_cb_rx_mcast_add(struct bnad *bnad, struct bna_rx *rx,
|
|
enum bna_cb_status status)
|
|
{
|
|
bnad->bnad_completions.mcast_comp_status = status;
|
|
complete(&bnad->bnad_completions.mcast_comp);
|
|
}
|
|
|
|
void
|
|
bnad_cb_stats_get(struct bnad *bnad, enum bna_cb_status status,
|
|
struct bna_stats *stats)
|
|
{
|
|
if (status == BNA_CB_SUCCESS)
|
|
BNAD_UPDATE_CTR(bnad, hw_stats_updates);
|
|
|
|
if (!netif_running(bnad->netdev) ||
|
|
!test_bit(BNAD_RF_STATS_TIMER_RUNNING, &bnad->run_flags))
|
|
return;
|
|
|
|
mod_timer(&bnad->stats_timer,
|
|
jiffies + msecs_to_jiffies(BNAD_STATS_TIMER_FREQ));
|
|
}
|
|
|
|
/* Resource allocation, free functions */
|
|
|
|
static void
|
|
bnad_mem_free(struct bnad *bnad,
|
|
struct bna_mem_info *mem_info)
|
|
{
|
|
int i;
|
|
dma_addr_t dma_pa;
|
|
|
|
if (mem_info->mdl == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < mem_info->num; i++) {
|
|
if (mem_info->mdl[i].kva != NULL) {
|
|
if (mem_info->mem_type == BNA_MEM_T_DMA) {
|
|
BNA_GET_DMA_ADDR(&(mem_info->mdl[i].dma),
|
|
dma_pa);
|
|
pci_free_consistent(bnad->pcidev,
|
|
mem_info->mdl[i].len,
|
|
mem_info->mdl[i].kva, dma_pa);
|
|
} else
|
|
kfree(mem_info->mdl[i].kva);
|
|
}
|
|
}
|
|
kfree(mem_info->mdl);
|
|
mem_info->mdl = NULL;
|
|
}
|
|
|
|
static int
|
|
bnad_mem_alloc(struct bnad *bnad,
|
|
struct bna_mem_info *mem_info)
|
|
{
|
|
int i;
|
|
dma_addr_t dma_pa;
|
|
|
|
if ((mem_info->num == 0) || (mem_info->len == 0)) {
|
|
mem_info->mdl = NULL;
|
|
return 0;
|
|
}
|
|
|
|
mem_info->mdl = kcalloc(mem_info->num, sizeof(struct bna_mem_descr),
|
|
GFP_KERNEL);
|
|
if (mem_info->mdl == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (mem_info->mem_type == BNA_MEM_T_DMA) {
|
|
for (i = 0; i < mem_info->num; i++) {
|
|
mem_info->mdl[i].len = mem_info->len;
|
|
mem_info->mdl[i].kva =
|
|
pci_alloc_consistent(bnad->pcidev,
|
|
mem_info->len, &dma_pa);
|
|
|
|
if (mem_info->mdl[i].kva == NULL)
|
|
goto err_return;
|
|
|
|
BNA_SET_DMA_ADDR(dma_pa,
|
|
&(mem_info->mdl[i].dma));
|
|
}
|
|
} else {
|
|
for (i = 0; i < mem_info->num; i++) {
|
|
mem_info->mdl[i].len = mem_info->len;
|
|
mem_info->mdl[i].kva = kzalloc(mem_info->len,
|
|
GFP_KERNEL);
|
|
if (mem_info->mdl[i].kva == NULL)
|
|
goto err_return;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_return:
|
|
bnad_mem_free(bnad, mem_info);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Free IRQ for Mailbox */
|
|
static void
|
|
bnad_mbox_irq_free(struct bnad *bnad,
|
|
struct bna_intr_info *intr_info)
|
|
{
|
|
int irq;
|
|
unsigned long flags;
|
|
|
|
if (intr_info->idl == NULL)
|
|
return;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bnad_disable_mbox_irq(bnad);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
irq = BNAD_GET_MBOX_IRQ(bnad);
|
|
free_irq(irq, bnad->netdev);
|
|
|
|
kfree(intr_info->idl);
|
|
}
|
|
|
|
/*
|
|
* Allocates IRQ for Mailbox, but keep it disabled
|
|
* This will be enabled once we get the mbox enable callback
|
|
* from bna
|
|
*/
|
|
static int
|
|
bnad_mbox_irq_alloc(struct bnad *bnad,
|
|
struct bna_intr_info *intr_info)
|
|
{
|
|
int err;
|
|
unsigned long flags;
|
|
u32 irq;
|
|
irq_handler_t irq_handler;
|
|
|
|
/* Mbox should use only 1 vector */
|
|
|
|
intr_info->idl = kzalloc(sizeof(*(intr_info->idl)), GFP_KERNEL);
|
|
if (!intr_info->idl)
|
|
return -ENOMEM;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
if (bnad->cfg_flags & BNAD_CF_MSIX) {
|
|
irq_handler = (irq_handler_t)bnad_msix_mbox_handler;
|
|
irq = bnad->msix_table[bnad->msix_num - 1].vector;
|
|
flags = 0;
|
|
intr_info->intr_type = BNA_INTR_T_MSIX;
|
|
intr_info->idl[0].vector = bnad->msix_num - 1;
|
|
} else {
|
|
irq_handler = (irq_handler_t)bnad_isr;
|
|
irq = bnad->pcidev->irq;
|
|
flags = IRQF_SHARED;
|
|
intr_info->intr_type = BNA_INTR_T_INTX;
|
|
/* intr_info->idl.vector = 0 ? */
|
|
}
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
sprintf(bnad->mbox_irq_name, "%s", BNAD_NAME);
|
|
|
|
/*
|
|
* Set the Mbox IRQ disable flag, so that the IRQ handler
|
|
* called from request_irq() for SHARED IRQs do not execute
|
|
*/
|
|
set_bit(BNAD_RF_MBOX_IRQ_DISABLED, &bnad->run_flags);
|
|
|
|
err = request_irq(irq, irq_handler, flags,
|
|
bnad->mbox_irq_name, bnad->netdev);
|
|
|
|
if (err) {
|
|
kfree(intr_info->idl);
|
|
intr_info->idl = NULL;
|
|
return err;
|
|
}
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
|
|
if (bnad->cfg_flags & BNAD_CF_MSIX)
|
|
disable_irq_nosync(irq);
|
|
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
bnad_txrx_irq_free(struct bnad *bnad, struct bna_intr_info *intr_info)
|
|
{
|
|
kfree(intr_info->idl);
|
|
intr_info->idl = NULL;
|
|
}
|
|
|
|
/* Allocates Interrupt Descriptor List for MSIX/INT-X vectors */
|
|
static int
|
|
bnad_txrx_irq_alloc(struct bnad *bnad, enum bnad_intr_source src,
|
|
uint txrx_id, struct bna_intr_info *intr_info)
|
|
{
|
|
int i, vector_start = 0;
|
|
u32 cfg_flags;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
cfg_flags = bnad->cfg_flags;
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
if (cfg_flags & BNAD_CF_MSIX) {
|
|
intr_info->intr_type = BNA_INTR_T_MSIX;
|
|
intr_info->idl = kcalloc(intr_info->num,
|
|
sizeof(struct bna_intr_descr),
|
|
GFP_KERNEL);
|
|
if (!intr_info->idl)
|
|
return -ENOMEM;
|
|
|
|
switch (src) {
|
|
case BNAD_INTR_TX:
|
|
vector_start = txrx_id;
|
|
break;
|
|
|
|
case BNAD_INTR_RX:
|
|
vector_start = bnad->num_tx * bnad->num_txq_per_tx +
|
|
txrx_id;
|
|
break;
|
|
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
for (i = 0; i < intr_info->num; i++)
|
|
intr_info->idl[i].vector = vector_start + i;
|
|
} else {
|
|
intr_info->intr_type = BNA_INTR_T_INTX;
|
|
intr_info->num = 1;
|
|
intr_info->idl = kcalloc(intr_info->num,
|
|
sizeof(struct bna_intr_descr),
|
|
GFP_KERNEL);
|
|
if (!intr_info->idl)
|
|
return -ENOMEM;
|
|
|
|
switch (src) {
|
|
case BNAD_INTR_TX:
|
|
intr_info->idl[0].vector = 0x1; /* Bit mask : Tx IB */
|
|
break;
|
|
|
|
case BNAD_INTR_RX:
|
|
intr_info->idl[0].vector = 0x2; /* Bit mask : Rx IB */
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* NOTE: Should be called for MSIX only
|
|
* Unregisters Tx MSIX vector(s) from the kernel
|
|
*/
|
|
static void
|
|
bnad_tx_msix_unregister(struct bnad *bnad, struct bnad_tx_info *tx_info,
|
|
int num_txqs)
|
|
{
|
|
int i;
|
|
int vector_num;
|
|
|
|
for (i = 0; i < num_txqs; i++) {
|
|
if (tx_info->tcb[i] == NULL)
|
|
continue;
|
|
|
|
vector_num = tx_info->tcb[i]->intr_vector;
|
|
free_irq(bnad->msix_table[vector_num].vector, tx_info->tcb[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* NOTE: Should be called for MSIX only
|
|
* Registers Tx MSIX vector(s) and ISR(s), cookie with the kernel
|
|
*/
|
|
static int
|
|
bnad_tx_msix_register(struct bnad *bnad, struct bnad_tx_info *tx_info,
|
|
uint tx_id, int num_txqs)
|
|
{
|
|
int i;
|
|
int err;
|
|
int vector_num;
|
|
|
|
for (i = 0; i < num_txqs; i++) {
|
|
vector_num = tx_info->tcb[i]->intr_vector;
|
|
sprintf(tx_info->tcb[i]->name, "%s TXQ %d", bnad->netdev->name,
|
|
tx_id + tx_info->tcb[i]->id);
|
|
err = request_irq(bnad->msix_table[vector_num].vector,
|
|
(irq_handler_t)bnad_msix_tx, 0,
|
|
tx_info->tcb[i]->name,
|
|
tx_info->tcb[i]);
|
|
if (err)
|
|
goto err_return;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_return:
|
|
if (i > 0)
|
|
bnad_tx_msix_unregister(bnad, tx_info, (i - 1));
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* NOTE: Should be called for MSIX only
|
|
* Unregisters Rx MSIX vector(s) from the kernel
|
|
*/
|
|
static void
|
|
bnad_rx_msix_unregister(struct bnad *bnad, struct bnad_rx_info *rx_info,
|
|
int num_rxps)
|
|
{
|
|
int i;
|
|
int vector_num;
|
|
|
|
for (i = 0; i < num_rxps; i++) {
|
|
if (rx_info->rx_ctrl[i].ccb == NULL)
|
|
continue;
|
|
|
|
vector_num = rx_info->rx_ctrl[i].ccb->intr_vector;
|
|
free_irq(bnad->msix_table[vector_num].vector,
|
|
rx_info->rx_ctrl[i].ccb);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* NOTE: Should be called for MSIX only
|
|
* Registers Tx MSIX vector(s) and ISR(s), cookie with the kernel
|
|
*/
|
|
static int
|
|
bnad_rx_msix_register(struct bnad *bnad, struct bnad_rx_info *rx_info,
|
|
uint rx_id, int num_rxps)
|
|
{
|
|
int i;
|
|
int err;
|
|
int vector_num;
|
|
|
|
for (i = 0; i < num_rxps; i++) {
|
|
vector_num = rx_info->rx_ctrl[i].ccb->intr_vector;
|
|
sprintf(rx_info->rx_ctrl[i].ccb->name, "%s CQ %d",
|
|
bnad->netdev->name,
|
|
rx_id + rx_info->rx_ctrl[i].ccb->id);
|
|
err = request_irq(bnad->msix_table[vector_num].vector,
|
|
(irq_handler_t)bnad_msix_rx, 0,
|
|
rx_info->rx_ctrl[i].ccb->name,
|
|
rx_info->rx_ctrl[i].ccb);
|
|
if (err)
|
|
goto err_return;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_return:
|
|
if (i > 0)
|
|
bnad_rx_msix_unregister(bnad, rx_info, (i - 1));
|
|
return -1;
|
|
}
|
|
|
|
/* Free Tx object Resources */
|
|
static void
|
|
bnad_tx_res_free(struct bnad *bnad, struct bna_res_info *res_info)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < BNA_TX_RES_T_MAX; i++) {
|
|
if (res_info[i].res_type == BNA_RES_T_MEM)
|
|
bnad_mem_free(bnad, &res_info[i].res_u.mem_info);
|
|
else if (res_info[i].res_type == BNA_RES_T_INTR)
|
|
bnad_txrx_irq_free(bnad, &res_info[i].res_u.intr_info);
|
|
}
|
|
}
|
|
|
|
/* Allocates memory and interrupt resources for Tx object */
|
|
static int
|
|
bnad_tx_res_alloc(struct bnad *bnad, struct bna_res_info *res_info,
|
|
uint tx_id)
|
|
{
|
|
int i, err = 0;
|
|
|
|
for (i = 0; i < BNA_TX_RES_T_MAX; i++) {
|
|
if (res_info[i].res_type == BNA_RES_T_MEM)
|
|
err = bnad_mem_alloc(bnad,
|
|
&res_info[i].res_u.mem_info);
|
|
else if (res_info[i].res_type == BNA_RES_T_INTR)
|
|
err = bnad_txrx_irq_alloc(bnad, BNAD_INTR_TX, tx_id,
|
|
&res_info[i].res_u.intr_info);
|
|
if (err)
|
|
goto err_return;
|
|
}
|
|
return 0;
|
|
|
|
err_return:
|
|
bnad_tx_res_free(bnad, res_info);
|
|
return err;
|
|
}
|
|
|
|
/* Free Rx object Resources */
|
|
static void
|
|
bnad_rx_res_free(struct bnad *bnad, struct bna_res_info *res_info)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < BNA_RX_RES_T_MAX; i++) {
|
|
if (res_info[i].res_type == BNA_RES_T_MEM)
|
|
bnad_mem_free(bnad, &res_info[i].res_u.mem_info);
|
|
else if (res_info[i].res_type == BNA_RES_T_INTR)
|
|
bnad_txrx_irq_free(bnad, &res_info[i].res_u.intr_info);
|
|
}
|
|
}
|
|
|
|
/* Allocates memory and interrupt resources for Rx object */
|
|
static int
|
|
bnad_rx_res_alloc(struct bnad *bnad, struct bna_res_info *res_info,
|
|
uint rx_id)
|
|
{
|
|
int i, err = 0;
|
|
|
|
/* All memory needs to be allocated before setup_ccbs */
|
|
for (i = 0; i < BNA_RX_RES_T_MAX; i++) {
|
|
if (res_info[i].res_type == BNA_RES_T_MEM)
|
|
err = bnad_mem_alloc(bnad,
|
|
&res_info[i].res_u.mem_info);
|
|
else if (res_info[i].res_type == BNA_RES_T_INTR)
|
|
err = bnad_txrx_irq_alloc(bnad, BNAD_INTR_RX, rx_id,
|
|
&res_info[i].res_u.intr_info);
|
|
if (err)
|
|
goto err_return;
|
|
}
|
|
return 0;
|
|
|
|
err_return:
|
|
bnad_rx_res_free(bnad, res_info);
|
|
return err;
|
|
}
|
|
|
|
/* Timer callbacks */
|
|
/* a) IOC timer */
|
|
static void
|
|
bnad_ioc_timeout(unsigned long data)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)data;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bfa_nw_ioc_timeout((void *) &bnad->bna.device.ioc);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
static void
|
|
bnad_ioc_hb_check(unsigned long data)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)data;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bfa_nw_ioc_hb_check((void *) &bnad->bna.device.ioc);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
static void
|
|
bnad_ioc_sem_timeout(unsigned long data)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)data;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bfa_nw_ioc_sem_timeout((void *) &bnad->bna.device.ioc);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* All timer routines use bnad->bna_lock to protect against
|
|
* the following race, which may occur in case of no locking:
|
|
* Time CPU m CPU n
|
|
* 0 1 = test_bit
|
|
* 1 clear_bit
|
|
* 2 del_timer_sync
|
|
* 3 mod_timer
|
|
*/
|
|
|
|
/* b) Dynamic Interrupt Moderation Timer */
|
|
static void
|
|
bnad_dim_timeout(unsigned long data)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)data;
|
|
struct bnad_rx_info *rx_info;
|
|
struct bnad_rx_ctrl *rx_ctrl;
|
|
int i, j;
|
|
unsigned long flags;
|
|
|
|
if (!netif_carrier_ok(bnad->netdev))
|
|
return;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
for (i = 0; i < bnad->num_rx; i++) {
|
|
rx_info = &bnad->rx_info[i];
|
|
if (!rx_info->rx)
|
|
continue;
|
|
for (j = 0; j < bnad->num_rxp_per_rx; j++) {
|
|
rx_ctrl = &rx_info->rx_ctrl[j];
|
|
if (!rx_ctrl->ccb)
|
|
continue;
|
|
bna_rx_dim_update(rx_ctrl->ccb);
|
|
}
|
|
}
|
|
|
|
/* Check for BNAD_CF_DIM_ENABLED, does not eleminate a race */
|
|
if (test_bit(BNAD_RF_DIM_TIMER_RUNNING, &bnad->run_flags))
|
|
mod_timer(&bnad->dim_timer,
|
|
jiffies + msecs_to_jiffies(BNAD_DIM_TIMER_FREQ));
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
/* c) Statistics Timer */
|
|
static void
|
|
bnad_stats_timeout(unsigned long data)
|
|
{
|
|
struct bnad *bnad = (struct bnad *)data;
|
|
unsigned long flags;
|
|
|
|
if (!netif_running(bnad->netdev) ||
|
|
!test_bit(BNAD_RF_STATS_TIMER_RUNNING, &bnad->run_flags))
|
|
return;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_stats_get(&bnad->bna);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* Set up timer for DIM
|
|
* Called with bnad->bna_lock held
|
|
*/
|
|
void
|
|
bnad_dim_timer_start(struct bnad *bnad)
|
|
{
|
|
if (bnad->cfg_flags & BNAD_CF_DIM_ENABLED &&
|
|
!test_bit(BNAD_RF_DIM_TIMER_RUNNING, &bnad->run_flags)) {
|
|
setup_timer(&bnad->dim_timer, bnad_dim_timeout,
|
|
(unsigned long)bnad);
|
|
set_bit(BNAD_RF_DIM_TIMER_RUNNING, &bnad->run_flags);
|
|
mod_timer(&bnad->dim_timer,
|
|
jiffies + msecs_to_jiffies(BNAD_DIM_TIMER_FREQ));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set up timer for statistics
|
|
* Called with mutex_lock(&bnad->conf_mutex) held
|
|
*/
|
|
static void
|
|
bnad_stats_timer_start(struct bnad *bnad)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
if (!test_and_set_bit(BNAD_RF_STATS_TIMER_RUNNING, &bnad->run_flags)) {
|
|
setup_timer(&bnad->stats_timer, bnad_stats_timeout,
|
|
(unsigned long)bnad);
|
|
mod_timer(&bnad->stats_timer,
|
|
jiffies + msecs_to_jiffies(BNAD_STATS_TIMER_FREQ));
|
|
}
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* Stops the stats timer
|
|
* Called with mutex_lock(&bnad->conf_mutex) held
|
|
*/
|
|
static void
|
|
bnad_stats_timer_stop(struct bnad *bnad)
|
|
{
|
|
int to_del = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
if (test_and_clear_bit(BNAD_RF_STATS_TIMER_RUNNING, &bnad->run_flags))
|
|
to_del = 1;
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
if (to_del)
|
|
del_timer_sync(&bnad->stats_timer);
|
|
}
|
|
|
|
/* Utilities */
|
|
|
|
static void
|
|
bnad_netdev_mc_list_get(struct net_device *netdev, u8 *mc_list)
|
|
{
|
|
int i = 1; /* Index 0 has broadcast address */
|
|
struct netdev_hw_addr *mc_addr;
|
|
|
|
netdev_for_each_mc_addr(mc_addr, netdev) {
|
|
memcpy(&mc_list[i * ETH_ALEN], &mc_addr->addr[0],
|
|
ETH_ALEN);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static int
|
|
bnad_napi_poll_rx(struct napi_struct *napi, int budget)
|
|
{
|
|
struct bnad_rx_ctrl *rx_ctrl =
|
|
container_of(napi, struct bnad_rx_ctrl, napi);
|
|
struct bna_ccb *ccb;
|
|
struct bnad *bnad;
|
|
int rcvd = 0;
|
|
|
|
ccb = rx_ctrl->ccb;
|
|
|
|
bnad = ccb->bnad;
|
|
|
|
if (!netif_carrier_ok(bnad->netdev))
|
|
goto poll_exit;
|
|
|
|
rcvd = bnad_poll_cq(bnad, ccb, budget);
|
|
if (rcvd == budget)
|
|
return rcvd;
|
|
|
|
poll_exit:
|
|
napi_complete((napi));
|
|
|
|
BNAD_UPDATE_CTR(bnad, netif_rx_complete);
|
|
|
|
bnad_enable_rx_irq(bnad, ccb);
|
|
return rcvd;
|
|
}
|
|
|
|
static int
|
|
bnad_napi_poll_txrx(struct napi_struct *napi, int budget)
|
|
{
|
|
struct bnad_rx_ctrl *rx_ctrl =
|
|
container_of(napi, struct bnad_rx_ctrl, napi);
|
|
struct bna_ccb *ccb;
|
|
struct bnad *bnad;
|
|
int rcvd = 0;
|
|
int i, j;
|
|
|
|
ccb = rx_ctrl->ccb;
|
|
|
|
bnad = ccb->bnad;
|
|
|
|
if (!netif_carrier_ok(bnad->netdev))
|
|
goto poll_exit;
|
|
|
|
/* Handle Tx Completions, if any */
|
|
for (i = 0; i < bnad->num_tx; i++) {
|
|
for (j = 0; j < bnad->num_txq_per_tx; j++)
|
|
bnad_tx(bnad, bnad->tx_info[i].tcb[j]);
|
|
}
|
|
|
|
/* Handle Rx Completions */
|
|
rcvd = bnad_poll_cq(bnad, ccb, budget);
|
|
if (rcvd == budget)
|
|
return rcvd;
|
|
poll_exit:
|
|
napi_complete((napi));
|
|
|
|
BNAD_UPDATE_CTR(bnad, netif_rx_complete);
|
|
|
|
bnad_enable_txrx_irqs(bnad);
|
|
return rcvd;
|
|
}
|
|
|
|
static void
|
|
bnad_napi_enable(struct bnad *bnad, u32 rx_id)
|
|
{
|
|
int (*napi_poll) (struct napi_struct *, int);
|
|
struct bnad_rx_ctrl *rx_ctrl;
|
|
int i;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
if (bnad->cfg_flags & BNAD_CF_MSIX)
|
|
napi_poll = bnad_napi_poll_rx;
|
|
else
|
|
napi_poll = bnad_napi_poll_txrx;
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
/* Initialize & enable NAPI */
|
|
for (i = 0; i < bnad->num_rxp_per_rx; i++) {
|
|
rx_ctrl = &bnad->rx_info[rx_id].rx_ctrl[i];
|
|
netif_napi_add(bnad->netdev, &rx_ctrl->napi,
|
|
napi_poll, 64);
|
|
napi_enable(&rx_ctrl->napi);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bnad_napi_disable(struct bnad *bnad, u32 rx_id)
|
|
{
|
|
int i;
|
|
|
|
/* First disable and then clean up */
|
|
for (i = 0; i < bnad->num_rxp_per_rx; i++) {
|
|
napi_disable(&bnad->rx_info[rx_id].rx_ctrl[i].napi);
|
|
netif_napi_del(&bnad->rx_info[rx_id].rx_ctrl[i].napi);
|
|
}
|
|
}
|
|
|
|
/* Should be held with conf_lock held */
|
|
void
|
|
bnad_cleanup_tx(struct bnad *bnad, uint tx_id)
|
|
{
|
|
struct bnad_tx_info *tx_info = &bnad->tx_info[tx_id];
|
|
struct bna_res_info *res_info = &bnad->tx_res_info[tx_id].res_info[0];
|
|
unsigned long flags;
|
|
|
|
if (!tx_info->tx)
|
|
return;
|
|
|
|
init_completion(&bnad->bnad_completions.tx_comp);
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_tx_disable(tx_info->tx, BNA_HARD_CLEANUP, bnad_cb_tx_disabled);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
wait_for_completion(&bnad->bnad_completions.tx_comp);
|
|
|
|
if (tx_info->tcb[0]->intr_type == BNA_INTR_T_MSIX)
|
|
bnad_tx_msix_unregister(bnad, tx_info,
|
|
bnad->num_txq_per_tx);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_tx_destroy(tx_info->tx);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
tx_info->tx = NULL;
|
|
|
|
if (0 == tx_id)
|
|
tasklet_kill(&bnad->tx_free_tasklet);
|
|
|
|
bnad_tx_res_free(bnad, res_info);
|
|
}
|
|
|
|
/* Should be held with conf_lock held */
|
|
int
|
|
bnad_setup_tx(struct bnad *bnad, uint tx_id)
|
|
{
|
|
int err;
|
|
struct bnad_tx_info *tx_info = &bnad->tx_info[tx_id];
|
|
struct bna_res_info *res_info = &bnad->tx_res_info[tx_id].res_info[0];
|
|
struct bna_intr_info *intr_info =
|
|
&res_info[BNA_TX_RES_INTR_T_TXCMPL].res_u.intr_info;
|
|
struct bna_tx_config *tx_config = &bnad->tx_config[tx_id];
|
|
struct bna_tx_event_cbfn tx_cbfn;
|
|
struct bna_tx *tx;
|
|
unsigned long flags;
|
|
|
|
/* Initialize the Tx object configuration */
|
|
tx_config->num_txq = bnad->num_txq_per_tx;
|
|
tx_config->txq_depth = bnad->txq_depth;
|
|
tx_config->tx_type = BNA_TX_T_REGULAR;
|
|
|
|
/* Initialize the tx event handlers */
|
|
tx_cbfn.tcb_setup_cbfn = bnad_cb_tcb_setup;
|
|
tx_cbfn.tcb_destroy_cbfn = bnad_cb_tcb_destroy;
|
|
tx_cbfn.tx_stall_cbfn = bnad_cb_tx_stall;
|
|
tx_cbfn.tx_resume_cbfn = bnad_cb_tx_resume;
|
|
tx_cbfn.tx_cleanup_cbfn = bnad_cb_tx_cleanup;
|
|
|
|
/* Get BNA's resource requirement for one tx object */
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_tx_res_req(bnad->num_txq_per_tx,
|
|
bnad->txq_depth, res_info);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
/* Fill Unmap Q memory requirements */
|
|
BNAD_FILL_UNMAPQ_MEM_REQ(
|
|
&res_info[BNA_TX_RES_MEM_T_UNMAPQ],
|
|
bnad->num_txq_per_tx,
|
|
BNAD_TX_UNMAPQ_DEPTH);
|
|
|
|
/* Allocate resources */
|
|
err = bnad_tx_res_alloc(bnad, res_info, tx_id);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Ask BNA to create one Tx object, supplying required resources */
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
tx = bna_tx_create(&bnad->bna, bnad, tx_config, &tx_cbfn, res_info,
|
|
tx_info);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
if (!tx)
|
|
goto err_return;
|
|
tx_info->tx = tx;
|
|
|
|
/* Register ISR for the Tx object */
|
|
if (intr_info->intr_type == BNA_INTR_T_MSIX) {
|
|
err = bnad_tx_msix_register(bnad, tx_info,
|
|
tx_id, bnad->num_txq_per_tx);
|
|
if (err)
|
|
goto err_return;
|
|
}
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_tx_enable(tx);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
return 0;
|
|
|
|
err_return:
|
|
bnad_tx_res_free(bnad, res_info);
|
|
return err;
|
|
}
|
|
|
|
/* Setup the rx config for bna_rx_create */
|
|
/* bnad decides the configuration */
|
|
static void
|
|
bnad_init_rx_config(struct bnad *bnad, struct bna_rx_config *rx_config)
|
|
{
|
|
rx_config->rx_type = BNA_RX_T_REGULAR;
|
|
rx_config->num_paths = bnad->num_rxp_per_rx;
|
|
|
|
if (bnad->num_rxp_per_rx > 1) {
|
|
rx_config->rss_status = BNA_STATUS_T_ENABLED;
|
|
rx_config->rss_config.hash_type =
|
|
(BFI_RSS_T_V4_TCP |
|
|
BFI_RSS_T_V6_TCP |
|
|
BFI_RSS_T_V4_IP |
|
|
BFI_RSS_T_V6_IP);
|
|
rx_config->rss_config.hash_mask =
|
|
bnad->num_rxp_per_rx - 1;
|
|
get_random_bytes(rx_config->rss_config.toeplitz_hash_key,
|
|
sizeof(rx_config->rss_config.toeplitz_hash_key));
|
|
} else {
|
|
rx_config->rss_status = BNA_STATUS_T_DISABLED;
|
|
memset(&rx_config->rss_config, 0,
|
|
sizeof(rx_config->rss_config));
|
|
}
|
|
rx_config->rxp_type = BNA_RXP_SLR;
|
|
rx_config->q_depth = bnad->rxq_depth;
|
|
|
|
rx_config->small_buff_size = BFI_SMALL_RXBUF_SIZE;
|
|
|
|
rx_config->vlan_strip_status = BNA_STATUS_T_ENABLED;
|
|
}
|
|
|
|
/* Called with mutex_lock(&bnad->conf_mutex) held */
|
|
void
|
|
bnad_cleanup_rx(struct bnad *bnad, uint rx_id)
|
|
{
|
|
struct bnad_rx_info *rx_info = &bnad->rx_info[rx_id];
|
|
struct bna_rx_config *rx_config = &bnad->rx_config[rx_id];
|
|
struct bna_res_info *res_info = &bnad->rx_res_info[rx_id].res_info[0];
|
|
unsigned long flags;
|
|
int dim_timer_del = 0;
|
|
|
|
if (!rx_info->rx)
|
|
return;
|
|
|
|
if (0 == rx_id) {
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
dim_timer_del = bnad_dim_timer_running(bnad);
|
|
if (dim_timer_del)
|
|
clear_bit(BNAD_RF_DIM_TIMER_RUNNING, &bnad->run_flags);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
if (dim_timer_del)
|
|
del_timer_sync(&bnad->dim_timer);
|
|
}
|
|
|
|
bnad_napi_disable(bnad, rx_id);
|
|
|
|
init_completion(&bnad->bnad_completions.rx_comp);
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_rx_disable(rx_info->rx, BNA_HARD_CLEANUP, bnad_cb_rx_disabled);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
wait_for_completion(&bnad->bnad_completions.rx_comp);
|
|
|
|
if (rx_info->rx_ctrl[0].ccb->intr_type == BNA_INTR_T_MSIX)
|
|
bnad_rx_msix_unregister(bnad, rx_info, rx_config->num_paths);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_rx_destroy(rx_info->rx);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
rx_info->rx = NULL;
|
|
|
|
bnad_rx_res_free(bnad, res_info);
|
|
}
|
|
|
|
/* Called with mutex_lock(&bnad->conf_mutex) held */
|
|
int
|
|
bnad_setup_rx(struct bnad *bnad, uint rx_id)
|
|
{
|
|
int err;
|
|
struct bnad_rx_info *rx_info = &bnad->rx_info[rx_id];
|
|
struct bna_res_info *res_info = &bnad->rx_res_info[rx_id].res_info[0];
|
|
struct bna_intr_info *intr_info =
|
|
&res_info[BNA_RX_RES_T_INTR].res_u.intr_info;
|
|
struct bna_rx_config *rx_config = &bnad->rx_config[rx_id];
|
|
struct bna_rx_event_cbfn rx_cbfn;
|
|
struct bna_rx *rx;
|
|
unsigned long flags;
|
|
|
|
/* Initialize the Rx object configuration */
|
|
bnad_init_rx_config(bnad, rx_config);
|
|
|
|
/* Initialize the Rx event handlers */
|
|
rx_cbfn.rcb_setup_cbfn = bnad_cb_rcb_setup;
|
|
rx_cbfn.rcb_destroy_cbfn = NULL;
|
|
rx_cbfn.ccb_setup_cbfn = bnad_cb_ccb_setup;
|
|
rx_cbfn.ccb_destroy_cbfn = bnad_cb_ccb_destroy;
|
|
rx_cbfn.rx_cleanup_cbfn = bnad_cb_rx_cleanup;
|
|
rx_cbfn.rx_post_cbfn = bnad_cb_rx_post;
|
|
|
|
/* Get BNA's resource requirement for one Rx object */
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_rx_res_req(rx_config, res_info);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
/* Fill Unmap Q memory requirements */
|
|
BNAD_FILL_UNMAPQ_MEM_REQ(
|
|
&res_info[BNA_RX_RES_MEM_T_UNMAPQ],
|
|
rx_config->num_paths +
|
|
((rx_config->rxp_type == BNA_RXP_SINGLE) ? 0 :
|
|
rx_config->num_paths), BNAD_RX_UNMAPQ_DEPTH);
|
|
|
|
/* Allocate resource */
|
|
err = bnad_rx_res_alloc(bnad, res_info, rx_id);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Ask BNA to create one Rx object, supplying required resources */
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
rx = bna_rx_create(&bnad->bna, bnad, rx_config, &rx_cbfn, res_info,
|
|
rx_info);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
if (!rx)
|
|
goto err_return;
|
|
rx_info->rx = rx;
|
|
|
|
/* Register ISR for the Rx object */
|
|
if (intr_info->intr_type == BNA_INTR_T_MSIX) {
|
|
err = bnad_rx_msix_register(bnad, rx_info, rx_id,
|
|
rx_config->num_paths);
|
|
if (err)
|
|
goto err_return;
|
|
}
|
|
|
|
/* Enable NAPI */
|
|
bnad_napi_enable(bnad, rx_id);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
if (0 == rx_id) {
|
|
/* Set up Dynamic Interrupt Moderation Vector */
|
|
if (bnad->cfg_flags & BNAD_CF_DIM_ENABLED)
|
|
bna_rx_dim_reconfig(&bnad->bna, bna_napi_dim_vector);
|
|
|
|
/* Enable VLAN filtering only on the default Rx */
|
|
bna_rx_vlanfilter_enable(rx);
|
|
|
|
/* Start the DIM timer */
|
|
bnad_dim_timer_start(bnad);
|
|
}
|
|
|
|
bna_rx_enable(rx);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
return 0;
|
|
|
|
err_return:
|
|
bnad_cleanup_rx(bnad, rx_id);
|
|
return err;
|
|
}
|
|
|
|
/* Called with conf_lock & bnad->bna_lock held */
|
|
void
|
|
bnad_tx_coalescing_timeo_set(struct bnad *bnad)
|
|
{
|
|
struct bnad_tx_info *tx_info;
|
|
|
|
tx_info = &bnad->tx_info[0];
|
|
if (!tx_info->tx)
|
|
return;
|
|
|
|
bna_tx_coalescing_timeo_set(tx_info->tx, bnad->tx_coalescing_timeo);
|
|
}
|
|
|
|
/* Called with conf_lock & bnad->bna_lock held */
|
|
void
|
|
bnad_rx_coalescing_timeo_set(struct bnad *bnad)
|
|
{
|
|
struct bnad_rx_info *rx_info;
|
|
int i;
|
|
|
|
for (i = 0; i < bnad->num_rx; i++) {
|
|
rx_info = &bnad->rx_info[i];
|
|
if (!rx_info->rx)
|
|
continue;
|
|
bna_rx_coalescing_timeo_set(rx_info->rx,
|
|
bnad->rx_coalescing_timeo);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called with bnad->bna_lock held
|
|
*/
|
|
static int
|
|
bnad_mac_addr_set_locked(struct bnad *bnad, u8 *mac_addr)
|
|
{
|
|
int ret;
|
|
|
|
if (!is_valid_ether_addr(mac_addr))
|
|
return -EADDRNOTAVAIL;
|
|
|
|
/* If datapath is down, pretend everything went through */
|
|
if (!bnad->rx_info[0].rx)
|
|
return 0;
|
|
|
|
ret = bna_rx_ucast_set(bnad->rx_info[0].rx, mac_addr, NULL);
|
|
if (ret != BNA_CB_SUCCESS)
|
|
return -EADDRNOTAVAIL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Should be called with conf_lock held */
|
|
static int
|
|
bnad_enable_default_bcast(struct bnad *bnad)
|
|
{
|
|
struct bnad_rx_info *rx_info = &bnad->rx_info[0];
|
|
int ret;
|
|
unsigned long flags;
|
|
|
|
init_completion(&bnad->bnad_completions.mcast_comp);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
ret = bna_rx_mcast_add(rx_info->rx, (u8 *)bnad_bcast_addr,
|
|
bnad_cb_rx_mcast_add);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
if (ret == BNA_CB_SUCCESS)
|
|
wait_for_completion(&bnad->bnad_completions.mcast_comp);
|
|
else
|
|
return -ENODEV;
|
|
|
|
if (bnad->bnad_completions.mcast_comp_status != BNA_CB_SUCCESS)
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Statistics utilities */
|
|
void
|
|
bnad_netdev_qstats_fill(struct bnad *bnad, struct rtnl_link_stats64 *stats)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < bnad->num_rx; i++) {
|
|
for (j = 0; j < bnad->num_rxp_per_rx; j++) {
|
|
if (bnad->rx_info[i].rx_ctrl[j].ccb) {
|
|
stats->rx_packets += bnad->rx_info[i].
|
|
rx_ctrl[j].ccb->rcb[0]->rxq->rx_packets;
|
|
stats->rx_bytes += bnad->rx_info[i].
|
|
rx_ctrl[j].ccb->rcb[0]->rxq->rx_bytes;
|
|
if (bnad->rx_info[i].rx_ctrl[j].ccb->rcb[1] &&
|
|
bnad->rx_info[i].rx_ctrl[j].ccb->
|
|
rcb[1]->rxq) {
|
|
stats->rx_packets +=
|
|
bnad->rx_info[i].rx_ctrl[j].
|
|
ccb->rcb[1]->rxq->rx_packets;
|
|
stats->rx_bytes +=
|
|
bnad->rx_info[i].rx_ctrl[j].
|
|
ccb->rcb[1]->rxq->rx_bytes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (i = 0; i < bnad->num_tx; i++) {
|
|
for (j = 0; j < bnad->num_txq_per_tx; j++) {
|
|
if (bnad->tx_info[i].tcb[j]) {
|
|
stats->tx_packets +=
|
|
bnad->tx_info[i].tcb[j]->txq->tx_packets;
|
|
stats->tx_bytes +=
|
|
bnad->tx_info[i].tcb[j]->txq->tx_bytes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Must be called with the bna_lock held.
|
|
*/
|
|
void
|
|
bnad_netdev_hwstats_fill(struct bnad *bnad, struct rtnl_link_stats64 *stats)
|
|
{
|
|
struct bfi_ll_stats_mac *mac_stats;
|
|
u64 bmap;
|
|
int i;
|
|
|
|
mac_stats = &bnad->stats.bna_stats->hw_stats->mac_stats;
|
|
stats->rx_errors =
|
|
mac_stats->rx_fcs_error + mac_stats->rx_alignment_error +
|
|
mac_stats->rx_frame_length_error + mac_stats->rx_code_error +
|
|
mac_stats->rx_undersize;
|
|
stats->tx_errors = mac_stats->tx_fcs_error +
|
|
mac_stats->tx_undersize;
|
|
stats->rx_dropped = mac_stats->rx_drop;
|
|
stats->tx_dropped = mac_stats->tx_drop;
|
|
stats->multicast = mac_stats->rx_multicast;
|
|
stats->collisions = mac_stats->tx_total_collision;
|
|
|
|
stats->rx_length_errors = mac_stats->rx_frame_length_error;
|
|
|
|
/* receive ring buffer overflow ?? */
|
|
|
|
stats->rx_crc_errors = mac_stats->rx_fcs_error;
|
|
stats->rx_frame_errors = mac_stats->rx_alignment_error;
|
|
/* recv'r fifo overrun */
|
|
bmap = (u64)bnad->stats.bna_stats->rxf_bmap[0] |
|
|
((u64)bnad->stats.bna_stats->rxf_bmap[1] << 32);
|
|
for (i = 0; bmap && (i < BFI_LL_RXF_ID_MAX); i++) {
|
|
if (bmap & 1) {
|
|
stats->rx_fifo_errors +=
|
|
bnad->stats.bna_stats->
|
|
hw_stats->rxf_stats[i].frame_drops;
|
|
break;
|
|
}
|
|
bmap >>= 1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
bnad_mbox_irq_sync(struct bnad *bnad)
|
|
{
|
|
u32 irq;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
if (bnad->cfg_flags & BNAD_CF_MSIX)
|
|
irq = bnad->msix_table[bnad->msix_num - 1].vector;
|
|
else
|
|
irq = bnad->pcidev->irq;
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
synchronize_irq(irq);
|
|
}
|
|
|
|
/* Utility used by bnad_start_xmit, for doing TSO */
|
|
static int
|
|
bnad_tso_prepare(struct bnad *bnad, struct sk_buff *skb)
|
|
{
|
|
int err;
|
|
|
|
/* SKB_GSO_TCPV4 and SKB_GSO_TCPV6 is defined since 2.6.18. */
|
|
BUG_ON(!(skb_shinfo(skb)->gso_type == SKB_GSO_TCPV4 ||
|
|
skb_shinfo(skb)->gso_type == SKB_GSO_TCPV6));
|
|
if (skb_header_cloned(skb)) {
|
|
err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC);
|
|
if (err) {
|
|
BNAD_UPDATE_CTR(bnad, tso_err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For TSO, the TCP checksum field is seeded with pseudo-header sum
|
|
* excluding the length field.
|
|
*/
|
|
if (skb->protocol == htons(ETH_P_IP)) {
|
|
struct iphdr *iph = ip_hdr(skb);
|
|
|
|
/* Do we really need these? */
|
|
iph->tot_len = 0;
|
|
iph->check = 0;
|
|
|
|
tcp_hdr(skb)->check =
|
|
~csum_tcpudp_magic(iph->saddr, iph->daddr, 0,
|
|
IPPROTO_TCP, 0);
|
|
BNAD_UPDATE_CTR(bnad, tso4);
|
|
} else {
|
|
struct ipv6hdr *ipv6h = ipv6_hdr(skb);
|
|
|
|
BUG_ON(!(skb->protocol == htons(ETH_P_IPV6)));
|
|
ipv6h->payload_len = 0;
|
|
tcp_hdr(skb)->check =
|
|
~csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, 0,
|
|
IPPROTO_TCP, 0);
|
|
BNAD_UPDATE_CTR(bnad, tso6);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize Q numbers depending on Rx Paths
|
|
* Called with bnad->bna_lock held, because of cfg_flags
|
|
* access.
|
|
*/
|
|
static void
|
|
bnad_q_num_init(struct bnad *bnad)
|
|
{
|
|
int rxps;
|
|
|
|
rxps = min((uint)num_online_cpus(),
|
|
(uint)(BNAD_MAX_RXS * BNAD_MAX_RXPS_PER_RX));
|
|
|
|
if (!(bnad->cfg_flags & BNAD_CF_MSIX))
|
|
rxps = 1; /* INTx */
|
|
|
|
bnad->num_rx = 1;
|
|
bnad->num_tx = 1;
|
|
bnad->num_rxp_per_rx = rxps;
|
|
bnad->num_txq_per_tx = BNAD_TXQ_NUM;
|
|
}
|
|
|
|
/*
|
|
* Adjusts the Q numbers, given a number of msix vectors
|
|
* Give preference to RSS as opposed to Tx priority Queues,
|
|
* in such a case, just use 1 Tx Q
|
|
* Called with bnad->bna_lock held b'cos of cfg_flags access
|
|
*/
|
|
static void
|
|
bnad_q_num_adjust(struct bnad *bnad, int msix_vectors)
|
|
{
|
|
bnad->num_txq_per_tx = 1;
|
|
if ((msix_vectors >= (bnad->num_tx * bnad->num_txq_per_tx) +
|
|
bnad_rxqs_per_cq + BNAD_MAILBOX_MSIX_VECTORS) &&
|
|
(bnad->cfg_flags & BNAD_CF_MSIX)) {
|
|
bnad->num_rxp_per_rx = msix_vectors -
|
|
(bnad->num_tx * bnad->num_txq_per_tx) -
|
|
BNAD_MAILBOX_MSIX_VECTORS;
|
|
} else
|
|
bnad->num_rxp_per_rx = 1;
|
|
}
|
|
|
|
static void
|
|
bnad_set_netdev_perm_addr(struct bnad *bnad)
|
|
{
|
|
struct net_device *netdev = bnad->netdev;
|
|
|
|
memcpy(netdev->perm_addr, &bnad->perm_addr, netdev->addr_len);
|
|
if (is_zero_ether_addr(netdev->dev_addr))
|
|
memcpy(netdev->dev_addr, &bnad->perm_addr, netdev->addr_len);
|
|
}
|
|
|
|
/* Enable / disable device */
|
|
static void
|
|
bnad_device_disable(struct bnad *bnad)
|
|
{
|
|
unsigned long flags;
|
|
|
|
init_completion(&bnad->bnad_completions.ioc_comp);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_device_disable(&bnad->bna.device, BNA_HARD_CLEANUP);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
wait_for_completion(&bnad->bnad_completions.ioc_comp);
|
|
}
|
|
|
|
static int
|
|
bnad_device_enable(struct bnad *bnad)
|
|
{
|
|
int err = 0;
|
|
unsigned long flags;
|
|
|
|
init_completion(&bnad->bnad_completions.ioc_comp);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_device_enable(&bnad->bna.device);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
wait_for_completion(&bnad->bnad_completions.ioc_comp);
|
|
|
|
if (bnad->bnad_completions.ioc_comp_status)
|
|
err = bnad->bnad_completions.ioc_comp_status;
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Free BNA resources */
|
|
static void
|
|
bnad_res_free(struct bnad *bnad)
|
|
{
|
|
int i;
|
|
struct bna_res_info *res_info = &bnad->res_info[0];
|
|
|
|
for (i = 0; i < BNA_RES_T_MAX; i++) {
|
|
if (res_info[i].res_type == BNA_RES_T_MEM)
|
|
bnad_mem_free(bnad, &res_info[i].res_u.mem_info);
|
|
else
|
|
bnad_mbox_irq_free(bnad, &res_info[i].res_u.intr_info);
|
|
}
|
|
}
|
|
|
|
/* Allocates memory and interrupt resources for BNA */
|
|
static int
|
|
bnad_res_alloc(struct bnad *bnad)
|
|
{
|
|
int i, err;
|
|
struct bna_res_info *res_info = &bnad->res_info[0];
|
|
|
|
for (i = 0; i < BNA_RES_T_MAX; i++) {
|
|
if (res_info[i].res_type == BNA_RES_T_MEM)
|
|
err = bnad_mem_alloc(bnad, &res_info[i].res_u.mem_info);
|
|
else
|
|
err = bnad_mbox_irq_alloc(bnad,
|
|
&res_info[i].res_u.intr_info);
|
|
if (err)
|
|
goto err_return;
|
|
}
|
|
return 0;
|
|
|
|
err_return:
|
|
bnad_res_free(bnad);
|
|
return err;
|
|
}
|
|
|
|
/* Interrupt enable / disable */
|
|
static void
|
|
bnad_enable_msix(struct bnad *bnad)
|
|
{
|
|
int i, ret;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
if (!(bnad->cfg_flags & BNAD_CF_MSIX)) {
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
return;
|
|
}
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
if (bnad->msix_table)
|
|
return;
|
|
|
|
bnad->msix_table =
|
|
kcalloc(bnad->msix_num, sizeof(struct msix_entry), GFP_KERNEL);
|
|
|
|
if (!bnad->msix_table)
|
|
goto intx_mode;
|
|
|
|
for (i = 0; i < bnad->msix_num; i++)
|
|
bnad->msix_table[i].entry = i;
|
|
|
|
ret = pci_enable_msix(bnad->pcidev, bnad->msix_table, bnad->msix_num);
|
|
if (ret > 0) {
|
|
/* Not enough MSI-X vectors. */
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
/* ret = #of vectors that we got */
|
|
bnad_q_num_adjust(bnad, ret);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
bnad->msix_num = (bnad->num_tx * bnad->num_txq_per_tx)
|
|
+ (bnad->num_rx
|
|
* bnad->num_rxp_per_rx) +
|
|
BNAD_MAILBOX_MSIX_VECTORS;
|
|
|
|
/* Try once more with adjusted numbers */
|
|
/* If this fails, fall back to INTx */
|
|
ret = pci_enable_msix(bnad->pcidev, bnad->msix_table,
|
|
bnad->msix_num);
|
|
if (ret)
|
|
goto intx_mode;
|
|
|
|
} else if (ret < 0)
|
|
goto intx_mode;
|
|
return;
|
|
|
|
intx_mode:
|
|
|
|
kfree(bnad->msix_table);
|
|
bnad->msix_table = NULL;
|
|
bnad->msix_num = 0;
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bnad->cfg_flags &= ~BNAD_CF_MSIX;
|
|
bnad_q_num_init(bnad);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
static void
|
|
bnad_disable_msix(struct bnad *bnad)
|
|
{
|
|
u32 cfg_flags;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
cfg_flags = bnad->cfg_flags;
|
|
if (bnad->cfg_flags & BNAD_CF_MSIX)
|
|
bnad->cfg_flags &= ~BNAD_CF_MSIX;
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
if (cfg_flags & BNAD_CF_MSIX) {
|
|
pci_disable_msix(bnad->pcidev);
|
|
kfree(bnad->msix_table);
|
|
bnad->msix_table = NULL;
|
|
}
|
|
}
|
|
|
|
/* Netdev entry points */
|
|
static int
|
|
bnad_open(struct net_device *netdev)
|
|
{
|
|
int err;
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
struct bna_pause_config pause_config;
|
|
int mtu;
|
|
unsigned long flags;
|
|
|
|
mutex_lock(&bnad->conf_mutex);
|
|
|
|
/* Tx */
|
|
err = bnad_setup_tx(bnad, 0);
|
|
if (err)
|
|
goto err_return;
|
|
|
|
/* Rx */
|
|
err = bnad_setup_rx(bnad, 0);
|
|
if (err)
|
|
goto cleanup_tx;
|
|
|
|
/* Port */
|
|
pause_config.tx_pause = 0;
|
|
pause_config.rx_pause = 0;
|
|
|
|
mtu = ETH_HLEN + bnad->netdev->mtu + ETH_FCS_LEN;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_port_mtu_set(&bnad->bna.port, mtu, NULL);
|
|
bna_port_pause_config(&bnad->bna.port, &pause_config, NULL);
|
|
bna_port_enable(&bnad->bna.port);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
/* Enable broadcast */
|
|
bnad_enable_default_bcast(bnad);
|
|
|
|
/* Set the UCAST address */
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bnad_mac_addr_set_locked(bnad, netdev->dev_addr);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
/* Start the stats timer */
|
|
bnad_stats_timer_start(bnad);
|
|
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
|
|
return 0;
|
|
|
|
cleanup_tx:
|
|
bnad_cleanup_tx(bnad, 0);
|
|
|
|
err_return:
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
bnad_stop(struct net_device *netdev)
|
|
{
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
unsigned long flags;
|
|
|
|
mutex_lock(&bnad->conf_mutex);
|
|
|
|
/* Stop the stats timer */
|
|
bnad_stats_timer_stop(bnad);
|
|
|
|
init_completion(&bnad->bnad_completions.port_comp);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_port_disable(&bnad->bna.port, BNA_HARD_CLEANUP,
|
|
bnad_cb_port_disabled);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
wait_for_completion(&bnad->bnad_completions.port_comp);
|
|
|
|
bnad_cleanup_tx(bnad, 0);
|
|
bnad_cleanup_rx(bnad, 0);
|
|
|
|
/* Synchronize mailbox IRQ */
|
|
bnad_mbox_irq_sync(bnad);
|
|
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* TX */
|
|
/*
|
|
* bnad_start_xmit : Netdev entry point for Transmit
|
|
* Called under lock held by net_device
|
|
*/
|
|
static netdev_tx_t
|
|
bnad_start_xmit(struct sk_buff *skb, struct net_device *netdev)
|
|
{
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
|
|
u16 txq_prod, vlan_tag = 0;
|
|
u32 unmap_prod, wis, wis_used, wi_range;
|
|
u32 vectors, vect_id, i, acked;
|
|
u32 tx_id;
|
|
int err;
|
|
|
|
struct bnad_tx_info *tx_info;
|
|
struct bna_tcb *tcb;
|
|
struct bnad_unmap_q *unmap_q;
|
|
dma_addr_t dma_addr;
|
|
struct bna_txq_entry *txqent;
|
|
bna_txq_wi_ctrl_flag_t flags;
|
|
|
|
if (unlikely
|
|
(skb->len <= ETH_HLEN || skb->len > BFI_TX_MAX_DATA_PER_PKT)) {
|
|
dev_kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
/*
|
|
* Takes care of the Tx that is scheduled between clearing the flag
|
|
* and the netif_stop_queue() call.
|
|
*/
|
|
if (unlikely(!test_bit(BNAD_RF_TX_STARTED, &bnad->run_flags))) {
|
|
dev_kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
tx_id = 0;
|
|
|
|
tx_info = &bnad->tx_info[tx_id];
|
|
tcb = tx_info->tcb[tx_id];
|
|
unmap_q = tcb->unmap_q;
|
|
|
|
vectors = 1 + skb_shinfo(skb)->nr_frags;
|
|
if (vectors > BFI_TX_MAX_VECTORS_PER_PKT) {
|
|
dev_kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
wis = BNA_TXQ_WI_NEEDED(vectors); /* 4 vectors per work item */
|
|
acked = 0;
|
|
if (unlikely
|
|
(wis > BNA_QE_FREE_CNT(tcb, tcb->q_depth) ||
|
|
vectors > BNA_QE_FREE_CNT(unmap_q, unmap_q->q_depth))) {
|
|
if ((u16) (*tcb->hw_consumer_index) !=
|
|
tcb->consumer_index &&
|
|
!test_and_set_bit(BNAD_TXQ_FREE_SENT, &tcb->flags)) {
|
|
acked = bnad_free_txbufs(bnad, tcb);
|
|
bna_ib_ack(tcb->i_dbell, acked);
|
|
smp_mb__before_clear_bit();
|
|
clear_bit(BNAD_TXQ_FREE_SENT, &tcb->flags);
|
|
} else {
|
|
netif_stop_queue(netdev);
|
|
BNAD_UPDATE_CTR(bnad, netif_queue_stop);
|
|
}
|
|
|
|
smp_mb();
|
|
/*
|
|
* Check again to deal with race condition between
|
|
* netif_stop_queue here, and netif_wake_queue in
|
|
* interrupt handler which is not inside netif tx lock.
|
|
*/
|
|
if (likely
|
|
(wis > BNA_QE_FREE_CNT(tcb, tcb->q_depth) ||
|
|
vectors > BNA_QE_FREE_CNT(unmap_q, unmap_q->q_depth))) {
|
|
BNAD_UPDATE_CTR(bnad, netif_queue_stop);
|
|
return NETDEV_TX_BUSY;
|
|
} else {
|
|
netif_wake_queue(netdev);
|
|
BNAD_UPDATE_CTR(bnad, netif_queue_wakeup);
|
|
}
|
|
}
|
|
|
|
unmap_prod = unmap_q->producer_index;
|
|
wis_used = 1;
|
|
vect_id = 0;
|
|
flags = 0;
|
|
|
|
txq_prod = tcb->producer_index;
|
|
BNA_TXQ_QPGE_PTR_GET(txq_prod, tcb->sw_qpt, txqent, wi_range);
|
|
BUG_ON(!(wi_range <= tcb->q_depth));
|
|
txqent->hdr.wi.reserved = 0;
|
|
txqent->hdr.wi.num_vectors = vectors;
|
|
txqent->hdr.wi.opcode =
|
|
htons((skb_is_gso(skb) ? BNA_TXQ_WI_SEND_LSO :
|
|
BNA_TXQ_WI_SEND));
|
|
|
|
if (vlan_tx_tag_present(skb)) {
|
|
vlan_tag = (u16) vlan_tx_tag_get(skb);
|
|
flags |= (BNA_TXQ_WI_CF_INS_PRIO | BNA_TXQ_WI_CF_INS_VLAN);
|
|
}
|
|
if (test_bit(BNAD_RF_CEE_RUNNING, &bnad->run_flags)) {
|
|
vlan_tag =
|
|
(tcb->priority & 0x7) << 13 | (vlan_tag & 0x1fff);
|
|
flags |= (BNA_TXQ_WI_CF_INS_PRIO | BNA_TXQ_WI_CF_INS_VLAN);
|
|
}
|
|
|
|
txqent->hdr.wi.vlan_tag = htons(vlan_tag);
|
|
|
|
if (skb_is_gso(skb)) {
|
|
err = bnad_tso_prepare(bnad, skb);
|
|
if (err) {
|
|
dev_kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
txqent->hdr.wi.lso_mss = htons(skb_is_gso(skb));
|
|
flags |= (BNA_TXQ_WI_CF_IP_CKSUM | BNA_TXQ_WI_CF_TCP_CKSUM);
|
|
txqent->hdr.wi.l4_hdr_size_n_offset =
|
|
htons(BNA_TXQ_WI_L4_HDR_N_OFFSET
|
|
(tcp_hdrlen(skb) >> 2,
|
|
skb_transport_offset(skb)));
|
|
} else if (skb->ip_summed == CHECKSUM_PARTIAL) {
|
|
u8 proto = 0;
|
|
|
|
txqent->hdr.wi.lso_mss = 0;
|
|
|
|
if (skb->protocol == htons(ETH_P_IP))
|
|
proto = ip_hdr(skb)->protocol;
|
|
else if (skb->protocol == htons(ETH_P_IPV6)) {
|
|
/* nexthdr may not be TCP immediately. */
|
|
proto = ipv6_hdr(skb)->nexthdr;
|
|
}
|
|
if (proto == IPPROTO_TCP) {
|
|
flags |= BNA_TXQ_WI_CF_TCP_CKSUM;
|
|
txqent->hdr.wi.l4_hdr_size_n_offset =
|
|
htons(BNA_TXQ_WI_L4_HDR_N_OFFSET
|
|
(0, skb_transport_offset(skb)));
|
|
|
|
BNAD_UPDATE_CTR(bnad, tcpcsum_offload);
|
|
|
|
BUG_ON(!(skb_headlen(skb) >=
|
|
skb_transport_offset(skb) + tcp_hdrlen(skb)));
|
|
|
|
} else if (proto == IPPROTO_UDP) {
|
|
flags |= BNA_TXQ_WI_CF_UDP_CKSUM;
|
|
txqent->hdr.wi.l4_hdr_size_n_offset =
|
|
htons(BNA_TXQ_WI_L4_HDR_N_OFFSET
|
|
(0, skb_transport_offset(skb)));
|
|
|
|
BNAD_UPDATE_CTR(bnad, udpcsum_offload);
|
|
|
|
BUG_ON(!(skb_headlen(skb) >=
|
|
skb_transport_offset(skb) +
|
|
sizeof(struct udphdr)));
|
|
} else {
|
|
err = skb_checksum_help(skb);
|
|
BNAD_UPDATE_CTR(bnad, csum_help);
|
|
if (err) {
|
|
dev_kfree_skb(skb);
|
|
BNAD_UPDATE_CTR(bnad, csum_help_err);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
}
|
|
} else {
|
|
txqent->hdr.wi.lso_mss = 0;
|
|
txqent->hdr.wi.l4_hdr_size_n_offset = 0;
|
|
}
|
|
|
|
txqent->hdr.wi.flags = htons(flags);
|
|
|
|
txqent->hdr.wi.frame_length = htonl(skb->len);
|
|
|
|
unmap_q->unmap_array[unmap_prod].skb = skb;
|
|
BUG_ON(!(skb_headlen(skb) <= BFI_TX_MAX_DATA_PER_VECTOR));
|
|
txqent->vector[vect_id].length = htons(skb_headlen(skb));
|
|
dma_addr = pci_map_single(bnad->pcidev, skb->data, skb_headlen(skb),
|
|
PCI_DMA_TODEVICE);
|
|
pci_unmap_addr_set(&unmap_q->unmap_array[unmap_prod], dma_addr,
|
|
dma_addr);
|
|
|
|
BNA_SET_DMA_ADDR(dma_addr, &txqent->vector[vect_id].host_addr);
|
|
BNA_QE_INDX_ADD(unmap_prod, 1, unmap_q->q_depth);
|
|
|
|
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
|
|
struct skb_frag_struct *frag = &skb_shinfo(skb)->frags[i];
|
|
u32 size = frag->size;
|
|
|
|
if (++vect_id == BFI_TX_MAX_VECTORS_PER_WI) {
|
|
vect_id = 0;
|
|
if (--wi_range)
|
|
txqent++;
|
|
else {
|
|
BNA_QE_INDX_ADD(txq_prod, wis_used,
|
|
tcb->q_depth);
|
|
wis_used = 0;
|
|
BNA_TXQ_QPGE_PTR_GET(txq_prod, tcb->sw_qpt,
|
|
txqent, wi_range);
|
|
BUG_ON(!(wi_range <= tcb->q_depth));
|
|
}
|
|
wis_used++;
|
|
txqent->hdr.wi_ext.opcode = htons(BNA_TXQ_WI_EXTENSION);
|
|
}
|
|
|
|
BUG_ON(!(size <= BFI_TX_MAX_DATA_PER_VECTOR));
|
|
txqent->vector[vect_id].length = htons(size);
|
|
dma_addr =
|
|
pci_map_page(bnad->pcidev, frag->page,
|
|
frag->page_offset, size,
|
|
PCI_DMA_TODEVICE);
|
|
pci_unmap_addr_set(&unmap_q->unmap_array[unmap_prod], dma_addr,
|
|
dma_addr);
|
|
BNA_SET_DMA_ADDR(dma_addr, &txqent->vector[vect_id].host_addr);
|
|
BNA_QE_INDX_ADD(unmap_prod, 1, unmap_q->q_depth);
|
|
}
|
|
|
|
unmap_q->producer_index = unmap_prod;
|
|
BNA_QE_INDX_ADD(txq_prod, wis_used, tcb->q_depth);
|
|
tcb->producer_index = txq_prod;
|
|
|
|
smp_mb();
|
|
bna_txq_prod_indx_doorbell(tcb);
|
|
|
|
if ((u16) (*tcb->hw_consumer_index) != tcb->consumer_index)
|
|
tasklet_schedule(&bnad->tx_free_tasklet);
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
/*
|
|
* Used spin_lock to synchronize reading of stats structures, which
|
|
* is written by BNA under the same lock.
|
|
*/
|
|
static struct rtnl_link_stats64 *
|
|
bnad_get_stats64(struct net_device *netdev, struct rtnl_link_stats64 *stats)
|
|
{
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
|
|
bnad_netdev_qstats_fill(bnad, stats);
|
|
bnad_netdev_hwstats_fill(bnad, stats);
|
|
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
return stats;
|
|
}
|
|
|
|
static void
|
|
bnad_set_rx_mode(struct net_device *netdev)
|
|
{
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
u32 new_mask, valid_mask;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
|
|
new_mask = valid_mask = 0;
|
|
|
|
if (netdev->flags & IFF_PROMISC) {
|
|
if (!(bnad->cfg_flags & BNAD_CF_PROMISC)) {
|
|
new_mask = BNAD_RXMODE_PROMISC_DEFAULT;
|
|
valid_mask = BNAD_RXMODE_PROMISC_DEFAULT;
|
|
bnad->cfg_flags |= BNAD_CF_PROMISC;
|
|
}
|
|
} else {
|
|
if (bnad->cfg_flags & BNAD_CF_PROMISC) {
|
|
new_mask = ~BNAD_RXMODE_PROMISC_DEFAULT;
|
|
valid_mask = BNAD_RXMODE_PROMISC_DEFAULT;
|
|
bnad->cfg_flags &= ~BNAD_CF_PROMISC;
|
|
}
|
|
}
|
|
|
|
if (netdev->flags & IFF_ALLMULTI) {
|
|
if (!(bnad->cfg_flags & BNAD_CF_ALLMULTI)) {
|
|
new_mask |= BNA_RXMODE_ALLMULTI;
|
|
valid_mask |= BNA_RXMODE_ALLMULTI;
|
|
bnad->cfg_flags |= BNAD_CF_ALLMULTI;
|
|
}
|
|
} else {
|
|
if (bnad->cfg_flags & BNAD_CF_ALLMULTI) {
|
|
new_mask &= ~BNA_RXMODE_ALLMULTI;
|
|
valid_mask |= BNA_RXMODE_ALLMULTI;
|
|
bnad->cfg_flags &= ~BNAD_CF_ALLMULTI;
|
|
}
|
|
}
|
|
|
|
bna_rx_mode_set(bnad->rx_info[0].rx, new_mask, valid_mask, NULL);
|
|
|
|
if (!netdev_mc_empty(netdev)) {
|
|
u8 *mcaddr_list;
|
|
int mc_count = netdev_mc_count(netdev);
|
|
|
|
/* Index 0 holds the broadcast address */
|
|
mcaddr_list =
|
|
kzalloc((mc_count + 1) * ETH_ALEN,
|
|
GFP_ATOMIC);
|
|
if (!mcaddr_list)
|
|
goto unlock;
|
|
|
|
memcpy(&mcaddr_list[0], &bnad_bcast_addr[0], ETH_ALEN);
|
|
|
|
/* Copy rest of the MC addresses */
|
|
bnad_netdev_mc_list_get(netdev, mcaddr_list);
|
|
|
|
bna_rx_mcast_listset(bnad->rx_info[0].rx, mc_count + 1,
|
|
mcaddr_list, NULL);
|
|
|
|
/* Should we enable BNAD_CF_ALLMULTI for err != 0 ? */
|
|
kfree(mcaddr_list);
|
|
}
|
|
unlock:
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* bna_lock is used to sync writes to netdev->addr
|
|
* conf_lock cannot be used since this call may be made
|
|
* in a non-blocking context.
|
|
*/
|
|
static int
|
|
bnad_set_mac_address(struct net_device *netdev, void *mac_addr)
|
|
{
|
|
int err;
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
struct sockaddr *sa = (struct sockaddr *)mac_addr;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
|
|
err = bnad_mac_addr_set_locked(bnad, sa->sa_data);
|
|
|
|
if (!err)
|
|
memcpy(netdev->dev_addr, sa->sa_data, netdev->addr_len);
|
|
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
bnad_change_mtu(struct net_device *netdev, int new_mtu)
|
|
{
|
|
int mtu, err = 0;
|
|
unsigned long flags;
|
|
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
|
|
if (new_mtu + ETH_HLEN < ETH_ZLEN || new_mtu > BNAD_JUMBO_MTU)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&bnad->conf_mutex);
|
|
|
|
netdev->mtu = new_mtu;
|
|
|
|
mtu = ETH_HLEN + new_mtu + ETH_FCS_LEN;
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_port_mtu_set(&bnad->bna.port, mtu, NULL);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
bnad_vlan_rx_register(struct net_device *netdev,
|
|
struct vlan_group *vlan_grp)
|
|
{
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
|
|
mutex_lock(&bnad->conf_mutex);
|
|
bnad->vlan_grp = vlan_grp;
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
}
|
|
|
|
static void
|
|
bnad_vlan_rx_add_vid(struct net_device *netdev,
|
|
unsigned short vid)
|
|
{
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
unsigned long flags;
|
|
|
|
if (!bnad->rx_info[0].rx)
|
|
return;
|
|
|
|
mutex_lock(&bnad->conf_mutex);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_rx_vlan_add(bnad->rx_info[0].rx, vid);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
}
|
|
|
|
static void
|
|
bnad_vlan_rx_kill_vid(struct net_device *netdev,
|
|
unsigned short vid)
|
|
{
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
unsigned long flags;
|
|
|
|
if (!bnad->rx_info[0].rx)
|
|
return;
|
|
|
|
mutex_lock(&bnad->conf_mutex);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_rx_vlan_del(bnad->rx_info[0].rx, vid);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
}
|
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
static void
|
|
bnad_netpoll(struct net_device *netdev)
|
|
{
|
|
struct bnad *bnad = netdev_priv(netdev);
|
|
struct bnad_rx_info *rx_info;
|
|
struct bnad_rx_ctrl *rx_ctrl;
|
|
u32 curr_mask;
|
|
int i, j;
|
|
|
|
if (!(bnad->cfg_flags & BNAD_CF_MSIX)) {
|
|
bna_intx_disable(&bnad->bna, curr_mask);
|
|
bnad_isr(bnad->pcidev->irq, netdev);
|
|
bna_intx_enable(&bnad->bna, curr_mask);
|
|
} else {
|
|
for (i = 0; i < bnad->num_rx; i++) {
|
|
rx_info = &bnad->rx_info[i];
|
|
if (!rx_info->rx)
|
|
continue;
|
|
for (j = 0; j < bnad->num_rxp_per_rx; j++) {
|
|
rx_ctrl = &rx_info->rx_ctrl[j];
|
|
if (rx_ctrl->ccb) {
|
|
bnad_disable_rx_irq(bnad,
|
|
rx_ctrl->ccb);
|
|
bnad_netif_rx_schedule_poll(bnad,
|
|
rx_ctrl->ccb);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static const struct net_device_ops bnad_netdev_ops = {
|
|
.ndo_open = bnad_open,
|
|
.ndo_stop = bnad_stop,
|
|
.ndo_start_xmit = bnad_start_xmit,
|
|
.ndo_get_stats64 = bnad_get_stats64,
|
|
.ndo_set_rx_mode = bnad_set_rx_mode,
|
|
.ndo_set_multicast_list = bnad_set_rx_mode,
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
.ndo_set_mac_address = bnad_set_mac_address,
|
|
.ndo_change_mtu = bnad_change_mtu,
|
|
.ndo_vlan_rx_register = bnad_vlan_rx_register,
|
|
.ndo_vlan_rx_add_vid = bnad_vlan_rx_add_vid,
|
|
.ndo_vlan_rx_kill_vid = bnad_vlan_rx_kill_vid,
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
.ndo_poll_controller = bnad_netpoll
|
|
#endif
|
|
};
|
|
|
|
static void
|
|
bnad_netdev_init(struct bnad *bnad, bool using_dac)
|
|
{
|
|
struct net_device *netdev = bnad->netdev;
|
|
|
|
netdev->features |= NETIF_F_IPV6_CSUM;
|
|
netdev->features |= NETIF_F_TSO;
|
|
netdev->features |= NETIF_F_TSO6;
|
|
|
|
netdev->features |= NETIF_F_GRO;
|
|
pr_warn("bna: GRO enabled, using kernel stack GRO\n");
|
|
|
|
netdev->features |= NETIF_F_SG | NETIF_F_IP_CSUM;
|
|
|
|
if (using_dac)
|
|
netdev->features |= NETIF_F_HIGHDMA;
|
|
|
|
netdev->features |=
|
|
NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_RX |
|
|
NETIF_F_HW_VLAN_FILTER;
|
|
|
|
netdev->vlan_features = netdev->features;
|
|
netdev->mem_start = bnad->mmio_start;
|
|
netdev->mem_end = bnad->mmio_start + bnad->mmio_len - 1;
|
|
|
|
netdev->netdev_ops = &bnad_netdev_ops;
|
|
bnad_set_ethtool_ops(netdev);
|
|
}
|
|
|
|
/*
|
|
* 1. Initialize the bnad structure
|
|
* 2. Setup netdev pointer in pci_dev
|
|
* 3. Initialze Tx free tasklet
|
|
* 4. Initialize no. of TxQ & CQs & MSIX vectors
|
|
*/
|
|
static int
|
|
bnad_init(struct bnad *bnad,
|
|
struct pci_dev *pdev, struct net_device *netdev)
|
|
{
|
|
unsigned long flags;
|
|
|
|
SET_NETDEV_DEV(netdev, &pdev->dev);
|
|
pci_set_drvdata(pdev, netdev);
|
|
|
|
bnad->netdev = netdev;
|
|
bnad->pcidev = pdev;
|
|
bnad->mmio_start = pci_resource_start(pdev, 0);
|
|
bnad->mmio_len = pci_resource_len(pdev, 0);
|
|
bnad->bar0 = ioremap_nocache(bnad->mmio_start, bnad->mmio_len);
|
|
if (!bnad->bar0) {
|
|
dev_err(&pdev->dev, "ioremap for bar0 failed\n");
|
|
pci_set_drvdata(pdev, NULL);
|
|
return -ENOMEM;
|
|
}
|
|
pr_info("bar0 mapped to %p, len %llu\n", bnad->bar0,
|
|
(unsigned long long) bnad->mmio_len);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
if (!bnad_msix_disable)
|
|
bnad->cfg_flags = BNAD_CF_MSIX;
|
|
|
|
bnad->cfg_flags |= BNAD_CF_DIM_ENABLED;
|
|
|
|
bnad_q_num_init(bnad);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
bnad->msix_num = (bnad->num_tx * bnad->num_txq_per_tx) +
|
|
(bnad->num_rx * bnad->num_rxp_per_rx) +
|
|
BNAD_MAILBOX_MSIX_VECTORS;
|
|
|
|
bnad->txq_depth = BNAD_TXQ_DEPTH;
|
|
bnad->rxq_depth = BNAD_RXQ_DEPTH;
|
|
bnad->rx_csum = true;
|
|
|
|
bnad->tx_coalescing_timeo = BFI_TX_COALESCING_TIMEO;
|
|
bnad->rx_coalescing_timeo = BFI_RX_COALESCING_TIMEO;
|
|
|
|
tasklet_init(&bnad->tx_free_tasklet, bnad_tx_free_tasklet,
|
|
(unsigned long)bnad);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Must be called after bnad_pci_uninit()
|
|
* so that iounmap() and pci_set_drvdata(NULL)
|
|
* happens only after PCI uninitialization.
|
|
*/
|
|
static void
|
|
bnad_uninit(struct bnad *bnad)
|
|
{
|
|
if (bnad->bar0)
|
|
iounmap(bnad->bar0);
|
|
pci_set_drvdata(bnad->pcidev, NULL);
|
|
}
|
|
|
|
/*
|
|
* Initialize locks
|
|
a) Per device mutes used for serializing configuration
|
|
changes from OS interface
|
|
b) spin lock used to protect bna state machine
|
|
*/
|
|
static void
|
|
bnad_lock_init(struct bnad *bnad)
|
|
{
|
|
spin_lock_init(&bnad->bna_lock);
|
|
mutex_init(&bnad->conf_mutex);
|
|
}
|
|
|
|
static void
|
|
bnad_lock_uninit(struct bnad *bnad)
|
|
{
|
|
mutex_destroy(&bnad->conf_mutex);
|
|
}
|
|
|
|
/* PCI Initialization */
|
|
static int
|
|
bnad_pci_init(struct bnad *bnad,
|
|
struct pci_dev *pdev, bool *using_dac)
|
|
{
|
|
int err;
|
|
|
|
err = pci_enable_device(pdev);
|
|
if (err)
|
|
return err;
|
|
err = pci_request_regions(pdev, BNAD_NAME);
|
|
if (err)
|
|
goto disable_device;
|
|
if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) &&
|
|
!pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64))) {
|
|
*using_dac = 1;
|
|
} else {
|
|
err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
|
|
if (err) {
|
|
err = pci_set_consistent_dma_mask(pdev,
|
|
DMA_BIT_MASK(32));
|
|
if (err)
|
|
goto release_regions;
|
|
}
|
|
*using_dac = 0;
|
|
}
|
|
pci_set_master(pdev);
|
|
return 0;
|
|
|
|
release_regions:
|
|
pci_release_regions(pdev);
|
|
disable_device:
|
|
pci_disable_device(pdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
bnad_pci_uninit(struct pci_dev *pdev)
|
|
{
|
|
pci_release_regions(pdev);
|
|
pci_disable_device(pdev);
|
|
}
|
|
|
|
static int __devinit
|
|
bnad_pci_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *pcidev_id)
|
|
{
|
|
bool using_dac;
|
|
int err;
|
|
struct bnad *bnad;
|
|
struct bna *bna;
|
|
struct net_device *netdev;
|
|
struct bfa_pcidev pcidev_info;
|
|
unsigned long flags;
|
|
|
|
pr_info("bnad_pci_probe : (0x%p, 0x%p) PCI Func : (%d)\n",
|
|
pdev, pcidev_id, PCI_FUNC(pdev->devfn));
|
|
|
|
mutex_lock(&bnad_fwimg_mutex);
|
|
if (!cna_get_firmware_buf(pdev)) {
|
|
mutex_unlock(&bnad_fwimg_mutex);
|
|
pr_warn("Failed to load Firmware Image!\n");
|
|
return -ENODEV;
|
|
}
|
|
mutex_unlock(&bnad_fwimg_mutex);
|
|
|
|
/*
|
|
* Allocates sizeof(struct net_device + struct bnad)
|
|
* bnad = netdev->priv
|
|
*/
|
|
netdev = alloc_etherdev(sizeof(struct bnad));
|
|
if (!netdev) {
|
|
dev_err(&pdev->dev, "alloc_etherdev failed\n");
|
|
err = -ENOMEM;
|
|
return err;
|
|
}
|
|
bnad = netdev_priv(netdev);
|
|
|
|
/*
|
|
* PCI initialization
|
|
* Output : using_dac = 1 for 64 bit DMA
|
|
* = 0 for 32 bit DMA
|
|
*/
|
|
err = bnad_pci_init(bnad, pdev, &using_dac);
|
|
if (err)
|
|
goto free_netdev;
|
|
|
|
bnad_lock_init(bnad);
|
|
/*
|
|
* Initialize bnad structure
|
|
* Setup relation between pci_dev & netdev
|
|
* Init Tx free tasklet
|
|
*/
|
|
err = bnad_init(bnad, pdev, netdev);
|
|
if (err)
|
|
goto pci_uninit;
|
|
/* Initialize netdev structure, set up ethtool ops */
|
|
bnad_netdev_init(bnad, using_dac);
|
|
|
|
bnad_enable_msix(bnad);
|
|
|
|
/* Get resource requirement form bna */
|
|
bna_res_req(&bnad->res_info[0]);
|
|
|
|
/* Allocate resources from bna */
|
|
err = bnad_res_alloc(bnad);
|
|
if (err)
|
|
goto free_netdev;
|
|
|
|
bna = &bnad->bna;
|
|
|
|
/* Setup pcidev_info for bna_init() */
|
|
pcidev_info.pci_slot = PCI_SLOT(bnad->pcidev->devfn);
|
|
pcidev_info.pci_func = PCI_FUNC(bnad->pcidev->devfn);
|
|
pcidev_info.device_id = bnad->pcidev->device;
|
|
pcidev_info.pci_bar_kva = bnad->bar0;
|
|
|
|
mutex_lock(&bnad->conf_mutex);
|
|
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_init(bna, bnad, &pcidev_info, &bnad->res_info[0]);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
bnad->stats.bna_stats = &bna->stats;
|
|
|
|
/* Set up timers */
|
|
setup_timer(&bnad->bna.device.ioc.ioc_timer, bnad_ioc_timeout,
|
|
((unsigned long)bnad));
|
|
setup_timer(&bnad->bna.device.ioc.hb_timer, bnad_ioc_hb_check,
|
|
((unsigned long)bnad));
|
|
setup_timer(&bnad->bna.device.ioc.sem_timer, bnad_ioc_sem_timeout,
|
|
((unsigned long)bnad));
|
|
|
|
/* Now start the timer before calling IOC */
|
|
mod_timer(&bnad->bna.device.ioc.ioc_timer,
|
|
jiffies + msecs_to_jiffies(BNA_IOC_TIMER_FREQ));
|
|
|
|
/*
|
|
* Start the chip
|
|
* Don't care even if err != 0, bna state machine will
|
|
* deal with it
|
|
*/
|
|
err = bnad_device_enable(bnad);
|
|
|
|
/* Get the burnt-in mac */
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_port_mac_get(&bna->port, &bnad->perm_addr);
|
|
bnad_set_netdev_perm_addr(bnad);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
|
|
/*
|
|
* Make sure the link appears down to the stack
|
|
*/
|
|
netif_carrier_off(netdev);
|
|
|
|
/* Finally, reguister with net_device layer */
|
|
err = register_netdev(netdev);
|
|
if (err) {
|
|
pr_err("BNA : Registering with netdev failed\n");
|
|
goto disable_device;
|
|
}
|
|
|
|
return 0;
|
|
|
|
disable_device:
|
|
mutex_lock(&bnad->conf_mutex);
|
|
bnad_device_disable(bnad);
|
|
del_timer_sync(&bnad->bna.device.ioc.ioc_timer);
|
|
del_timer_sync(&bnad->bna.device.ioc.sem_timer);
|
|
del_timer_sync(&bnad->bna.device.ioc.hb_timer);
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_uninit(bna);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
|
|
bnad_res_free(bnad);
|
|
bnad_disable_msix(bnad);
|
|
pci_uninit:
|
|
bnad_pci_uninit(pdev);
|
|
bnad_lock_uninit(bnad);
|
|
bnad_uninit(bnad);
|
|
free_netdev:
|
|
free_netdev(netdev);
|
|
return err;
|
|
}
|
|
|
|
static void __devexit
|
|
bnad_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct net_device *netdev = pci_get_drvdata(pdev);
|
|
struct bnad *bnad;
|
|
struct bna *bna;
|
|
unsigned long flags;
|
|
|
|
if (!netdev)
|
|
return;
|
|
|
|
pr_info("%s bnad_pci_remove\n", netdev->name);
|
|
bnad = netdev_priv(netdev);
|
|
bna = &bnad->bna;
|
|
|
|
unregister_netdev(netdev);
|
|
|
|
mutex_lock(&bnad->conf_mutex);
|
|
bnad_device_disable(bnad);
|
|
del_timer_sync(&bnad->bna.device.ioc.ioc_timer);
|
|
del_timer_sync(&bnad->bna.device.ioc.sem_timer);
|
|
del_timer_sync(&bnad->bna.device.ioc.hb_timer);
|
|
spin_lock_irqsave(&bnad->bna_lock, flags);
|
|
bna_uninit(bna);
|
|
spin_unlock_irqrestore(&bnad->bna_lock, flags);
|
|
mutex_unlock(&bnad->conf_mutex);
|
|
|
|
bnad_res_free(bnad);
|
|
bnad_disable_msix(bnad);
|
|
bnad_pci_uninit(pdev);
|
|
bnad_lock_uninit(bnad);
|
|
bnad_uninit(bnad);
|
|
free_netdev(netdev);
|
|
}
|
|
|
|
static const struct pci_device_id bnad_pci_id_table[] = {
|
|
{
|
|
PCI_DEVICE(PCI_VENDOR_ID_BROCADE,
|
|
PCI_DEVICE_ID_BROCADE_CT),
|
|
.class = PCI_CLASS_NETWORK_ETHERNET << 8,
|
|
.class_mask = 0xffff00
|
|
}, {0, }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, bnad_pci_id_table);
|
|
|
|
static struct pci_driver bnad_pci_driver = {
|
|
.name = BNAD_NAME,
|
|
.id_table = bnad_pci_id_table,
|
|
.probe = bnad_pci_probe,
|
|
.remove = __devexit_p(bnad_pci_remove),
|
|
};
|
|
|
|
static int __init
|
|
bnad_module_init(void)
|
|
{
|
|
int err;
|
|
|
|
pr_info("Brocade 10G Ethernet driver\n");
|
|
|
|
bfa_nw_ioc_auto_recover(bnad_ioc_auto_recover);
|
|
|
|
err = pci_register_driver(&bnad_pci_driver);
|
|
if (err < 0) {
|
|
pr_err("bna : PCI registration failed in module init "
|
|
"(%d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit
|
|
bnad_module_exit(void)
|
|
{
|
|
pci_unregister_driver(&bnad_pci_driver);
|
|
|
|
if (bfi_fw)
|
|
release_firmware(bfi_fw);
|
|
}
|
|
|
|
module_init(bnad_module_init);
|
|
module_exit(bnad_module_exit);
|
|
|
|
MODULE_AUTHOR("Brocade");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Brocade 10G PCIe Ethernet driver");
|
|
MODULE_VERSION(BNAD_VERSION);
|
|
MODULE_FIRMWARE(CNA_FW_FILE_CT);
|