blob: 96a927576adbe9a9afcc4317fe4da3dedde01193 [file] [log] [blame]
/**
* Copyright (c) 2011-2012 Quantenna Communications, Inc.
* 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
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <asm/system.h>
#include <qtn/dmautil.h>
#include <drivers/ruby/dma_cache_ops.h>
#include "topaz_test.h"
#include <qtn/topaz_fwt_sw.h>
#include <qtn/topaz_fwt_db.h>
#include <qtn/topaz_tqe_cpuif.h>
#include <qtn/topaz_tqe.h>
#include <qtn/topaz_hbm_cpuif.h>
#include <qtn/topaz_hbm.h>
#include <qtn/topaz_fwt.h>
#include <qtn/topaz_vlan_cpuif.h>
#include "net80211/ieee80211.h"
#include "net80211/if_ethersubr.h"
#include <qtn/qtn_net_packet.h>
#include <qtn/qdrv_sch.h>
#include <qtn/topaz_congest_queue.h>
#include <qtn/qtn_wowlan.h>
#include <qtn/iputil.h>
#include <qtn/mproc_sync.h>
#include <qtn/qtn_vlan.h>
int g_dscp_flag = 0;
int g_dscp_value[2];
uint16_t g_wowlan_host_state = 0;
uint16_t g_wowlan_match_type = 0;
uint16_t g_wowlan_l2_ether_type = 0x0842;
uint16_t g_wowlan_l3_udp_port = 0xffff;
uint8_t g_l2_ext_filter = 0;
uint8_t g_l2_ext_filter_port = TOPAZ_TQE_NUM_PORTS;
EXPORT_SYMBOL(g_l2_ext_filter);
EXPORT_SYMBOL(g_l2_ext_filter_port);
EXPORT_SYMBOL(g_wowlan_host_state);
EXPORT_SYMBOL(g_wowlan_match_type);
EXPORT_SYMBOL(g_wowlan_l2_ether_type);
EXPORT_SYMBOL(g_wowlan_l3_udp_port);
EXPORT_SYMBOL(g_dscp_flag);
EXPORT_SYMBOL(g_dscp_value);
int tqe_sem_en = 0;
module_param(tqe_sem_en, int, S_IRWXU);
struct tqe_netdev_priv {
struct napi_struct napi;
struct net_device_stats stats;
struct topaz_congest_queue *congest_queue;
ALIGNED_DMA_DESC(union, topaz_tqe_cpuif_descr) rx;
};
static tqe_fwt_get_mcast_hook g_tqe_fwt_get_mcast_hook = NULL;
static tqe_fwt_get_mcast_ff_hook g_tqe_fwt_get_mcast_ff_hook = NULL;
static tqe_fwt_get_ucast_hook g_tqe_fwt_get_ucast_hook = NULL;
static tqe_fwt_false_miss_hook g_tqe_fwt_false_miss_hook = NULL;
static tqe_fwt_get_from_mac_hook g_tqe_fwt_get_from_mac_hook = NULL;
static tqe_mac_reserved_hook g_tqe_mac_reserved_hook = NULL;
static inline void __sram_text tqe_rx_pkt_drop(const union topaz_tqe_cpuif_descr *desc);
static struct {
tqe_port_handler handler;
void *token;
int32_t group;
} tqe_port_handlers[TOPAZ_TQE_NUM_PORTS];
int tqe_port_add_handler(enum topaz_tqe_port port, tqe_port_handler handler, void *token)
{
if (port >= TOPAZ_TQE_NUM_PORTS || !handler) {
return -EINVAL;
}
tqe_port_handlers[port].handler = handler;
tqe_port_handlers[port].token = token;
return 0;
}
EXPORT_SYMBOL(tqe_port_add_handler);
void tqe_port_remove_handler(enum topaz_tqe_port port)
{
if (port >= TOPAZ_TQE_NUM_PORTS || !tqe_port_handlers[port].handler) {
printk(KERN_ERR "%s: invalid port %u\n", __FUNCTION__, port);
return;
}
tqe_port_handlers[port].handler = NULL;
tqe_port_handlers[port].token = NULL;
}
EXPORT_SYMBOL(tqe_port_remove_handler);
static void tqe_port_set(const enum topaz_tqe_port port, const uint8_t enable)
{
struct topaz_fwt_sw_mcast_entry *mcast_ent;
int i;
if (!g_tqe_fwt_get_mcast_ff_hook) {
return;
}
for (i = 0; i < TOPAZ_FWT_MCAST_FF_ENTRIES; i++) {
mcast_ent = g_tqe_fwt_get_mcast_ff_hook(i);
if (!mcast_ent) {
return;
}
if (enable) {
topaz_fwt_sw_mcast_port_set(mcast_ent, port);
} else {
topaz_fwt_sw_mcast_port_clear(mcast_ent, port);
}
topaz_fwt_sw_mcast_flush(mcast_ent);
}
}
void tqe_port_set_group(const enum topaz_tqe_port port, int32_t group)
{
if ((port < TOPAZ_TQE_NUM_PORTS) && (group > 0))
tqe_port_handlers[port].group = group;
}
EXPORT_SYMBOL(tqe_port_set_group);
void tqe_port_clear_group(const enum topaz_tqe_port port)
{
if (port < TOPAZ_TQE_NUM_PORTS)
tqe_port_handlers[port].group = 0;
}
EXPORT_SYMBOL(tqe_port_clear_group);
void tqe_port_register(const enum topaz_tqe_port port)
{
tqe_port_set(port, 1);
}
EXPORT_SYMBOL(tqe_port_register);
void tqe_port_unregister(const enum topaz_tqe_port port)
{
tqe_port_set(port, 0);
}
EXPORT_SYMBOL(tqe_port_unregister);
struct update_multicast_tx_stats {
void (*fn)(void *ctx, uint8_t node);
void *ctx;
};
struct update_multicast_tx_stats update_multicast;
void tqe_reg_multicast_tx_stats(void (*fn)(void *ctx, uint8_t), void *ctx)
{
update_multicast.fn = fn;
update_multicast.ctx = ctx;
}
EXPORT_SYMBOL(tqe_reg_multicast_tx_stats);
#if defined(CONFIG_ARCH_TOPAZ_SWITCH_TEST) || defined(CONFIG_ARCH_TOPAZ_SWITCH_TEST_MODULE)
static void topaz_tqe_test_ctrl(const uint8_t *buff_virt_rx)
{
const uint8_t ctrl_dstmac[ETH_ALEN] = TOPAZ_TEST_CTRL_DSTMAC;
const uint8_t ctrl_srcmac[ETH_ALEN] = TOPAZ_TEST_CTRL_SRCMAC;
if (memcmp(&buff_virt_rx[ETH_ALEN * 0], ctrl_dstmac, ETH_ALEN) == 0 &&
memcmp(&buff_virt_rx[ETH_ALEN * 1], ctrl_srcmac, ETH_ALEN) == 0) {
const char *test_str = (const char *)&buff_virt_rx[128];
unsigned long len;
char *cmd = NULL;
char **words = NULL;
int rc;
int word_count;
int (*parse)(int, char**) = NULL;
len = strlen(test_str);
cmd = kmalloc(len + 1, GFP_KERNEL);
words = kmalloc(len * sizeof(char *) / 2, GFP_KERNEL);
if (!cmd || !words) {
rc = -ENOMEM;
goto out;
}
strcpy(cmd, test_str);
word_count = topaz_test_split_words(words, cmd);
if (strcmp(words[0], "dpi_test") == 0) {
parse = &topaz_dpi_test_parse;
} else if (strcmp(words[0], "fwt_test") == 0) {
parse = &topaz_fwt_test_parse;
} else if (strcmp(words[0], "ipprt_emac0") == 0) {
parse = &topaz_ipprt_emac0_test_parse;
} else if (strcmp(words[0], "ipprt_emac1") == 0) {
parse = &topaz_ipprt_emac1_test_parse;
} else if (strcmp(words[0], "vlan_test") == 0) {
parse = &topaz_vlan_test_parse;
} else {
printk("%s: invalid ctrl packet\n", __FUNCTION__);
}
if (parse) {
rc = parse(word_count - 1, words + 1);
printk("%s: rc %d '%s'\n", __FUNCTION__, rc, test_str);
}
out:
if (cmd)
kfree(cmd);
if (words)
kfree(words);
}
}
#endif
uint32_t
switch_tqe_multi_proc_sem_down(char * funcname, int linenum)
{
uint32_t prtcnt;
if (tqe_sem_en == 0)
return 1;
prtcnt = 0;
while (_qtn_mproc_3way_tqe_sem_down(TOPAZ_MPROC_TQE_SEM_LHOST) == 0) {
if ((prtcnt & 0xff) == 0)
printk("%s line %d fail to get tqe semaphore\n", funcname, linenum);
prtcnt++;
}
return 1;
}
EXPORT_SYMBOL(switch_tqe_multi_proc_sem_down);
uint32_t
switch_tqe_multi_proc_sem_up(void)
{
if (tqe_sem_en == 0)
return 1;
if (_qtn_mproc_3way_tqe_sem_up(TOPAZ_MPROC_TQE_SEM_LHOST)) {
return 1;
} else {
WARN_ONCE(1, "%s failed to relese HW semaphore\n", __func__);
return 0;
}
}
EXPORT_SYMBOL(switch_tqe_multi_proc_sem_up);
static void tqe_buf_set_refcounts(void *buf_start, int32_t enqueue, int32_t free)
{
uint32_t *p = buf_start;
uint32_t *_m = topaz_hbm_buf_get_meta(p);
uint32_t *enqueuep = _m - HBM_HR_OFFSET_ENQ_CNT;
uint32_t *freep = _m - HBM_HR_OFFSET_FREE_CNT;
if (enqueue >= 0)
arc_write_uncached_32(enqueuep, enqueue);
if (free >= 0)
arc_write_uncached_32(freep, free);
}
int topaz_tqe_xmit(union topaz_tqe_cpuif_ppctl *pp_cntl)
{
int num = topaz_tqe_cpuif_port_to_num(TOPAZ_TQE_LOCAL_CPU);
topaz_tqe_wait();
switch_tqe_multi_proc_sem_down("topaz_tqe_xmit",__LINE__);
topaz_tqe_cpuif_tx_start(pp_cntl);
switch_tqe_multi_proc_sem_up();
wmb();
topaz_tqe_wait();
if ((qtn_mproc_sync_mem_read(TOPAZ_TQE_CPUIF_TXSTART(num)) &
TOPAZ_TQE_CPUIF_TX_START_NOT_SUCCESS))
return NET_XMIT_CN;
else
return NET_XMIT_SUCCESS;
}
void topaz_tqe_congest_queue_process(const union topaz_tqe_cpuif_descr *desc,
void *queue, uint8_t node, uint8_t tqe_tid,
union topaz_tqe_cpuif_ppctl *ppctl, uint8_t is_unicast)
{
struct topaz_congest_queue *congest_queue = (struct topaz_congest_queue *)queue;
struct topaz_congest_q_desc *q_desc;
int8_t re_sched = 0;
int8_t ret = 0;
if (topaz_queue_congested(congest_queue, node, tqe_tid)) {
q_desc = topaz_get_congest_queue(congest_queue, node, tqe_tid);
ret = topaz_congest_enqueue(q_desc, ppctl);
if (ret == NET_XMIT_CN) {
topaz_hbm_congest_queue_put_buf(ppctl);
}
re_sched = topaz_congest_queue_xmit(q_desc, TOPAZ_SOFTIRQ_BUDGET);
if (re_sched)
tasklet_schedule(&congest_queue->congest_tx);
} else {
ret = congest_queue->xmit_func(ppctl);
if (unlikely(ret != NET_XMIT_SUCCESS)) {
if (is_unicast)
q_desc = topaz_congest_alloc_unicast_queue(congest_queue,
node, tqe_tid);
else
q_desc = topaz_congest_alloc_queue(congest_queue, node, tqe_tid);
if (!q_desc) {
topaz_hbm_congest_queue_put_buf(ppctl);
} else {
ret = topaz_congest_enqueue(q_desc, ppctl);
if (ret == NET_XMIT_CN) {
topaz_hbm_congest_queue_put_buf(ppctl);
} else {
tasklet_schedule(&congest_queue->congest_tx);
}
}
}
}
}
/*
* Push a packet to the TQE
*/
static void __sram_text tqe_push_mcast(const void *token1, void *token2,
uint8_t port, uint8_t node, uint8_t tid)
{
const union topaz_tqe_cpuif_descr *desc = token1;
union topaz_tqe_cpuif_ppctl ppctl;
const uint8_t portal = 1;
const uint16_t misc_user = 0;
void *queue = token2;
uint8_t tqe_free = queue ? 0 : 1;
struct qtn_vlan_dev *vdev;
/*
* VLAN egress check -- multicast forwarding
* for out_port==WMAC, AuC handles egress
*/
if (vlan_enabled && TOPAZ_TQE_PORT_IS_EMAC(port)) {
vdev = vport_tbl_lhost[port];
if (!qtn_vlan_egress(vdev, 0, bus_to_virt((uintptr_t)desc->data.pkt))) {
tqe_rx_pkt_drop(desc);
return;
}
}
topaz_tqe_cpuif_ppctl_init(&ppctl,
port, &node, 1, tid,
portal, 1, TOPAZ_HBM_EMAC_TX_DONE_POOL, tqe_free, misc_user);
ppctl.data.pkt = desc->data.pkt;
ppctl.data.length = desc->data.length;
ppctl.data.buff_ptr_offset = desc->data.buff_ptr_offset;
if (queue) {
topaz_tqe_congest_queue_process(desc, queue, node, tid, &ppctl, 0);
} else {
topaz_tqe_wait();
switch_tqe_multi_proc_sem_down("tqe_push_mcast",__LINE__);
topaz_tqe_cpuif_tx_start(&ppctl);
switch_tqe_multi_proc_sem_up();
}
if (port == TOPAZ_TQE_WMAC_PORT && update_multicast.fn)
update_multicast.fn(update_multicast.ctx, node);
}
/*
* returns the number of TQE pushes; 0 means buffer is not consumed here
*/
static uint32_t __sram_text tqe_push_mc_ports(void *queue,
const struct topaz_fwt_sw_mcast_entry *mcast_ent_shared,
const union topaz_tqe_cpuif_descr *desc, uint8_t tid, uint8_t in_node,
uint32_t header_access_bytes)
{
struct topaz_fwt_sw_mcast_entry mcast_ent;
enum topaz_tqe_port in_port = desc->data.in_port;
uint32_t push_count;
uint32_t pushes = 0;
uint8_t port = TOPAZ_TQE_FIRST_PORT;
void *buf_bus_rx = desc->data.pkt;
void *buf_virt_rx = bus_to_virt((unsigned long) buf_bus_rx);
const struct ether_header *eh = buf_virt_rx;
int offset = desc->data.buff_ptr_offset;
mcast_ent = *mcast_ent_shared;
/* The MuC handles snooped multicast directly */
if (in_port == TOPAZ_TQE_WMAC_PORT || in_port == TOPAZ_TQE_MUC_PORT) {
if (printk_ratelimit())
printk("%s: mcast pkt from mac t=%04x d=%pM s=%pM\n", __FUNCTION__,
eh->ether_type, eh->ether_dhost, eh->ether_shost);
return 0;
}
/* find the expected enqueue count and set the HBM buffer reference count */
push_count = topaz_fwt_sw_mcast_enqueues(&mcast_ent, mcast_ent.port_bitmap,
in_port, in_node);
if (unlikely(!push_count)) {
return 0;
}
tqe_buf_set_refcounts((uint8_t *)buf_virt_rx + offset, push_count, 0);
flush_and_inv_dcache_range((unsigned long)buf_virt_rx, (unsigned long)(buf_virt_rx + header_access_bytes));
/* push this packet to the tqe for each port/node */
while (mcast_ent.port_bitmap) {
if (mcast_ent.port_bitmap & 0x1) {
if (topaz_fwt_sw_mcast_port_has_nodes(port)) {
pushes += topaz_fwt_sw_mcast_do_per_node(tqe_push_mcast,
&mcast_ent, desc, queue, in_node, port, tid);
} else {
if (port != in_port) {
tqe_push_mcast(desc, NULL, port, 0, 0);
++pushes;
}
}
}
mcast_ent.port_bitmap >>= 1;
port++;
}
if (unlikely(pushes != push_count)) {
printk(KERN_CRIT "%s: pushes %u push_count %u, buffer leak imminent\n",
__FUNCTION__, pushes, push_count);
}
return push_count;
}
int __sram_text tqe_rx_get_node_id(const struct ether_header *eh)
{
const struct fwt_db_entry *fwt_ent = NULL;
if (likely(g_tqe_fwt_get_ucast_hook)) {
fwt_ent = g_tqe_fwt_get_ucast_hook(eh->ether_shost, eh->ether_shost);
if (likely(fwt_ent) && fwt_ent->valid)
return fwt_ent->out_node;
}
return 0;
}
inline
const uint16_t *
tqe_rx_ether_type_skip_vlan(const struct ether_header *eh, uint32_t len)
{
const uint16_t *ether_type = &eh->ether_type;
if (len < sizeof(struct ether_header)) {
return NULL;
}
while(qtn_ether_type_is_vlan(*ether_type)) {
if (len < sizeof(struct ether_header) + VLAN_HLEN) {
return NULL;
}
ether_type += VLAN_HLEN / sizeof(*ether_type);
len -= VLAN_HLEN;
}
return ether_type;
}
int __sram_text tqe_rx_multicast(void *congest_queue, const union topaz_tqe_cpuif_descr *desc)
{
int timeout;
union topaz_fwt_lookup fwt_lu;
const struct topaz_fwt_sw_mcast_entry *mcast_ent = NULL;
const struct ether_header *eh = bus_to_virt((uintptr_t) desc->data.pkt);
const enum topaz_tqe_port in_port = desc->data.in_port;
const void *ipaddr = NULL;
uint8_t tid = 0;
const uint16_t *ether_type = tqe_rx_ether_type_skip_vlan(eh, desc->data.length);
const void *iphdr = NULL;
uint8_t vlan_index;
uint8_t in_node = 0;
uint32_t header_access_bytes = 0;
uint32_t ether_payload_length = 0;
uint8_t false_miss = 0;
if (unlikely(!ether_type)) {
printk(KERN_WARNING "%s: malformed packet in_port %u\n", __FUNCTION__, in_port);
return 0;
}
iphdr = ether_type + 1;
ether_payload_length = desc->data.length - ((char *)iphdr - (char *)eh);
header_access_bytes = iphdr - (const void *)eh;
/* FIXME: this won't work for 802.3 frames */
if (*ether_type == __constant_htons(ETH_P_IP)
&& iputil_mac_is_v4_multicast(eh->ether_dhost)
&& (ether_payload_length >= sizeof(struct qtn_ipv4))) {
const struct qtn_ipv4 *ipv4 = (const struct qtn_ipv4 *)iphdr;
/* do not accelerate IGMP */
if (ipv4->proto == QTN_IP_PROTO_IGMP) {
return 0;
}
ipaddr = &ipv4->dstip;
/* Option field doesn't take into account because they are not accessed */
header_access_bytes += sizeof (struct qtn_ipv4);
} else if (*ether_type == __constant_htons(ETH_P_IPV6)
&& iputil_mac_is_v6_multicast(eh->ether_dhost)
&& (ether_payload_length >= sizeof(struct qtn_ipv6))) {
const struct qtn_ipv6 *ipv6 = (const struct qtn_ipv6 *)iphdr;
ipaddr = &ipv6->dstip;
header_access_bytes += sizeof (struct qtn_ipv6);
}
if (ipaddr) {
topaz_tqe_vlan_gettid(desc->data.pkt, &tid, &vlan_index);
fwt_lu = topaz_fwt_hw_lookup_wait_be(eh->ether_dhost, &timeout, &false_miss);
if (fwt_lu.data.valid && !timeout) {
#ifndef TOPAZ_DISABLE_FWT_WAR
if (unlikely(false_miss && g_tqe_fwt_false_miss_hook))
g_tqe_fwt_false_miss_hook(fwt_lu.data.entry_addr, false_miss);
#endif
if (g_tqe_fwt_get_mcast_hook)
mcast_ent = g_tqe_fwt_get_mcast_hook(fwt_lu.data.entry_addr,
ipaddr, *ether_type, vlan_index);
if (mcast_ent) {
if ((mcast_ent->flood_forward) && (in_port == TOPAZ_TQE_MUC_PORT)) {
in_node = tqe_rx_get_node_id(eh);
if (in_node == 0)
return 0;
}
return tqe_push_mc_ports(congest_queue, mcast_ent, desc, tid,
in_node, header_access_bytes);
}
}
}
return 0;
}
EXPORT_SYMBOL(tqe_rx_multicast);
static inline void __sram_text tqe_rx_pkt_drop(const union topaz_tqe_cpuif_descr *desc)
{
void *buf_virt_rx = bus_to_virt((unsigned long) desc->data.pkt);
uint16_t buflen = desc->data.length;
const int8_t dest_pool = topaz_hbm_payload_get_pool_bus(desc->data.pkt);
void *buf_bus = topaz_hbm_payload_store_align_bus(desc->data.pkt, dest_pool, 0);
unsigned long flags;
cache_op_before_rx(buf_virt_rx, buflen, 0);
local_irq_save(flags);
topaz_hbm_filter_txdone_buf(buf_bus);
local_irq_restore(flags);
}
void tqe_register_mac_reserved_cbk(tqe_mac_reserved_hook cbk_func)
{
g_tqe_mac_reserved_hook = cbk_func;
}
EXPORT_SYMBOL(tqe_register_mac_reserved_cbk);
void tqe_register_ucastfwt_cbk(tqe_fwt_get_ucast_hook cbk_func)
{
g_tqe_fwt_get_ucast_hook = cbk_func;
}
EXPORT_SYMBOL(tqe_register_ucastfwt_cbk);
void tqe_register_macfwt_cbk(tqe_fwt_get_from_mac_hook cbk_func)
{
g_tqe_fwt_get_from_mac_hook = cbk_func;
}
EXPORT_SYMBOL(tqe_register_macfwt_cbk);
static int topaz_swfwd_tqe_xmit(const fwt_db_entry *fwt_ent,
const union topaz_tqe_cpuif_descr *desc,
void *queue)
{
uint8_t port;
uint8_t node;
uint16_t misc_user;
uint8_t tid = 0;
union topaz_tqe_cpuif_ppctl ctl;
uint8_t portal;
uint8_t vlan_index;
uint8_t tqe_free = queue ? 0 : 1;
struct qtn_vlan_dev *vdev;
if (fwt_ent->out_port == TOPAZ_TQE_LHOST_PORT)
return 0;
/*
* VLAN egress check -- unicast forwarding
* for out_port==WMAC, AuC handles egress
*/
if (vlan_enabled && TOPAZ_TQE_PORT_IS_EMAC(fwt_ent->out_port)) {
vdev = vport_tbl_lhost[fwt_ent->out_port];
if (!qtn_vlan_egress(vdev, 0, bus_to_virt((uintptr_t)desc->data.pkt))) {
tqe_rx_pkt_drop(desc);
return 1;
}
}
if (TOPAZ_TQE_PORT_IS_EMAC(desc->data.in_port)) {
if(g_dscp_flag){
tid = (g_dscp_value[desc->data.in_port] & 0xFF);
} else {
topaz_tqe_vlan_gettid(desc->data.pkt, &tid, &vlan_index);
}
} else {
topaz_tqe_vlan_gettid(desc->data.pkt, &tid, &vlan_index);
}
port = fwt_ent->out_port;
node = fwt_ent->out_node;
portal = fwt_ent->portal;
misc_user = 0;
topaz_tqe_cpuif_ppctl_init(&ctl,
port, &node, 1, tid,
portal, 1, 0, tqe_free, misc_user);
ctl.data.pkt = (void *)desc->data.pkt;
ctl.data.buff_ptr_offset = desc->data.buff_ptr_offset;
ctl.data.length = desc->data.length;
ctl.data.buff_pool_num = TOPAZ_HBM_EMAC_TX_DONE_POOL;
if (queue) {
topaz_tqe_congest_queue_process(desc, queue, node, tid, &ctl, 1);
} else {
while (topaz_tqe_cpuif_tx_nready());
switch_tqe_multi_proc_sem_down("topaz_swfwd_tqe_xmit",__LINE__);
topaz_tqe_cpuif_tx_start(&ctl);
switch_tqe_multi_proc_sem_up();
}
return 1;
}
static inline int topaz_is_dhcp(const struct iphdr *ipv4h)
{
const struct udphdr *udph;
if (ipv4h->protocol != IPPROTO_UDP)
return 0;
udph = (const struct udphdr *)((const uint8_t *)ipv4h + (ipv4h->ihl << 2));
if (udph->source == __constant_htons(DHCPSERVER_PORT)
&& udph->dest == __constant_htons(DHCPCLIENT_PORT))
return 1;
return 0;
}
#ifdef CONFIG_IPV6
static inline int topaz_ipv6_not_accel(const struct ipv6hdr *ipv6h, int seg_len)
{
uint8_t nexthdr;
const struct udphdr *udph;
const struct icmp6hdr *icmph;
int nhdr_off;
nhdr_off = iputil_v6_skip_exthdr(ipv6h, sizeof(struct ipv6hdr),
&nexthdr, seg_len, NULL, NULL);
if (nexthdr == IPPROTO_UDP) {
udph = (const struct udphdr *)((const uint8_t *)ipv6h + nhdr_off);
if (udph->source == __constant_htons(DHCPV6SERVER_PORT)
&& udph->dest == __constant_htons(DHCPV6CLIENT_PORT))
return 1;
} else if (nexthdr == IPPROTO_ICMPV6) {
icmph = (const struct icmp6hdr *)((const uint8_t *)ipv6h + nhdr_off);
if (icmph->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION
|| icmph->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT)
return 1;
}
return 0;
}
#endif
static int __sram_text tqe_rx_pktfwd(void *queue, const union topaz_tqe_cpuif_descr *desc)
{
enum topaz_tqe_port in_port = desc->data.in_port;
const struct fwt_db_entry *fwt_ent;
const struct ether_header *eh = bus_to_virt((uintptr_t) desc->data.pkt);
const struct vlan_ethhdr *vlan_hdr = (struct vlan_ethhdr *)eh;
const struct iphdr *ipv4h;
const struct ipv6hdr *ipv6h;
uint16_t ether_type;
uint16_t ether_hdrlen;
if (!TOPAZ_TQE_PORT_IS_EMAC(in_port))
return 0;
if (unlikely(iputil_eth_is_multicast(eh)))
return 0;
ether_type = eh->ether_type;
if (ether_type == __constant_htons(ETH_P_8021Q)) {
ether_type = vlan_hdr->h_vlan_encapsulated_proto;
ipv4h = (const struct iphdr *)(vlan_hdr + 1);
ipv6h = (const struct ipv6hdr *)(vlan_hdr + 1);
ether_hdrlen = sizeof(struct vlan_ethhdr);
} else {
ipv4h = (const struct iphdr *)(eh + 1);
ipv6h = (const struct ipv6hdr *)(eh + 1);
ether_hdrlen = sizeof(struct ether_header);
}
if (ether_type == __constant_htons(ETH_P_ARP))
return 0;
if (ether_type == __constant_htons(ETH_P_IP)
&& topaz_is_dhcp(ipv4h))
return 0;
#ifdef CONFIG_IPV6
if (ether_type == __constant_htons(ETH_P_IPV6)
&& topaz_ipv6_not_accel(ipv6h, desc->data.length - ether_hdrlen))
return 0;
#endif
if (unlikely(!g_tqe_fwt_get_ucast_hook))
return 0;
fwt_ent = g_tqe_fwt_get_ucast_hook(eh->ether_shost, eh->ether_dhost);
if (unlikely(!fwt_ent))
return 0;
/* Don't return to sender */
if (unlikely((in_port == fwt_ent->out_port) ||
((tqe_port_handlers[in_port].group > 0) &&
tqe_port_handlers[in_port].group ==
tqe_port_handlers[fwt_ent->out_port].group))) {
tqe_rx_pkt_drop(desc);
return 1;
}
return topaz_swfwd_tqe_xmit(fwt_ent, desc, queue);
}
int wowlan_magic_packet_check(const union topaz_tqe_cpuif_descr *desc)
{
const struct ether_header *eh = bus_to_virt((uintptr_t) desc->data.pkt);
const uint16_t *ether_type = NULL;
const void *iphdr = NULL;
uint32_t ether_payload_length = 0;
if (likely(!g_wowlan_host_state) ||
(desc->data.in_port != TOPAZ_TQE_MUC_PORT))
return 0;
ether_type = tqe_rx_ether_type_skip_vlan(eh, desc->data.length);
if (unlikely(!ether_type)) {
return 0;
}
iphdr = (void *)(ether_type + 1);
ether_payload_length = desc->data.length - ((char *)iphdr - (char *)eh);
if ((*ether_type == __constant_htons(ETH_P_IP))
&& (ether_payload_length < sizeof(struct iphdr))) {
return 0;
}
return wowlan_is_magic_packet(*ether_type, eh, iphdr,
g_wowlan_match_type,
g_wowlan_l2_ether_type,
g_wowlan_l3_udp_port);
}
static int tqe_rx_l2_ext_filter_handler(union topaz_tqe_cpuif_descr *desc, struct sk_buff *skb)
{
enum topaz_tqe_port in_port = desc->data.in_port;
const struct fwt_db_entry *fwt_ent;
const struct ether_header *eh = bus_to_virt((uintptr_t) desc->data.pkt);
if (in_port != g_l2_ext_filter_port)
return 0;
if (unlikely(!g_tqe_fwt_get_from_mac_hook))
return 0;
fwt_ent = g_tqe_fwt_get_from_mac_hook(eh->ether_shost);
if (unlikely(!fwt_ent))
return 0;
if (TOPAZ_TQE_PORT_IS_WMAC(fwt_ent->out_port)) {
/* Change the in port to prevent FWT updates */
desc->data.in_port = TOPAZ_TQE_MUC_PORT;
desc->data.misc_user = fwt_ent->out_node;
skb->ext_l2_filter = 1;
return 1;
}
return 0;
}
int __sram_text tqe_rx_l2_ext_filter(union topaz_tqe_cpuif_descr *desc, struct sk_buff *skb)
{
if (unlikely(g_l2_ext_filter))
return tqe_rx_l2_ext_filter_handler(desc, skb);
return 0;
}
EXPORT_SYMBOL(tqe_rx_l2_ext_filter);
void __sram_text tqe_rx_call_port_handler(union topaz_tqe_cpuif_descr *desc,
struct sk_buff *skb, uint8_t *whole_frm_hdr)
{
enum topaz_tqe_port in_port = desc->data.in_port;
tqe_port_handlers[in_port].handler(tqe_port_handlers[in_port].token,
desc, skb, whole_frm_hdr);
}
EXPORT_SYMBOL(tqe_rx_call_port_handler);
static int __sram_text tqe_rx_desc_handler(const struct tqe_netdev_priv *priv, union topaz_tqe_cpuif_descr *desc)
{
enum topaz_tqe_port in_port = desc->data.in_port;
void *buf_bus_rx = desc->data.pkt;
void *buf_virt_rx = bus_to_virt((unsigned long) buf_bus_rx);
uint16_t buflen = desc->data.length;
const int8_t pool = topaz_hbm_payload_get_pool_bus(buf_bus_rx);
const struct ether_header *eh = bus_to_virt((uintptr_t) desc->data.pkt);
uint8_t vinfo_hdr = 0;
if (unlikely(buf_bus_rx == NULL)) {
if (printk_ratelimit()) {
printk(KERN_CRIT "%s: NULL buffer from TQE, len %u, in port %u",
__FUNCTION__, buflen, in_port);
}
return -1;
}
if (unlikely(buflen < ETH_HLEN)) {
printk(KERN_WARNING
"%s: buffer from TQE smaller than ethernet header, len %u, in port %u",
__FUNCTION__, buflen, in_port);
return -1;
}
if (unlikely(!topaz_hbm_pool_valid(pool))) {
printk(KERN_CRIT "%s: invalid pool buffer from TQE: 0x%p", __FUNCTION__, buf_bus_rx);
return -1;
}
if (likely((in_port < TOPAZ_TQE_NUM_PORTS) && tqe_port_handlers[in_port].handler)) {
struct sk_buff *skb;
uint8_t *whole_frm_hdr;
topaz_hbm_debug_stamp(topaz_hbm_payload_store_align_virt(buf_virt_rx, pool, 0),
TOPAZ_HBM_OWNER_LH_RX_TQE, buflen);
/* invalidate enough for l3 packet inspection for multicast frames */
inv_dcache_sizerange_safe(buf_virt_rx, 64);
#if defined(CONFIG_ARCH_TOPAZ_SWITCH_TEST) || defined(CONFIG_ARCH_TOPAZ_SWITCH_TEST_MODULE)
topaz_tqe_test_ctrl(buf_virt_rx);
#endif
if (TOPAZ_TQE_PORT_IS_EMAC(in_port)) {
if (vlan_enabled) {
struct qtn_vlan_dev *vdev = vport_tbl_lhost[in_port];
BUG_ON(vdev == NULL);
if (!qtn_vlan_ingress(vdev, 0,
buf_virt_rx, 0, 1)) {
tqe_rx_pkt_drop(desc);
return 0;
}
}
} else if (unlikely(((in_port == TOPAZ_TQE_WMAC_PORT) || (in_port == TOPAZ_TQE_MUC_PORT)))) {
if (g_tqe_mac_reserved_hook && g_tqe_mac_reserved_hook(eh->ether_shost)) {
tqe_rx_pkt_drop(desc);
return 0;
}
} else {
BUG_ON(1);
}
if (vlan_enabled)
vinfo_hdr = QVLAN_PKTCTRL_LEN;
if (likely(!wowlan_magic_packet_check(desc))) {
#ifdef CONFIG_TOPAZ_PCIE_HOST
if (tqe_rx_multicast(NULL, desc))
#else
if (tqe_rx_multicast(priv->congest_queue, desc))
#endif
return 0;
if (tqe_rx_pktfwd(priv->congest_queue, desc))
return 0;
}
#if TOPAZ_HBM_BUF_WMAC_RX_QUARANTINE
if (pool == TOPAZ_HBM_BUF_WMAC_RX_POOL)
{
skb = topaz_hbm_attach_skb_quarantine(buf_virt_rx, pool, buflen, &whole_frm_hdr);
/* now desc doesn't link to the new skb data buffer */
if (skb) {
/* new buf is used, no need for original one */
tqe_rx_pkt_drop(desc);
}
}
else
#endif
{
skb = topaz_hbm_attach_skb((uint8_t *)buf_virt_rx, pool, vinfo_hdr);
whole_frm_hdr = skb->head;
}
if (skb) {
/* attach VLAN information to skb */
skb_put(skb, buflen);
if (vlan_enabled) {
struct qtn_vlan_pkt *pkt = qtn_vlan_get_info(skb->data);
if (unlikely(pkt->magic != QVLAN_PKT_MAGIC)) {
printk(KERN_WARNING "WARNING: pkt(%p) magic not right. 0x%04x\n", skb->data, pkt->magic);
} else {
skb->vlan_tci = pkt->vlan_info & QVLAN_MASK_VID;
}
M_FLAG_SET(skb, M_VLAN_TAGGED);
}
/* Frame received from external L2 filter will not have MAC header */
if (tqe_rx_l2_ext_filter(desc, skb))
whole_frm_hdr = NULL;
tqe_rx_call_port_handler(desc, skb, whole_frm_hdr);
return 0;
}
} else {
if (printk_ratelimit()) {
printk(KERN_ERR "%s: input from unhandled port %u misc %u\n",
__FUNCTION__, in_port, (unsigned)desc->data.misc_user);
}
}
tqe_rx_pkt_drop(desc);
return 0;
}
static void tqe_irq_enable(void)
{
topaz_tqe_cpuif_setup_irq(1, 0);
}
static void tqe_irq_disable(void)
{
topaz_tqe_cpuif_setup_irq(0, 0);
}
static int __sram_text tqe_rx_napi_handler(struct napi_struct *napi, int budget)
{
int processed = 0;
struct tqe_netdev_priv *priv = container_of(napi, struct tqe_netdev_priv, napi);
while (processed < budget) {
union topaz_tqe_cpuif_status status;
union topaz_tqe_cpuif_descr __iomem *desc_bus;
union topaz_tqe_cpuif_descr *desc_virt;
union topaz_tqe_cpuif_descr desc_local;
uintptr_t inv_start;
size_t inv_size;
status = topaz_tqe_cpuif_get_status();
if (status.data.empty) {
break;
}
desc_bus = topaz_tqe_cpuif_get_curr();
desc_virt = bus_to_virt((uintptr_t) desc_bus);
/* invalidate descriptor and copy to the stack */
inv_start = (uintptr_t) align_buf_cache(desc_virt);
inv_size = align_buf_cache_size(desc_virt, sizeof(*desc_virt));
inv_dcache_range(inv_start, inv_start + inv_size);
memcpy(&desc_local, desc_virt, sizeof(*desc_virt));
if (likely(desc_local.data.own)) {
topaz_tqe_cpuif_put_back(desc_bus);
tqe_rx_desc_handler(priv, &desc_local);
++processed;
} else {
printk("%s unowned descriptor? desc_bus 0x%p 0x%08x 0x%08x 0x%08x 0x%08x\n",
__FUNCTION__, desc_bus,
desc_local.raw.dw0, desc_local.raw.dw1,
desc_local.raw.dw2, desc_local.raw.dw3);
break;
}
}
if (processed < budget) {
napi_complete(napi);
tqe_irq_enable();
}
return processed;
}
static irqreturn_t __sram_text tqe_irqh(int irq, void *_dev)
{
struct net_device *dev = _dev;
struct tqe_netdev_priv *priv = netdev_priv(dev);
napi_schedule(&priv->napi);
tqe_irq_disable();
return IRQ_HANDLED;
}
/*
* TQE network device ops
*/
static int tqe_ndo_open(struct net_device *dev)
{
return -ENODEV;
}
static int tqe_ndo_stop(struct net_device *dev)
{
return -ENODEV;
}
static int tqe_tx_buf(union topaz_tqe_cpuif_ppctl *ppctl,
void __iomem *virt_buf, unsigned long data_len, int8_t pool)
{
const uintptr_t bus_data_start = virt_to_bus(virt_buf);
const long buff_ptr_offset = topaz_hbm_payload_buff_ptr_offset_bus((void *)bus_data_start, pool, NULL);
ppctl->data.pkt = (void *) bus_data_start;
ppctl->data.buff_ptr_offset = buff_ptr_offset;
ppctl->data.length = data_len;
/* always free to txdone pool */
ppctl->data.buff_pool_num = TOPAZ_HBM_EMAC_TX_DONE_POOL;
while (topaz_tqe_cpuif_tx_nready());
switch_tqe_multi_proc_sem_down("tqe_tx_buf",__LINE__);
topaz_tqe_cpuif_tx_start(ppctl);
switch_tqe_multi_proc_sem_up();
return 0;
}
void tqe_register_fwt_cbk(tqe_fwt_get_mcast_hook get_mcast_cbk_func,
tqe_fwt_get_mcast_ff_hook get_mcast_ff_cbk_func,
tqe_fwt_false_miss_hook false_miss_func)
{
g_tqe_fwt_get_mcast_hook = get_mcast_cbk_func;
g_tqe_fwt_get_mcast_ff_hook = get_mcast_ff_cbk_func;
g_tqe_fwt_false_miss_hook = false_miss_func;
}
EXPORT_SYMBOL(tqe_register_fwt_cbk);
int tqe_tx(union topaz_tqe_cpuif_ppctl *ppctl, struct sk_buff *skb)
{
unsigned int data_len = skb->len;
void *buf_virt = skb->data;
void *buf_bus = (void *) virt_to_bus(buf_virt);
void *buf_virt_vlan;
int8_t pool = topaz_hbm_payload_get_pool_bus(buf_bus);
const bool hbm_can_use = !vlan_enabled &&
topaz_hbm_pool_valid(pool) &&
(atomic_read(&skb->users) == 1) &&
(atomic_read(&skb_shinfo(skb)->dataref) == 1);
if (hbm_can_use) {
/*
* skb is otherwise unused; clear to send out tqe.
* Set flag such that payload isn't returned to the hbm on free
*/
skb->hbm_no_free = 1;
topaz_hbm_flush_skb_cache(skb);
} else {
void *hbm_buf_virt;
uintptr_t flush_start;
size_t flush_size;
int from_local = 0;
if (vlan_enabled && ppctl->data.out_port == TOPAZ_TQE_WMAC_PORT) {
from_local = switch_vlan_is_local(buf_virt);
}
if (data_len < TOPAZ_HBM_BUF_EMAC_RX_SIZE) {
pool = TOPAZ_HBM_BUF_EMAC_RX_POOL;
} else {
/*
* requested impossibly large transmission
*/
if (printk_ratelimit()) {
printk(KERN_ERR "%s: requested oversize transmission"
" (%u bytes) to port %d\n",
__FUNCTION__, data_len, ppctl->data.out_port);
}
kfree_skb(skb);
return NETDEV_TX_OK;
}
hbm_buf_virt = topaz_hbm_get_payload_virt(pool);
if (unlikely(!hbm_buf_virt)) {
/* buffer will be stored in gso_skb and re-attempted for xmit */
return NETDEV_TX_BUSY;
}
topaz_hbm_debug_stamp(hbm_buf_virt, TOPAZ_HBM_OWNER_LH_TX_TQE, data_len);
memcpy(hbm_buf_virt, buf_virt, data_len);
buf_virt = hbm_buf_virt;
if (from_local) {
struct qtn_vlan_pkt *pkt = (struct qtn_vlan_pkt *)((uint8_t *)buf_virt - QVLAN_PKTCTRL_LEN);
pkt->magic = QVLAN_PKT_MAGIC;
pkt->vlan_info = QVLAN_SKIP_CHECK;
flush_start = (uintptr_t) align_buf_cache((uint8_t *)buf_virt - QVLAN_PKTCTRL_LEN);
flush_size = align_buf_cache_size((uint8_t *)buf_virt - QVLAN_PKTCTRL_LEN, data_len + QVLAN_PKTCTRL_LEN);
} else if (M_FLAG_ISSET(skb, M_VLAN_TAGGED)) {
buf_virt_vlan = qtn_vlan_get_info(buf_virt);
memcpy(buf_virt_vlan, (uint8_t *)qtn_vlan_get_info(skb->data),
QVLAN_PKTCTRL_LEN);
flush_start = (uintptr_t) align_buf_cache(buf_virt_vlan);
flush_size = align_buf_cache_size(buf_virt_vlan, data_len + QVLAN_PKTCTRL_LEN);
} else {
flush_start = (uintptr_t) align_buf_cache(buf_virt);
flush_size = align_buf_cache_size(buf_virt, data_len);
}
flush_and_inv_dcache_range(flush_start, flush_start + flush_size);
}
dev_kfree_skb(skb);
tqe_tx_buf(ppctl, buf_virt, data_len, pool);
return NETDEV_TX_OK;
}
EXPORT_SYMBOL(tqe_tx);
static int tqe_ndo_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
return NETDEV_TX_BUSY;
}
static const struct net_device_ops tqe_ndo = {
.ndo_open = tqe_ndo_open,
.ndo_stop = tqe_ndo_stop,
.ndo_start_xmit = tqe_ndo_start_xmit,
.ndo_set_mac_address = eth_mac_addr,
};
static int tqe_descs_alloc(struct tqe_netdev_priv *priv)
{
int i;
union topaz_tqe_cpuif_descr __iomem *bus_descs;
if (ALIGNED_DMA_DESC_ALLOC(&priv->rx, QTN_BUFS_LHOST_TQE_RX_RING, TOPAZ_TQE_CPUIF_RXDESC_ALIGN, 1)) {
return -ENOMEM;
}
bus_descs = (void *)priv->rx.descs_dma_addr;
for (i = 0; i < QTN_BUFS_LHOST_TQE_RX_RING; i++) {
priv->rx.descs[i].data.next = &bus_descs[(i + 1) % QTN_BUFS_LHOST_TQE_RX_RING];
}
printk(KERN_INFO "%s: %u tqe_rx_descriptors at kern uncached 0x%p bus 0x%p\n",
__FUNCTION__, priv->rx.desc_count, priv->rx.descs, bus_descs);
topaz_tqe_cpuif_setup_ring((void *)priv->rx.descs_dma_addr, priv->rx.desc_count);
return 0;
}
static void tqe_descs_free(struct tqe_netdev_priv *priv)
{
if (priv->rx.descs) {
ALIGNED_DMA_DESC_FREE(&priv->rx);
}
}
void print_tqe_counters(struct tqe_netdev_priv *priv)
{
int i;
if (priv->congest_queue == NULL)
return;
for (i = 0; i < TOPAZ_CONGEST_QUEUE_NUM; i++)
printk("rx_congest_fwd %d:\t%08x \t%d\n",
i, priv->congest_queue->queues[i].congest_xmit,
priv->congest_queue->queues[i].qlen);
for (i = 0; i < TOPAZ_CONGEST_QUEUE_NUM; i++)
printk("rx_congest_drop %d:\t%08x\n",
i, priv->congest_queue->queues[i].congest_drop);
for (i = 0; i < TOPAZ_CONGEST_QUEUE_NUM; i++)
printk("rx_congest_enq_fail %d:\t%08x\n",
i, priv->congest_queue->queues[i].congest_enq_fail);
/* Congest Queue */
printk("rx_congest_entry:\t%08x\n", priv->congest_queue->func_entry);
printk("rx_congest_retry:\t%08x\n", priv->congest_queue->cnt_retries);
printk("total len:\t%08x \tunicast count:%d\n",
priv->congest_queue->total_qlen,
priv->congest_queue->unicast_qcount);
printk("active tid num:\t%08x\n", qtn_mproc_sync_shared_params_get()->active_tid_num);
}
static ssize_t tqe_dbg_show(struct device *dev, struct device_attribute *attr,
char *buff)
{
return 0;
}
static void tqe_init_port_handler(void)
{
memset(tqe_port_handlers, 0, sizeof(tqe_port_handlers));
return;
}
static ssize_t tqe_dbg_set(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct net_device *ndev = container_of(dev, struct net_device, dev);
struct tqe_netdev_priv *priv = netdev_priv(ndev);
char buffer[128];
char *str = buffer;
char *token;
uint32_t cmd;
strncpy(str, buf, sizeof(buffer) - 1);
token = strsep(&str, " ,\n");
cmd = (uint32_t)simple_strtoul(token, NULL, 10);
switch (cmd) {
case 0:
print_tqe_counters(priv);
break;
case 1:
topaz_congest_dump(priv->congest_queue);
break;
default:
break;
}
return count;
}
DEVICE_ATTR(dbg, S_IWUSR | S_IRUSR, tqe_dbg_show, tqe_dbg_set); /* dev_attr_dbg */
static struct net_device * __init tqe_netdev_init(void)
{
int rc = 0;
struct net_device *dev = NULL;
struct tqe_netdev_priv *priv;
static const int tqe_netdev_irq = TOPAZ_IRQ_TQE;
tqe_init_port_handler();
dev = alloc_netdev(sizeof(struct tqe_netdev_priv), "tqe", &ether_setup);
if (!dev) {
printk(KERN_ERR "%s: unable to allocate dev\n", __FUNCTION__);
goto netdev_alloc_error;
}
priv = netdev_priv(dev);
dev->base_addr = 0;
dev->irq = tqe_netdev_irq;
dev->watchdog_timeo = 60 * HZ;
dev->tx_queue_len = 1;
dev->netdev_ops = &tqe_ndo;
/* Initialise TQE */
topaz_tqe_cpuif_setup_reset(1);
topaz_tqe_cpuif_setup_reset(0);
if (tqe_descs_alloc(priv)) {
goto desc_alloc_error;
}
rc = request_irq(dev->irq, &tqe_irqh, 0, dev->name, dev);
if (rc) {
printk(KERN_ERR "%s: unable to get %s IRQ %d\n",
__FUNCTION__, dev->name, tqe_netdev_irq);
goto irq_request_error;
}
#ifndef CONFIG_TOPAZ_PCIE_HOST
/* Initialize congestion queue */
priv->congest_queue = topaz_congest_queue_init();
if (priv->congest_queue == NULL){
printk(KERN_ERR "LHOST TQE: Can't allocate congest queue\n");
goto congest_queue_alloc_error;
}
priv->congest_queue->xmit_func = topaz_tqe_xmit;
#endif
rc = register_netdev(dev);
if (rc) {
printk(KERN_ERR "%s: Cannot register net device '%s', error %d\n",
__FUNCTION__, dev->name, rc);
goto netdev_register_error;
}
netif_napi_add(dev, &priv->napi, &tqe_rx_napi_handler, 8);
napi_enable(&priv->napi);
tqe_irq_enable();
device_create_file(&dev->dev, &dev_attr_dbg);
return dev;
netdev_register_error:
topaz_congest_queue_exit(priv->congest_queue);
#ifndef CONFIG_TOPAZ_PCIE_HOST
congest_queue_alloc_error:
free_irq(dev->irq, dev);
#endif
irq_request_error:
tqe_descs_free(priv);
desc_alloc_error:
free_netdev(dev);
netdev_alloc_error:
return NULL;
}
static void __exit tqe_netdev_exit(struct net_device *dev)
{
struct tqe_netdev_priv *priv = netdev_priv(dev);
device_remove_file(&dev->dev, &dev_attr_dbg);
tqe_irq_disable();
free_irq(dev->irq, dev);
free_netdev(dev);
topaz_congest_queue_exit(priv->congest_queue);
}
static struct net_device *tqe_netdev;
static int __init tqe_module_init(void)
{
tqe_netdev = tqe_netdev_init();
return tqe_netdev ? 0 : -EFAULT;
}
static void __exit tqe_module_exit(void)
{
tqe_netdev_exit(tqe_netdev);
}
module_init(tqe_module_init);
module_exit(tqe_module_exit);
MODULE_LICENSE("GPL");