mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-12-27 03:05:11 +02:00
fa4dd3d5de
Bridging between the ramips ethernet driver and rt2800pci was somewhat broken. Frames received by the ethernet driver which were passed to the wifi driver for transmission were sometimes corrupted or sent out with huge delays. The reason for this is the missing assignment of skb->tail in the ramips ethernet driver's rx path resulting in skb->tail pointing to skb->data. Since skb->tail is used by mac80211 it writes into skb->data which messes up the frames content. Fix this by using skb_put to correctly set skb->len and skb->tail. Signed-off-by: Helmut Schaa <helmut.schaa@googlemail.com> git-svn-id: svn://svn.openwrt.org/openwrt/trunk@22172 3c298f89-4303-0410-b956-a3cf2f4a3e73
506 lines
12 KiB
C
506 lines
12 KiB
C
/*
|
|
* 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; version 2 of the License
|
|
*
|
|
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* Copyright (C) 2009 John Crispin <blogic@openwrt.org>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/init.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <ramips_eth_platform.h>
|
|
#include "ramips_eth.h"
|
|
|
|
#define TX_TIMEOUT (20 * HZ / 100)
|
|
#define MAX_RX_LENGTH 1600
|
|
|
|
#ifdef CONFIG_RALINK_RT305X
|
|
#include "ramips_esw.c"
|
|
#endif
|
|
|
|
#define phys_to_bus(a) (a & 0x1FFFFFFF)
|
|
|
|
static struct net_device * ramips_dev;
|
|
static void __iomem *ramips_fe_base = 0;
|
|
|
|
static inline void
|
|
ramips_fe_wr(u32 val, unsigned reg)
|
|
{
|
|
__raw_writel(val, ramips_fe_base + reg);
|
|
}
|
|
|
|
static inline u32
|
|
ramips_fe_rr(unsigned reg)
|
|
{
|
|
return __raw_readl(ramips_fe_base + reg);
|
|
}
|
|
|
|
static inline void
|
|
ramips_fe_int_disable(u32 mask)
|
|
{
|
|
ramips_fe_wr(ramips_fe_rr(RAMIPS_FE_INT_ENABLE) & ~mask,
|
|
RAMIPS_FE_INT_ENABLE);
|
|
/* flush write */
|
|
ramips_fe_rr(RAMIPS_FE_INT_ENABLE);
|
|
}
|
|
|
|
static inline void
|
|
ramips_fe_int_enable(u32 mask)
|
|
{
|
|
ramips_fe_wr(ramips_fe_rr(RAMIPS_FE_INT_ENABLE) | mask,
|
|
RAMIPS_FE_INT_ENABLE);
|
|
/* flush write */
|
|
ramips_fe_rr(RAMIPS_FE_INT_ENABLE);
|
|
}
|
|
|
|
static inline void
|
|
ramips_hw_set_macaddr(unsigned char *mac)
|
|
{
|
|
ramips_fe_wr((mac[0] << 8) | mac[1], RAMIPS_GDMA1_MAC_ADRH);
|
|
ramips_fe_wr((mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5],
|
|
RAMIPS_GDMA1_MAC_ADRL);
|
|
}
|
|
|
|
static void
|
|
ramips_cleanup_dma(struct raeth_priv *re)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_RX_DESC; i++)
|
|
if (re->rx_skb[i])
|
|
dev_kfree_skb_any(re->rx_skb[i]);
|
|
|
|
if (re->rx)
|
|
dma_free_coherent(NULL,
|
|
NUM_RX_DESC * sizeof(struct ramips_rx_dma),
|
|
re->rx, re->phy_rx);
|
|
|
|
if (re->tx)
|
|
dma_free_coherent(NULL,
|
|
NUM_TX_DESC * sizeof(struct ramips_tx_dma),
|
|
re->tx, re->phy_tx);
|
|
}
|
|
|
|
static int
|
|
ramips_alloc_dma(struct raeth_priv *re)
|
|
{
|
|
int err = -ENOMEM;
|
|
int i;
|
|
|
|
re->skb_free_idx = 0;
|
|
|
|
/* setup tx ring */
|
|
re->tx = dma_alloc_coherent(NULL,
|
|
NUM_TX_DESC * sizeof(struct ramips_tx_dma),
|
|
&re->phy_tx, GFP_ATOMIC);
|
|
if (!re->tx)
|
|
goto err_cleanup;
|
|
|
|
memset(re->tx, 0, NUM_TX_DESC * sizeof(struct ramips_tx_dma));
|
|
for (i = 0; i < NUM_TX_DESC; i++) {
|
|
re->tx[i].txd2 = TX_DMA_LSO | TX_DMA_DONE;
|
|
re->tx[i].txd4 = TX_DMA_QN(3) | TX_DMA_PN(1);
|
|
}
|
|
|
|
/* setup rx ring */
|
|
re->rx = dma_alloc_coherent(NULL,
|
|
NUM_RX_DESC * sizeof(struct ramips_rx_dma),
|
|
&re->phy_rx, GFP_ATOMIC);
|
|
if (!re->rx)
|
|
goto err_cleanup;
|
|
|
|
memset(re->rx, 0, sizeof(struct ramips_rx_dma) * NUM_RX_DESC);
|
|
for (i = 0; i < NUM_RX_DESC; i++) {
|
|
struct sk_buff *new_skb = dev_alloc_skb(MAX_RX_LENGTH + 2);
|
|
|
|
if (!new_skb)
|
|
goto err_cleanup;
|
|
|
|
skb_reserve(new_skb, 2);
|
|
re->rx[i].rxd1 = dma_map_single(NULL,
|
|
skb_put(new_skb, 2),
|
|
MAX_RX_LENGTH + 2,
|
|
DMA_FROM_DEVICE);
|
|
re->rx[i].rxd2 |= RX_DMA_LSO;
|
|
re->rx_skb[i] = new_skb;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_cleanup:
|
|
ramips_cleanup_dma(re);
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
ramips_setup_dma(struct raeth_priv *re)
|
|
{
|
|
ramips_fe_wr(phys_to_bus(re->phy_tx), RAMIPS_TX_BASE_PTR0);
|
|
ramips_fe_wr(NUM_TX_DESC, RAMIPS_TX_MAX_CNT0);
|
|
ramips_fe_wr(0, RAMIPS_TX_CTX_IDX0);
|
|
ramips_fe_wr(RAMIPS_PST_DTX_IDX0, RAMIPS_PDMA_RST_CFG);
|
|
|
|
ramips_fe_wr(phys_to_bus(re->phy_rx), RAMIPS_RX_BASE_PTR0);
|
|
ramips_fe_wr(NUM_RX_DESC, RAMIPS_RX_MAX_CNT0);
|
|
ramips_fe_wr((NUM_RX_DESC - 1), RAMIPS_RX_CALC_IDX0);
|
|
ramips_fe_wr(RAMIPS_PST_DRX_IDX0, RAMIPS_PDMA_RST_CFG);
|
|
}
|
|
|
|
static int
|
|
ramips_eth_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
{
|
|
struct raeth_priv *priv = netdev_priv(dev);
|
|
unsigned long tx;
|
|
unsigned int tx_next;
|
|
unsigned int mapped_addr;
|
|
unsigned long flags;
|
|
|
|
if (priv->plat->min_pkt_len) {
|
|
if (skb->len < priv->plat->min_pkt_len) {
|
|
if (skb_padto(skb, priv->plat->min_pkt_len)) {
|
|
printk(KERN_ERR
|
|
"ramips_eth: skb_padto failed\n");
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
skb_put(skb, priv->plat->min_pkt_len - skb->len);
|
|
}
|
|
}
|
|
|
|
dev->trans_start = jiffies;
|
|
mapped_addr = (unsigned int) dma_map_single(NULL, skb->data, skb->len,
|
|
DMA_TO_DEVICE);
|
|
dma_sync_single_for_device(NULL, mapped_addr, skb->len, DMA_TO_DEVICE);
|
|
spin_lock_irqsave(&priv->page_lock, flags);
|
|
tx = ramips_fe_rr(RAMIPS_TX_CTX_IDX0);
|
|
tx_next = (tx + 1) % NUM_TX_DESC;
|
|
|
|
if ((priv->tx_skb[tx]) || (priv->tx_skb[tx_next]) ||
|
|
!(priv->tx[tx].txd2 & TX_DMA_DONE) ||
|
|
!(priv->tx[tx_next].txd2 & TX_DMA_DONE))
|
|
goto out;
|
|
|
|
priv->tx[tx].txd1 = mapped_addr;
|
|
priv->tx[tx].txd2 &= ~(TX_DMA_PLEN0_MASK | TX_DMA_DONE);
|
|
priv->tx[tx].txd2 |= TX_DMA_PLEN0(skb->len);
|
|
dev->stats.tx_packets++;
|
|
dev->stats.tx_bytes += skb->len;
|
|
priv->tx_skb[tx] = skb;
|
|
wmb();
|
|
ramips_fe_wr(tx_next, RAMIPS_TX_CTX_IDX0);
|
|
spin_unlock_irqrestore(&priv->page_lock, flags);
|
|
return NETDEV_TX_OK;
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&priv->page_lock, flags);
|
|
dev->stats.tx_dropped++;
|
|
kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
static void
|
|
ramips_eth_rx_hw(unsigned long ptr)
|
|
{
|
|
struct net_device *dev = (struct net_device *) ptr;
|
|
struct raeth_priv *priv = netdev_priv(dev);
|
|
int rx;
|
|
int max_rx = 16;
|
|
|
|
while (max_rx) {
|
|
struct sk_buff *rx_skb, *new_skb;
|
|
|
|
rx = (ramips_fe_rr(RAMIPS_RX_CALC_IDX0) + 1) % NUM_RX_DESC;
|
|
if (!(priv->rx[rx].rxd2 & RX_DMA_DONE))
|
|
break;
|
|
max_rx--;
|
|
|
|
rx_skb = priv->rx_skb[rx];
|
|
skb_put(rx_skb, RX_DMA_PLEN0(priv->rx[rx].rxd2));
|
|
rx_skb->dev = dev;
|
|
rx_skb->protocol = eth_type_trans(rx_skb, dev);
|
|
rx_skb->ip_summed = CHECKSUM_NONE;
|
|
dev->stats.rx_packets++;
|
|
dev->stats.rx_bytes += rx_skb->len;
|
|
netif_rx(rx_skb);
|
|
|
|
new_skb = netdev_alloc_skb(dev, MAX_RX_LENGTH + 2);
|
|
priv->rx_skb[rx] = new_skb;
|
|
BUG_ON(!new_skb);
|
|
skb_reserve(new_skb, 2);
|
|
priv->rx[rx].rxd1 = dma_map_single(NULL,
|
|
new_skb->data,
|
|
MAX_RX_LENGTH + 2,
|
|
DMA_FROM_DEVICE);
|
|
priv->rx[rx].rxd2 &= ~RX_DMA_DONE;
|
|
wmb();
|
|
ramips_fe_wr(rx, RAMIPS_RX_CALC_IDX0);
|
|
}
|
|
|
|
if (max_rx == 0)
|
|
tasklet_schedule(&priv->rx_tasklet);
|
|
else
|
|
ramips_fe_int_enable(RAMIPS_RX_DLY_INT);
|
|
}
|
|
|
|
static void
|
|
ramips_eth_tx_housekeeping(unsigned long ptr)
|
|
{
|
|
struct net_device *dev = (struct net_device*)ptr;
|
|
struct raeth_priv *priv = netdev_priv(dev);
|
|
|
|
while ((priv->tx[priv->skb_free_idx].txd2 & TX_DMA_DONE) &&
|
|
(priv->tx_skb[priv->skb_free_idx])) {
|
|
dev_kfree_skb_irq(priv->tx_skb[priv->skb_free_idx]);
|
|
priv->tx_skb[priv->skb_free_idx] = 0;
|
|
priv->skb_free_idx++;
|
|
if (priv->skb_free_idx >= NUM_TX_DESC)
|
|
priv->skb_free_idx = 0;
|
|
}
|
|
|
|
ramips_fe_int_enable(RAMIPS_TX_DLY_INT);
|
|
}
|
|
|
|
static void
|
|
ramips_eth_timeout(struct net_device *dev)
|
|
{
|
|
struct raeth_priv *priv = netdev_priv(dev);
|
|
|
|
tasklet_schedule(&priv->tx_housekeeping_tasklet);
|
|
}
|
|
|
|
static irqreturn_t
|
|
ramips_eth_irq(int irq, void *dev)
|
|
{
|
|
struct raeth_priv *priv = netdev_priv(dev);
|
|
unsigned long fe_int = ramips_fe_rr(RAMIPS_FE_INT_STATUS);
|
|
|
|
ramips_fe_wr(0xFFFFFFFF, RAMIPS_FE_INT_STATUS);
|
|
|
|
if (fe_int & RAMIPS_RX_DLY_INT) {
|
|
ramips_fe_int_disable(RAMIPS_RX_DLY_INT);
|
|
tasklet_schedule(&priv->rx_tasklet);
|
|
}
|
|
|
|
if (fe_int & RAMIPS_TX_DLY_INT)
|
|
ramips_eth_tx_housekeeping((unsigned long)dev);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int
|
|
ramips_eth_open(struct net_device *dev)
|
|
{
|
|
struct raeth_priv *priv = netdev_priv(dev);
|
|
int err;
|
|
|
|
err = request_irq(dev->irq, ramips_eth_irq, IRQF_DISABLED,
|
|
dev->name, dev);
|
|
if (err)
|
|
return err;
|
|
|
|
err = ramips_alloc_dma(priv);
|
|
if (err)
|
|
goto err_free_irq;
|
|
|
|
ramips_hw_set_macaddr(dev->dev_addr);
|
|
|
|
ramips_setup_dma(priv);
|
|
ramips_fe_wr((ramips_fe_rr(RAMIPS_PDMA_GLO_CFG) & 0xff) |
|
|
(RAMIPS_TX_WB_DDONE | RAMIPS_RX_DMA_EN |
|
|
RAMIPS_TX_DMA_EN | RAMIPS_PDMA_SIZE_4DWORDS),
|
|
RAMIPS_PDMA_GLO_CFG);
|
|
ramips_fe_wr((ramips_fe_rr(RAMIPS_FE_GLO_CFG) &
|
|
~(RAMIPS_US_CYC_CNT_MASK << RAMIPS_US_CYC_CNT_SHIFT)) |
|
|
((priv->plat->sys_freq / RAMIPS_US_CYC_CNT_DIVISOR) << RAMIPS_US_CYC_CNT_SHIFT),
|
|
RAMIPS_FE_GLO_CFG);
|
|
|
|
tasklet_init(&priv->tx_housekeeping_tasklet, ramips_eth_tx_housekeeping,
|
|
(unsigned long)dev);
|
|
tasklet_init(&priv->rx_tasklet, ramips_eth_rx_hw, (unsigned long)dev);
|
|
|
|
ramips_fe_wr(RAMIPS_DELAY_INIT, RAMIPS_DLY_INT_CFG);
|
|
ramips_fe_wr(RAMIPS_TX_DLY_INT | RAMIPS_RX_DLY_INT, RAMIPS_FE_INT_ENABLE);
|
|
ramips_fe_wr(ramips_fe_rr(RAMIPS_GDMA1_FWD_CFG) &
|
|
~(RAMIPS_GDM1_ICS_EN | RAMIPS_GDM1_TCS_EN | RAMIPS_GDM1_UCS_EN | 0xffff),
|
|
RAMIPS_GDMA1_FWD_CFG);
|
|
ramips_fe_wr(ramips_fe_rr(RAMIPS_CDMA_CSG_CFG) &
|
|
~(RAMIPS_ICS_GEN_EN | RAMIPS_TCS_GEN_EN | RAMIPS_UCS_GEN_EN),
|
|
RAMIPS_CDMA_CSG_CFG);
|
|
ramips_fe_wr(RAMIPS_PSE_FQFC_CFG_INIT, RAMIPS_PSE_FQ_CFG);
|
|
ramips_fe_wr(1, RAMIPS_FE_RST_GL);
|
|
ramips_fe_wr(0, RAMIPS_FE_RST_GL);
|
|
|
|
netif_start_queue(dev);
|
|
return 0;
|
|
|
|
err_free_irq:
|
|
free_irq(dev->irq, dev);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
ramips_eth_stop(struct net_device *dev)
|
|
{
|
|
struct raeth_priv *priv = netdev_priv(dev);
|
|
|
|
ramips_fe_wr(ramips_fe_rr(RAMIPS_PDMA_GLO_CFG) &
|
|
~(RAMIPS_TX_WB_DDONE | RAMIPS_RX_DMA_EN | RAMIPS_TX_DMA_EN),
|
|
RAMIPS_PDMA_GLO_CFG);
|
|
free_irq(dev->irq, dev);
|
|
netif_stop_queue(dev);
|
|
tasklet_kill(&priv->tx_housekeeping_tasklet);
|
|
tasklet_kill(&priv->rx_tasklet);
|
|
ramips_cleanup_dma(priv);
|
|
printk(KERN_DEBUG "ramips_eth: stopped\n");
|
|
return 0;
|
|
}
|
|
|
|
static int __init
|
|
ramips_eth_probe(struct net_device *dev)
|
|
{
|
|
struct raeth_priv *priv = netdev_priv(dev);
|
|
|
|
BUG_ON(!priv->plat->reset_fe);
|
|
priv->plat->reset_fe();
|
|
net_srandom(jiffies);
|
|
memcpy(dev->dev_addr, priv->plat->mac, ETH_ALEN);
|
|
|
|
ether_setup(dev);
|
|
dev->mtu = 1500;
|
|
dev->watchdog_timeo = TX_TIMEOUT;
|
|
spin_lock_init(&priv->page_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct net_device_ops ramips_eth_netdev_ops = {
|
|
.ndo_init = ramips_eth_probe,
|
|
.ndo_open = ramips_eth_open,
|
|
.ndo_stop = ramips_eth_stop,
|
|
.ndo_start_xmit = ramips_eth_hard_start_xmit,
|
|
.ndo_tx_timeout = ramips_eth_timeout,
|
|
.ndo_change_mtu = eth_change_mtu,
|
|
.ndo_set_mac_address = eth_mac_addr,
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
};
|
|
|
|
static int
|
|
ramips_eth_plat_probe(struct platform_device *plat)
|
|
{
|
|
struct raeth_priv *priv;
|
|
struct ramips_eth_platform_data *data = plat->dev.platform_data;
|
|
struct resource *res;
|
|
int err;
|
|
|
|
if (!data) {
|
|
dev_err(&plat->dev, "no platform data specified\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
res = platform_get_resource(plat, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(&plat->dev, "no memory resource found\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
ramips_fe_base = ioremap_nocache(res->start, res->end - res->start + 1);
|
|
if (!ramips_fe_base)
|
|
return -ENOMEM;
|
|
|
|
ramips_dev = alloc_etherdev(sizeof(struct raeth_priv));
|
|
if (!ramips_dev) {
|
|
dev_err(&plat->dev, "alloc_etherdev failed\n");
|
|
err = -ENOMEM;
|
|
goto err_unmap;
|
|
}
|
|
|
|
strcpy(ramips_dev->name, "eth%d");
|
|
ramips_dev->irq = platform_get_irq(plat, 0);
|
|
if (ramips_dev->irq < 0) {
|
|
dev_err(&plat->dev, "no IRQ resource found\n");
|
|
err = -ENXIO;
|
|
goto err_free_dev;
|
|
}
|
|
ramips_dev->addr_len = ETH_ALEN;
|
|
ramips_dev->base_addr = (unsigned long)ramips_fe_base;
|
|
ramips_dev->netdev_ops = &ramips_eth_netdev_ops;
|
|
|
|
priv = netdev_priv(ramips_dev);
|
|
priv->plat = data;
|
|
|
|
err = register_netdev(ramips_dev);
|
|
if (err) {
|
|
dev_err(&plat->dev, "error bringing up device\n");
|
|
goto err_free_dev;
|
|
}
|
|
|
|
#ifdef CONFIG_RALINK_RT305X
|
|
rt305x_esw_init();
|
|
#endif
|
|
printk(KERN_DEBUG "ramips_eth: loaded\n");
|
|
return 0;
|
|
|
|
err_free_dev:
|
|
kfree(ramips_dev);
|
|
err_unmap:
|
|
iounmap(ramips_fe_base);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
ramips_eth_plat_remove(struct platform_device *plat)
|
|
{
|
|
unregister_netdev(ramips_dev);
|
|
free_netdev(ramips_dev);
|
|
printk(KERN_DEBUG "ramips_eth: unloaded\n");
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver ramips_eth_driver = {
|
|
.probe = ramips_eth_plat_probe,
|
|
.remove = ramips_eth_plat_remove,
|
|
.driver = {
|
|
.name = "ramips_eth",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init
|
|
ramips_eth_init(void)
|
|
{
|
|
int ret = platform_driver_register(&ramips_eth_driver);
|
|
if (ret)
|
|
printk(KERN_ERR
|
|
"ramips_eth: Error registering platfom driver!\n");
|
|
return ret;
|
|
}
|
|
|
|
static void __exit
|
|
ramips_eth_cleanup(void)
|
|
{
|
|
platform_driver_unregister(&ramips_eth_driver);
|
|
}
|
|
|
|
module_init(ramips_eth_init);
|
|
module_exit(ramips_eth_cleanup);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
|
|
MODULE_DESCRIPTION("ethernet driver for ramips boards");
|