706 lines
20 KiB
C
706 lines
20 KiB
C
/*
|
|
* Copyright (C) 2005 - 2008 ServerEngines
|
|
* All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation. The full GNU General
|
|
* Public License is included in this distribution in the file called COPYING.
|
|
*
|
|
* Contact Information:
|
|
* linux-drivers@serverengines.com
|
|
*
|
|
* ServerEngines
|
|
* 209 N. Fair Oaks Ave
|
|
* Sunnyvale, CA 94085
|
|
*/
|
|
/*
|
|
* be_netif.c
|
|
*
|
|
* This file contains various entry points of drivers seen by tcp/ip stack.
|
|
*/
|
|
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/in.h>
|
|
#include "benet.h"
|
|
#include <linux/ip.h>
|
|
#include <linux/inet_lro.h>
|
|
|
|
/* Strings to print Link properties */
|
|
static const char *link_speed[] = {
|
|
"Invalid link Speed Value",
|
|
"10 Mbps",
|
|
"100 Mbps",
|
|
"1 Gbps",
|
|
"10 Gbps"
|
|
};
|
|
|
|
static const char *link_duplex[] = {
|
|
"Invalid Duplex Value",
|
|
"Half Duplex",
|
|
"Full Duplex"
|
|
};
|
|
|
|
static const char *link_state[] = {
|
|
"",
|
|
"(active)"
|
|
};
|
|
|
|
void be_print_link_info(struct BE_LINK_STATUS *lnk_status)
|
|
{
|
|
u16 si, di, ai;
|
|
|
|
/* Port 0 */
|
|
if (lnk_status->mac0_speed && lnk_status->mac0_duplex) {
|
|
/* Port is up and running */
|
|
si = (lnk_status->mac0_speed < 5) ? lnk_status->mac0_speed : 0;
|
|
di = (lnk_status->mac0_duplex < 3) ?
|
|
lnk_status->mac0_duplex : 0;
|
|
ai = (lnk_status->active_port == 0) ? 1 : 0;
|
|
printk(KERN_INFO "PortNo. 0: Speed - %s %s %s\n",
|
|
link_speed[si], link_duplex[di], link_state[ai]);
|
|
} else
|
|
printk(KERN_INFO "PortNo. 0: Down\n");
|
|
|
|
/* Port 1 */
|
|
if (lnk_status->mac1_speed && lnk_status->mac1_duplex) {
|
|
/* Port is up and running */
|
|
si = (lnk_status->mac1_speed < 5) ? lnk_status->mac1_speed : 0;
|
|
di = (lnk_status->mac1_duplex < 3) ?
|
|
lnk_status->mac1_duplex : 0;
|
|
ai = (lnk_status->active_port == 0) ? 1 : 0;
|
|
printk(KERN_INFO "PortNo. 1: Speed - %s %s %s\n",
|
|
link_speed[si], link_duplex[di], link_state[ai]);
|
|
} else
|
|
printk(KERN_INFO "PortNo. 1: Down\n");
|
|
|
|
return;
|
|
}
|
|
|
|
static int
|
|
be_get_frag_header(struct skb_frag_struct *frag, void **mac_hdr,
|
|
void **ip_hdr, void **tcpudp_hdr,
|
|
u64 *hdr_flags, void *priv)
|
|
{
|
|
struct ethhdr *eh;
|
|
struct vlan_ethhdr *veh;
|
|
struct iphdr *iph;
|
|
u8 *va = page_address(frag->page) + frag->page_offset;
|
|
unsigned long ll_hlen;
|
|
|
|
/* find the mac header, abort if not IPv4 */
|
|
|
|
prefetch(va);
|
|
eh = (struct ethhdr *)va;
|
|
*mac_hdr = eh;
|
|
ll_hlen = ETH_HLEN;
|
|
if (eh->h_proto != htons(ETH_P_IP)) {
|
|
if (eh->h_proto == htons(ETH_P_8021Q)) {
|
|
veh = (struct vlan_ethhdr *)va;
|
|
if (veh->h_vlan_encapsulated_proto != htons(ETH_P_IP))
|
|
return -1;
|
|
|
|
ll_hlen += VLAN_HLEN;
|
|
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
*hdr_flags = LRO_IPV4;
|
|
|
|
iph = (struct iphdr *)(va + ll_hlen);
|
|
*ip_hdr = iph;
|
|
if (iph->protocol != IPPROTO_TCP)
|
|
return -1;
|
|
*hdr_flags |= LRO_TCP;
|
|
*tcpudp_hdr = (u8 *) (*ip_hdr) + (iph->ihl << 2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int benet_open(struct net_device *netdev)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
struct be_adapter *adapter = pnob->adapter;
|
|
struct net_lro_mgr *lro_mgr;
|
|
|
|
if (adapter->dev_state < BE_DEV_STATE_INIT)
|
|
return -EAGAIN;
|
|
|
|
lro_mgr = &pnob->lro_mgr;
|
|
lro_mgr->dev = netdev;
|
|
|
|
lro_mgr->features = LRO_F_NAPI;
|
|
lro_mgr->ip_summed = CHECKSUM_UNNECESSARY;
|
|
lro_mgr->ip_summed_aggr = CHECKSUM_UNNECESSARY;
|
|
lro_mgr->max_desc = BE_MAX_LRO_DESCRIPTORS;
|
|
lro_mgr->lro_arr = pnob->lro_desc;
|
|
lro_mgr->get_frag_header = be_get_frag_header;
|
|
lro_mgr->max_aggr = adapter->max_rx_coal;
|
|
lro_mgr->frag_align_pad = 2;
|
|
if (lro_mgr->max_aggr > MAX_SKB_FRAGS)
|
|
lro_mgr->max_aggr = MAX_SKB_FRAGS;
|
|
|
|
adapter->max_rx_coal = BE_LRO_MAX_PKTS;
|
|
|
|
be_update_link_status(adapter);
|
|
|
|
/*
|
|
* Set carrier on only if Physical Link up
|
|
* Either of the port link status up signifies this
|
|
*/
|
|
if ((adapter->port0_link_sts == BE_PORT_LINK_UP) ||
|
|
(adapter->port1_link_sts == BE_PORT_LINK_UP)) {
|
|
netif_start_queue(netdev);
|
|
netif_carrier_on(netdev);
|
|
}
|
|
|
|
adapter->dev_state = BE_DEV_STATE_OPEN;
|
|
napi_enable(&pnob->napi);
|
|
be_enable_intr(pnob);
|
|
be_enable_eq_intr(pnob);
|
|
/*
|
|
* RX completion queue may be in dis-armed state. Arm it.
|
|
*/
|
|
be_notify_cmpl(pnob, 0, pnob->rx_cq_id, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int benet_close(struct net_device *netdev)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
struct be_adapter *adapter = pnob->adapter;
|
|
|
|
netif_stop_queue(netdev);
|
|
synchronize_irq(netdev->irq);
|
|
|
|
be_wait_nic_tx_cmplx_cmpl(pnob);
|
|
adapter->dev_state = BE_DEV_STATE_INIT;
|
|
netif_carrier_off(netdev);
|
|
|
|
adapter->port0_link_sts = BE_PORT_LINK_DOWN;
|
|
adapter->port1_link_sts = BE_PORT_LINK_DOWN;
|
|
be_disable_intr(pnob);
|
|
be_disable_eq_intr(pnob);
|
|
napi_disable(&pnob->napi);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Setting a Mac Address for BE
|
|
* Takes netdev and a void pointer as arguments.
|
|
* The pointer holds the new addres to be used.
|
|
*/
|
|
static int benet_set_mac_addr(struct net_device *netdev, void *p)
|
|
{
|
|
struct sockaddr *addr = p;
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
|
|
memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len);
|
|
be_rxf_mac_address_read_write(&pnob->fn_obj, 0, 0, false, true, false,
|
|
netdev->dev_addr, NULL, NULL);
|
|
/*
|
|
* Since we are doing Active-Passive failover, both
|
|
* ports should have matching MAC addresses everytime.
|
|
*/
|
|
be_rxf_mac_address_read_write(&pnob->fn_obj, 1, 0, false, true, false,
|
|
netdev->dev_addr, NULL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void be_get_stats_timer_handler(unsigned long context)
|
|
{
|
|
struct be_timer_ctxt *ctxt = (struct be_timer_ctxt *)context;
|
|
|
|
if (atomic_read(&ctxt->get_stat_flag)) {
|
|
atomic_dec(&ctxt->get_stat_flag);
|
|
up((void *)ctxt->get_stat_sem_addr);
|
|
}
|
|
del_timer(&ctxt->get_stats_timer);
|
|
return;
|
|
}
|
|
|
|
void be_get_stat_cb(void *context, int status,
|
|
struct MCC_WRB_AMAP *optional_wrb)
|
|
{
|
|
struct be_timer_ctxt *ctxt = (struct be_timer_ctxt *)context;
|
|
/*
|
|
* just up the semaphore if the get_stat_flag
|
|
* reads 1. so that the waiter can continue.
|
|
* If it is 0, then it was handled by the timer handler.
|
|
*/
|
|
del_timer(&ctxt->get_stats_timer);
|
|
if (atomic_read(&ctxt->get_stat_flag)) {
|
|
atomic_dec(&ctxt->get_stat_flag);
|
|
up((void *)ctxt->get_stat_sem_addr);
|
|
}
|
|
}
|
|
|
|
struct net_device_stats *benet_get_stats(struct net_device *dev)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(dev);
|
|
struct be_adapter *adapter = pnob->adapter;
|
|
u64 pa;
|
|
struct be_timer_ctxt *ctxt = &adapter->timer_ctxt;
|
|
|
|
if (adapter->dev_state != BE_DEV_STATE_OPEN) {
|
|
/* Return previously read stats */
|
|
return &(adapter->benet_stats);
|
|
}
|
|
/* Get Physical Addr */
|
|
pa = pci_map_single(adapter->pdev, adapter->eth_statsp,
|
|
sizeof(struct FWCMD_ETH_GET_STATISTICS),
|
|
PCI_DMA_FROMDEVICE);
|
|
ctxt->get_stat_sem_addr = (unsigned long)&adapter->get_eth_stat_sem;
|
|
atomic_inc(&ctxt->get_stat_flag);
|
|
|
|
be_rxf_query_eth_statistics(&pnob->fn_obj, adapter->eth_statsp,
|
|
cpu_to_le64(pa), be_get_stat_cb, ctxt,
|
|
NULL);
|
|
|
|
ctxt->get_stats_timer.data = (unsigned long)ctxt;
|
|
mod_timer(&ctxt->get_stats_timer, (jiffies + (HZ * 2)));
|
|
down((void *)ctxt->get_stat_sem_addr); /* callback will unblock us */
|
|
|
|
/* Adding port0 and port1 stats. */
|
|
adapter->benet_stats.rx_packets =
|
|
adapter->eth_statsp->params.response.p0recvdtotalframes +
|
|
adapter->eth_statsp->params.response.p1recvdtotalframes;
|
|
adapter->benet_stats.tx_packets =
|
|
adapter->eth_statsp->params.response.p0xmitunicastframes +
|
|
adapter->eth_statsp->params.response.p1xmitunicastframes;
|
|
adapter->benet_stats.tx_bytes =
|
|
adapter->eth_statsp->params.response.p0xmitbyteslsd +
|
|
adapter->eth_statsp->params.response.p1xmitbyteslsd;
|
|
adapter->benet_stats.rx_errors =
|
|
adapter->eth_statsp->params.response.p0crcerrors +
|
|
adapter->eth_statsp->params.response.p1crcerrors;
|
|
adapter->benet_stats.rx_errors +=
|
|
adapter->eth_statsp->params.response.p0alignmentsymerrs +
|
|
adapter->eth_statsp->params.response.p1alignmentsymerrs;
|
|
adapter->benet_stats.rx_errors +=
|
|
adapter->eth_statsp->params.response.p0inrangelenerrors +
|
|
adapter->eth_statsp->params.response.p1inrangelenerrors;
|
|
adapter->benet_stats.rx_bytes =
|
|
adapter->eth_statsp->params.response.p0recvdtotalbytesLSD +
|
|
adapter->eth_statsp->params.response.p1recvdtotalbytesLSD;
|
|
adapter->benet_stats.rx_crc_errors =
|
|
adapter->eth_statsp->params.response.p0crcerrors +
|
|
adapter->eth_statsp->params.response.p1crcerrors;
|
|
|
|
adapter->benet_stats.tx_packets +=
|
|
adapter->eth_statsp->params.response.p0xmitmulticastframes +
|
|
adapter->eth_statsp->params.response.p1xmitmulticastframes;
|
|
adapter->benet_stats.tx_packets +=
|
|
adapter->eth_statsp->params.response.p0xmitbroadcastframes +
|
|
adapter->eth_statsp->params.response.p1xmitbroadcastframes;
|
|
adapter->benet_stats.tx_errors = 0;
|
|
|
|
adapter->benet_stats.multicast =
|
|
adapter->eth_statsp->params.response.p0xmitmulticastframes +
|
|
adapter->eth_statsp->params.response.p1xmitmulticastframes;
|
|
|
|
adapter->benet_stats.rx_fifo_errors =
|
|
adapter->eth_statsp->params.response.p0rxfifooverflowdropped +
|
|
adapter->eth_statsp->params.response.p1rxfifooverflowdropped;
|
|
adapter->benet_stats.rx_frame_errors =
|
|
adapter->eth_statsp->params.response.p0alignmentsymerrs +
|
|
adapter->eth_statsp->params.response.p1alignmentsymerrs;
|
|
adapter->benet_stats.rx_length_errors =
|
|
adapter->eth_statsp->params.response.p0inrangelenerrors +
|
|
adapter->eth_statsp->params.response.p1inrangelenerrors;
|
|
adapter->benet_stats.rx_length_errors +=
|
|
adapter->eth_statsp->params.response.p0outrangeerrors +
|
|
adapter->eth_statsp->params.response.p1outrangeerrors;
|
|
adapter->benet_stats.rx_length_errors +=
|
|
adapter->eth_statsp->params.response.p0frametoolongerrors +
|
|
adapter->eth_statsp->params.response.p1frametoolongerrors;
|
|
|
|
pci_unmap_single(adapter->pdev, (ulong) adapter->eth_statsp,
|
|
sizeof(struct FWCMD_ETH_GET_STATISTICS),
|
|
PCI_DMA_FROMDEVICE);
|
|
return &(adapter->benet_stats);
|
|
|
|
}
|
|
|
|
static void be_start_tx(struct be_net_object *pnob, u32 nposted)
|
|
{
|
|
#define CSR_ETH_MAX_SQPOSTS 255
|
|
struct SQ_DB_AMAP sqdb;
|
|
|
|
sqdb.dw[0] = 0;
|
|
|
|
AMAP_SET_BITS_PTR(SQ_DB, cid, &sqdb, pnob->tx_q_id);
|
|
while (nposted) {
|
|
if (nposted > CSR_ETH_MAX_SQPOSTS) {
|
|
AMAP_SET_BITS_PTR(SQ_DB, numPosted, &sqdb,
|
|
CSR_ETH_MAX_SQPOSTS);
|
|
nposted -= CSR_ETH_MAX_SQPOSTS;
|
|
} else {
|
|
AMAP_SET_BITS_PTR(SQ_DB, numPosted, &sqdb, nposted);
|
|
nposted = 0;
|
|
}
|
|
PD_WRITE(&pnob->fn_obj, etx_sq_db, sqdb.dw[0]);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void update_tx_rate(struct be_adapter *adapter)
|
|
{
|
|
/* update the rate once in two seconds */
|
|
if ((jiffies - adapter->eth_tx_jiffies) > 2 * (HZ)) {
|
|
u32 r;
|
|
r = adapter->eth_tx_bytes /
|
|
((jiffies - adapter->eth_tx_jiffies) / (HZ));
|
|
r = (r / 1000000); /* M bytes/s */
|
|
adapter->be_stat.bes_eth_tx_rate = (r * 8); /* M bits/s */
|
|
adapter->eth_tx_jiffies = jiffies;
|
|
adapter->eth_tx_bytes = 0;
|
|
}
|
|
}
|
|
|
|
static int wrb_cnt_in_skb(struct sk_buff *skb)
|
|
{
|
|
int cnt = 0;
|
|
while (skb) {
|
|
if (skb->len > skb->data_len)
|
|
cnt++;
|
|
cnt += skb_shinfo(skb)->nr_frags;
|
|
skb = skb_shinfo(skb)->frag_list;
|
|
}
|
|
BUG_ON(cnt > BE_MAX_TX_FRAG_COUNT);
|
|
return cnt;
|
|
}
|
|
|
|
static void wrb_fill(struct ETH_WRB_AMAP *wrb, u64 addr, int len)
|
|
{
|
|
AMAP_SET_BITS_PTR(ETH_WRB, frag_pa_hi, wrb, addr >> 32);
|
|
AMAP_SET_BITS_PTR(ETH_WRB, frag_pa_lo, wrb, addr & 0xFFFFFFFF);
|
|
AMAP_SET_BITS_PTR(ETH_WRB, frag_len, wrb, len);
|
|
}
|
|
|
|
static void wrb_fill_extra(struct ETH_WRB_AMAP *wrb, struct sk_buff *skb,
|
|
struct be_net_object *pnob)
|
|
{
|
|
wrb->dw[2] = 0;
|
|
wrb->dw[3] = 0;
|
|
AMAP_SET_BITS_PTR(ETH_WRB, crc, wrb, 1);
|
|
if (skb_shinfo(skb)->gso_segs > 1 && skb_shinfo(skb)->gso_size) {
|
|
AMAP_SET_BITS_PTR(ETH_WRB, lso, wrb, 1);
|
|
AMAP_SET_BITS_PTR(ETH_WRB, lso_mss, wrb,
|
|
skb_shinfo(skb)->gso_size);
|
|
} else if (skb->ip_summed == CHECKSUM_PARTIAL) {
|
|
u8 proto = ((struct iphdr *)ip_hdr(skb))->protocol;
|
|
if (proto == IPPROTO_TCP)
|
|
AMAP_SET_BITS_PTR(ETH_WRB, tcpcs, wrb, 1);
|
|
else if (proto == IPPROTO_UDP)
|
|
AMAP_SET_BITS_PTR(ETH_WRB, udpcs, wrb, 1);
|
|
}
|
|
if (pnob->vlan_grp && vlan_tx_tag_present(skb)) {
|
|
AMAP_SET_BITS_PTR(ETH_WRB, vlan, wrb, 1);
|
|
AMAP_SET_BITS_PTR(ETH_WRB, vlan_tag, wrb, vlan_tx_tag_get(skb));
|
|
}
|
|
}
|
|
|
|
static inline void wrb_copy_extra(struct ETH_WRB_AMAP *to,
|
|
struct ETH_WRB_AMAP *from)
|
|
{
|
|
|
|
to->dw[2] = from->dw[2];
|
|
to->dw[3] = from->dw[3];
|
|
}
|
|
|
|
/* Returns the actual count of wrbs used including a possible dummy */
|
|
static int copy_skb_to_txq(struct be_net_object *pnob, struct sk_buff *skb,
|
|
u32 wrb_cnt, u32 *copied)
|
|
{
|
|
u64 busaddr;
|
|
struct ETH_WRB_AMAP *wrb = NULL, *first = NULL;
|
|
u32 i;
|
|
bool dummy = true;
|
|
struct pci_dev *pdev = pnob->adapter->pdev;
|
|
|
|
if (wrb_cnt & 1)
|
|
wrb_cnt++;
|
|
else
|
|
dummy = false;
|
|
|
|
atomic_add(wrb_cnt, &pnob->tx_q_used);
|
|
|
|
while (skb) {
|
|
if (skb->len > skb->data_len) {
|
|
int len = skb->len - skb->data_len;
|
|
busaddr = pci_map_single(pdev, skb->data, len,
|
|
PCI_DMA_TODEVICE);
|
|
busaddr = cpu_to_le64(busaddr);
|
|
wrb = &pnob->tx_q[pnob->tx_q_hd];
|
|
if (first == NULL) {
|
|
wrb_fill_extra(wrb, skb, pnob);
|
|
first = wrb;
|
|
} else {
|
|
wrb_copy_extra(wrb, first);
|
|
}
|
|
wrb_fill(wrb, busaddr, len);
|
|
be_adv_txq_hd(pnob);
|
|
*copied += len;
|
|
}
|
|
|
|
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
|
|
struct skb_frag_struct *frag =
|
|
&skb_shinfo(skb)->frags[i];
|
|
busaddr = pci_map_page(pdev, frag->page,
|
|
frag->page_offset, frag->size,
|
|
PCI_DMA_TODEVICE);
|
|
busaddr = cpu_to_le64(busaddr);
|
|
wrb = &pnob->tx_q[pnob->tx_q_hd];
|
|
if (first == NULL) {
|
|
wrb_fill_extra(wrb, skb, pnob);
|
|
first = wrb;
|
|
} else {
|
|
wrb_copy_extra(wrb, first);
|
|
}
|
|
wrb_fill(wrb, busaddr, frag->size);
|
|
be_adv_txq_hd(pnob);
|
|
*copied += frag->size;
|
|
}
|
|
skb = skb_shinfo(skb)->frag_list;
|
|
}
|
|
|
|
if (dummy) {
|
|
wrb = &pnob->tx_q[pnob->tx_q_hd];
|
|
BUG_ON(first == NULL);
|
|
wrb_copy_extra(wrb, first);
|
|
wrb_fill(wrb, 0, 0);
|
|
be_adv_txq_hd(pnob);
|
|
}
|
|
AMAP_SET_BITS_PTR(ETH_WRB, complete, wrb, 1);
|
|
AMAP_SET_BITS_PTR(ETH_WRB, last, wrb, 1);
|
|
return wrb_cnt;
|
|
}
|
|
|
|
/* For each skb transmitted, tx_ctxt stores the num of wrbs in the
|
|
* start index and skb pointer in the end index
|
|
*/
|
|
static inline void be_tx_wrb_info_remember(struct be_net_object *pnob,
|
|
struct sk_buff *skb, int wrb_cnt,
|
|
u32 start)
|
|
{
|
|
*(u32 *) (&pnob->tx_ctxt[start]) = wrb_cnt;
|
|
index_adv(&start, wrb_cnt - 1, pnob->tx_q_len);
|
|
pnob->tx_ctxt[start] = skb;
|
|
}
|
|
|
|
static int benet_xmit(struct sk_buff *skb, struct net_device *netdev)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
struct be_adapter *adapter = pnob->adapter;
|
|
u32 wrb_cnt, copied = 0;
|
|
u32 start = pnob->tx_q_hd;
|
|
|
|
adapter->be_stat.bes_tx_reqs++;
|
|
|
|
wrb_cnt = wrb_cnt_in_skb(skb);
|
|
spin_lock_bh(&adapter->txq_lock);
|
|
if ((pnob->tx_q_len - 2 - atomic_read(&pnob->tx_q_used)) <= wrb_cnt) {
|
|
netif_stop_queue(pnob->netdev);
|
|
spin_unlock_bh(&adapter->txq_lock);
|
|
adapter->be_stat.bes_tx_fails++;
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
spin_unlock_bh(&adapter->txq_lock);
|
|
|
|
wrb_cnt = copy_skb_to_txq(pnob, skb, wrb_cnt, &copied);
|
|
be_tx_wrb_info_remember(pnob, skb, wrb_cnt, start);
|
|
|
|
be_start_tx(pnob, wrb_cnt);
|
|
|
|
adapter->eth_tx_bytes += copied;
|
|
adapter->be_stat.bes_tx_wrbs += wrb_cnt;
|
|
update_tx_rate(adapter);
|
|
netdev->trans_start = jiffies;
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
/*
|
|
* This is the driver entry point to change the mtu of the device
|
|
* Returns 0 for success and errno for failure.
|
|
*/
|
|
static int benet_change_mtu(struct net_device *netdev, int new_mtu)
|
|
{
|
|
/*
|
|
* BE supports jumbo frame size upto 9000 bytes including the link layer
|
|
* header. Considering the different variants of frame formats possible
|
|
* like VLAN, SNAP/LLC, the maximum possible value for MTU is 8974 bytes
|
|
*/
|
|
|
|
if (new_mtu < (ETH_ZLEN + ETH_FCS_LEN) || (new_mtu > BE_MAX_MTU)) {
|
|
dev_info(&netdev->dev, "Invalid MTU requested. "
|
|
"Must be between %d and %d bytes\n",
|
|
(ETH_ZLEN + ETH_FCS_LEN), BE_MAX_MTU);
|
|
return -EINVAL;
|
|
}
|
|
dev_info(&netdev->dev, "MTU changed from %d to %d\n",
|
|
netdev->mtu, new_mtu);
|
|
netdev->mtu = new_mtu;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is the driver entry point to register a vlan with the device
|
|
*/
|
|
static void benet_vlan_register(struct net_device *netdev,
|
|
struct vlan_group *grp)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
|
|
be_disable_eq_intr(pnob);
|
|
pnob->vlan_grp = grp;
|
|
pnob->num_vlans = 0;
|
|
be_enable_eq_intr(pnob);
|
|
}
|
|
|
|
/*
|
|
* This is the driver entry point to add a vlan vlan_id
|
|
* with the device netdev
|
|
*/
|
|
static void benet_vlan_add_vid(struct net_device *netdev, u16 vlan_id)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
|
|
if (pnob->num_vlans == (BE_NUM_VLAN_SUPPORTED - 1)) {
|
|
/* no way to return an error */
|
|
dev_info(&netdev->dev,
|
|
"BladeEngine: Cannot configure more than %d Vlans\n",
|
|
BE_NUM_VLAN_SUPPORTED);
|
|
return;
|
|
}
|
|
/* The new vlan tag will be in the slot indicated by num_vlans. */
|
|
pnob->vlan_tag[pnob->num_vlans++] = vlan_id;
|
|
be_rxf_vlan_config(&pnob->fn_obj, false, pnob->num_vlans,
|
|
pnob->vlan_tag, NULL, NULL, NULL);
|
|
}
|
|
|
|
/*
|
|
* This is the driver entry point to remove a vlan vlan_id
|
|
* with the device netdev
|
|
*/
|
|
static void benet_vlan_rem_vid(struct net_device *netdev, u16 vlan_id)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
|
|
u32 i, value;
|
|
|
|
/*
|
|
* In Blade Engine, we support 32 vlan tag filters across both ports.
|
|
* To program a vlan tag, the RXF_RTPR_CSR register is used.
|
|
* Each 32-bit value of RXF_RTDR_CSR can address 2 vlan tag entries.
|
|
* The Vlan table is of depth 16. thus we support 32 tags.
|
|
*/
|
|
|
|
value = vlan_id | VLAN_VALID_BIT;
|
|
for (i = 0; i < BE_NUM_VLAN_SUPPORTED; i++) {
|
|
if (pnob->vlan_tag[i] == vlan_id)
|
|
break;
|
|
}
|
|
|
|
if (i == BE_NUM_VLAN_SUPPORTED)
|
|
return;
|
|
/* Now compact the vlan tag array by removing hole created. */
|
|
while ((i + 1) < BE_NUM_VLAN_SUPPORTED) {
|
|
pnob->vlan_tag[i] = pnob->vlan_tag[i + 1];
|
|
i++;
|
|
}
|
|
if ((i + 1) == BE_NUM_VLAN_SUPPORTED)
|
|
pnob->vlan_tag[i] = (u16) 0x0;
|
|
pnob->num_vlans--;
|
|
be_rxf_vlan_config(&pnob->fn_obj, false, pnob->num_vlans,
|
|
pnob->vlan_tag, NULL, NULL, NULL);
|
|
}
|
|
|
|
/*
|
|
* This function is called to program multicast
|
|
* address in the multicast filter of the ASIC.
|
|
*/
|
|
static void be_set_multicast_filter(struct net_device *netdev)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
struct dev_mc_list *mc_ptr;
|
|
u8 mac_addr[32][ETH_ALEN];
|
|
int i;
|
|
|
|
if (netdev->flags & IFF_ALLMULTI) {
|
|
/* set BE in Multicast promiscuous */
|
|
be_rxf_multicast_config(&pnob->fn_obj, true, 0, NULL, NULL,
|
|
NULL, NULL);
|
|
return;
|
|
}
|
|
|
|
for (mc_ptr = netdev->mc_list, i = 0; mc_ptr;
|
|
mc_ptr = mc_ptr->next, i++) {
|
|
memcpy(&mac_addr[i][0], mc_ptr->dmi_addr, ETH_ALEN);
|
|
}
|
|
|
|
/* reset the promiscuous mode also. */
|
|
be_rxf_multicast_config(&pnob->fn_obj, false, i,
|
|
&mac_addr[0][0], NULL, NULL, NULL);
|
|
}
|
|
|
|
/*
|
|
* This is the driver entry point to set multicast list
|
|
* with the device netdev. This function will be used to
|
|
* set promiscuous mode or multicast promiscuous mode
|
|
* or multicast mode....
|
|
*/
|
|
static void benet_set_multicast_list(struct net_device *netdev)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
|
|
if (netdev->flags & IFF_PROMISC) {
|
|
be_rxf_promiscuous(&pnob->fn_obj, 1, 1, NULL, NULL, NULL);
|
|
} else {
|
|
be_rxf_promiscuous(&pnob->fn_obj, 0, 0, NULL, NULL, NULL);
|
|
be_set_multicast_filter(netdev);
|
|
}
|
|
}
|
|
|
|
int benet_init(struct net_device *netdev)
|
|
{
|
|
struct be_net_object *pnob = netdev_priv(netdev);
|
|
struct be_adapter *adapter = pnob->adapter;
|
|
|
|
ether_setup(netdev);
|
|
|
|
netdev->open = &benet_open;
|
|
netdev->stop = &benet_close;
|
|
netdev->hard_start_xmit = &benet_xmit;
|
|
|
|
netdev->get_stats = &benet_get_stats;
|
|
|
|
netdev->set_multicast_list = &benet_set_multicast_list;
|
|
|
|
netdev->change_mtu = &benet_change_mtu;
|
|
netdev->set_mac_address = &benet_set_mac_addr;
|
|
|
|
netdev->vlan_rx_register = benet_vlan_register;
|
|
netdev->vlan_rx_add_vid = benet_vlan_add_vid;
|
|
netdev->vlan_rx_kill_vid = benet_vlan_rem_vid;
|
|
|
|
netdev->features =
|
|
NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_HW_VLAN_RX | NETIF_F_TSO |
|
|
NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_FILTER | NETIF_F_IP_CSUM;
|
|
|
|
netdev->flags |= IFF_MULTICAST;
|
|
|
|
/* If device is DAC Capable, set the HIGHDMA flag for netdevice. */
|
|
if (adapter->dma_64bit_cap)
|
|
netdev->features |= NETIF_F_HIGHDMA;
|
|
|
|
SET_ETHTOOL_OPS(netdev, &be_ethtool_ops);
|
|
return 0;
|
|
}
|