477 lines
11 KiB
C
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;
|
|
}
|