2010-08-27 11:19:57 +02:00

477 lines
11 KiB
C

/*
* This is part of the Sequans SQN1130 driver.
* Copyright 2008 SEQUANS Communications
* Written by Andy Shevchenko <andy@smile.org.ua>,
* Dmitriy Chumak <chumakd@gmail.com>
*
* 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.
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/etherdevice.h>
#include <linux/kthread.h>
#include <linux/netdevice.h>
#include <linux/device.h>
#include "sdio-netdev.h"
#include "version.h"
#include "msg.h"
#include "thp.h"
#include "sdio.h"
#include "sdio-pm.h"
#include "sdio-fw.h"
#include "sdio-driver.h"
#define DRIVER_DEBUG 0
#define SKB_DEBUG 0
#define IGNORE_CARRIER_STATE 1
/*******************************************************************/
/* Module parameter variables */
/*******************************************************************/
/** firmware_name - specifies the name of firmware binary */
char *firmware_name = SQN_DEFAULT_FW_NAME;
/**
* load_firmware - boolean flag, controls whether firmware
* should be loaded or not
*/
int load_firmware = 1;
bool drop_packet = false;
module_param(firmware_name, charp, S_IRUGO);
module_param(load_firmware, bool, S_IRUGO);
struct sqn_private *g_priv = 0;
//reference sdio-driver.c
extern const uint8_t ss_macaddr[ETH_ALEN];
/*******************************************************************/
/* Network interface functions */
/*******************************************************************/
static int sqn_dev_open(struct net_device *dev)
{
struct sqn_private *priv = netdev_priv(dev);
sqn_pr_enter();
spin_lock(&priv->drv_lock);
netif_wake_queue(dev);
spin_unlock(&priv->drv_lock);
sqn_pr_leave();
return 0;
}
static int sqn_dev_stop(struct net_device *dev)
{
struct sqn_private *priv = netdev_priv(dev);
sqn_pr_enter();
spin_lock(&priv->drv_lock);
netif_stop_queue(dev);
spin_unlock(&priv->drv_lock);
sqn_pr_leave();
return 0;
}
/*******************************************************************/
/* TX queue handlers */
/*******************************************************************/
int sqn_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned long irq_flags = 0;
struct ethhdr *eth;
struct sqn_private *priv = netdev_priv(dev);
#if DRIVER_DEBUG
printk(KERN_WARNING "sqn_hard_start_xmit \n");
#endif
sqn_pr_enter();
sqn_pr_dbg("skb->len = %d\n", skb->len);
#if SKB_DEBUG
sqn_pr_info("%s: got skb [0x%p] from kernel, users %d\n", __func__, skb, atomic_read(&skb->users));
#endif
spin_lock_irqsave(&priv->drv_lock, irq_flags);
//HTC code: for DDTM
if(drop_packet){
eth = (struct ethhdr*) skb->data;
if(memcmp(eth->h_dest, ss_macaddr, ETH_ALEN) != 0){
sqn_pr_dbg("HTC drop_packet enabled: not THP, drop it\n");
#if DRIVER_DEBUG
printk(KERN_WARNING "sqn_hard_start_xmit: network packet\n");
#endif
priv->stats.tx_dropped++;
priv->stats.tx_errors++;
dev_kfree_skb_any(skb);
goto out;
}else{
sqn_pr_dbg("HTC drop_packet enabled: THP, let it live\n");
#if DRIVER_DEBUG
printk(KERN_WARNING "sqn_hard_start_xmit: thp packet\n");
#endif
}
}
if (priv->removed)
goto out;
if (skb->len < 1 || (skb->len > SQN_MAX_PDU_LEN)) {
sqn_pr_dbg("skb length %d not in range (1, %d)\n", skb->len,
SQN_MAX_PDU_LEN);
/*
* We'll never manage to send this one;
* drop it and return 'OK'
*/
priv->stats.tx_dropped++;
priv->stats.tx_errors++;
spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
goto out;
}
/* netif_stop_queue(priv->dev); */
priv->add_skb_to_tx_queue(priv, skb, 1);
priv->stats.tx_packets++;
priv->stats.tx_bytes += skb->len;
dev->trans_start = jiffies;
spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
wake_up_interruptible(&priv->tx_waitq);
out:
sqn_pr_leave();
return NETDEV_TX_OK;
}
static void sqn_tx_timeout(struct net_device *dev)
{
/* struct sqn_private *priv = netdev_priv(dev); */
sqn_pr_enter();
sqn_pr_err("TX watch dog timeout\n");
sqn_pr_leave();
}
static int sqn_tx_thread(void *data)
{
struct net_device *dev = (struct net_device *) data;
struct sqn_private *priv = netdev_priv(dev);
int rv = 0;
unsigned long irq_flags = 0;
sqn_pr_enter();
/*
* Set PF_NOFREEZE to prevent kernel to freeze this thread
* when going to suspend. We will manually stop it from
* driver's suspend handler.
*/
current->flags |= PF_NOFREEZE;
for (;;) {
spin_lock_irqsave(&priv->drv_lock, irq_flags);
if (!(priv->is_tx_queue_empty(priv)) || priv->removed)
spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
else {
int rv = 0;
sqn_pr_dbg("wait for skb\n");
spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
rv = wait_event_interruptible(priv->tx_waitq
, !(priv->is_tx_queue_empty(priv))
|| kthread_should_stop()
|| priv->removed);
/*
* If we've been interrupted by a signal, then we
* should stop a thread
*/
if (0 != rv) {
sqn_pr_dbg("got a signal from kernel %d\n", rv);
break;
}
}
sqn_pr_dbg("got skb to send, wake up\n");
if (kthread_should_stop()) {
sqn_pr_dbg("break from main thread\n");
break;
}
spin_lock_irqsave(&priv->drv_lock, irq_flags);
if (priv->removed) {
sqn_pr_dbg("adapter removed; wait to die...\n");
spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
mdelay(1);
continue;
}
spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
rv= priv->hw_host_to_card(priv);
if (rv)
sqn_pr_dbg("failed to send PDU: %d\n", rv);
}
sqn_pr_leave();
return 0;
}
int sqn_start_tx_thread(struct sqn_private *priv)
{
int rv = 0;
sqn_pr_enter();
priv->tx_thread = kthread_run(sqn_tx_thread, priv->dev, "sqn_tx");
if (IS_ERR(priv->tx_thread)) {
sqn_pr_dbg("error creating TX thread.\n");
rv = 1;
goto out;
}
sqn_pr_leave();
out:
return rv;
}
int sqn_stop_tx_thread(struct sqn_private *priv)
{
int rv = 0;
sqn_pr_enter();
kthread_stop(priv->tx_thread);
wake_up_interruptible(&priv->tx_waitq);
sqn_pr_leave();
return rv;
}
/*******************************************************************/
/* RX queue handlers */
/*******************************************************************/
int sqn_rx_process(struct net_device *dev, struct sk_buff *skb)
{
int rc = 0;
struct sqn_private *priv = netdev_priv(dev);
#if DRIVER_DEBUG
printk(KERN_WARNING "sqn_rx_process \n");
#endif
sqn_pr_enter();
dev->last_rx = jiffies;
skb->protocol = eth_type_trans(skb, dev);
skb->dev = dev;
priv->stats.rx_packets++;
priv->stats.rx_bytes += skb->len;
#if SKB_DEBUG
sqn_pr_info("%s: push skb [0x%p] to kernel, users %d\n", __func__, skb, atomic_read(&skb->users));
#endif
netif_rx(skb);
/* netif_receive_skb(skb); */
sqn_pr_leave();
return rc;
}
/*******************************************************************/
/* Interface statistics */
/*******************************************************************/
static struct net_device_stats *sqn_get_stats(struct net_device *dev)
{
struct sqn_private *priv = netdev_priv(dev);
sqn_pr_enter();
sqn_pr_leave();
return &priv->stats;
}
/*******************************************************************/
/* Adding and removing procedures */
/*******************************************************************/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
static const struct net_device_ops sqn_netdev_ops = {
.ndo_open = sqn_dev_open
, .ndo_stop = sqn_dev_stop
, .ndo_start_xmit = sqn_hard_start_xmit
, .ndo_validate_addr = eth_validate_addr
, .ndo_tx_timeout = sqn_tx_timeout
, .ndo_get_stats = sqn_get_stats
};
#endif
struct sqn_private *sqn_add_card(void *card, struct device *realdev)
{
struct sqn_private *priv = 0;
u8 dummy_wimax_mac_addr[ETH_ALEN] = { 0x00, 0x16, 0x08, 0x00, 0x06, 0x53 };
/* Allocate an Ethernet device and register it */
struct net_device *dev = alloc_netdev(sizeof(struct sqn_private), "wimax%d", ether_setup);
sqn_pr_enter();
if (!dev) {
sqn_pr_err("init wimaxX device failed\n");
goto done;
}
priv = netdev_priv(dev);
g_priv = priv;
memset(priv, 0, sizeof(struct sqn_private));
/*
* Use dummy WiMAX mac address for development version (boot from
* flash) of WiMAX SDIO cards. Production cards use mac address from
* firmware which is loaded by driver. Random ethernet address can't be
* used if IPv4 convergence layer is enabled on WiMAX base station.
*/
memcpy(priv->mac_addr, dummy_wimax_mac_addr, ETH_ALEN);
spin_lock_init(&priv->drv_lock);
/* Fill the private stucture */
priv->dev = dev;
priv->card = card;
/* Setup the OS Interface to our functions */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
dev->open = sqn_dev_open;
dev->stop = sqn_dev_stop;
dev->hard_start_xmit = sqn_hard_start_xmit;
dev->tx_timeout = sqn_tx_timeout;
dev->get_stats = sqn_get_stats;
#else
dev->netdev_ops = &sqn_netdev_ops;
#endif
/* TODO: Make multicast possible */
dev->flags &= ~IFF_MULTICAST;
//wimax interface mtu must be 1400 (in spec)
dev->mtu = 1400;
SET_NETDEV_DEV(dev, realdev);
done:
sqn_pr_leave();
return priv;
}
int sqn_remove_card(struct sqn_private *priv)
{
struct net_device *dev = priv->dev;
unsigned long irq_flags = 0;
sqn_pr_enter();
dev = priv->dev;
spin_lock_irqsave(&priv->drv_lock, irq_flags);
priv->removed = 1;
priv->dev = NULL;
spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
/* kthread_stop(priv->tx_thread); */
/* wake_up_interruptible(&priv->tx_waitq); */
free_netdev(dev);
sqn_pr_leave();
return 0;
}
int sqn_start_card(struct sqn_private *priv)
{
struct net_device *dev = priv->dev;
sqn_pr_enter();
if (register_netdev(dev)) {
sqn_pr_err("cannot register ethX device\n");
return -1;
}
sqn_pr_dbg("starting TX thread...\n");
/* TODO: move waitq initializatio to add_card() */
init_waitqueue_head(&priv->tx_waitq);
init_waitqueue_head(&priv->rx_waitq);
if (sqn_start_tx_thread(priv))
goto err_init_adapter;
sqn_pr_info("%s: Sequans WiMAX adapter\n", dev->name);
#if IGNORE_CARRIER_STATE
netif_carrier_on(priv->dev);
#else
/* In release version this should be uncommented */
/* netif_carrier_off(priv->dev); */
#endif
done:
sqn_pr_leave();
return 0;
err_init_adapter:
/* TODO: Free allocated resources */
sqn_pr_err("error while init adapter\n");
free_netdev(dev);
priv = NULL;
goto done;
}
int sqn_stop_card(struct sqn_private *priv)
{
struct net_device *dev = priv->dev;
sqn_pr_enter();
unregister_netdev(dev);
sqn_pr_leave();
return 0;
}