2005-06-20 17:54:21 +02:00
|
|
|
/*
|
2005-04-17 00:20:36 +02:00
|
|
|
* drivers/net/gianfar.c
|
|
|
|
*
|
|
|
|
* Gianfar Ethernet Driver
|
2005-11-11 19:38:59 +01:00
|
|
|
* This driver is designed for the non-CPM ethernet controllers
|
|
|
|
* on the 85xx and 83xx family of integrated processors
|
2005-04-17 00:20:36 +02:00
|
|
|
* Based on 8260_io/fcc_enet.c
|
|
|
|
*
|
|
|
|
* Author: Andy Fleming
|
2005-11-14 01:06:30 +01:00
|
|
|
* Maintainer: Kumar Gala
|
2005-04-17 00:20:36 +02:00
|
|
|
*
|
|
|
|
* Copyright (c) 2002-2004 Freescale Semiconductor, Inc.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* Gianfar: AKA Lambda Draconis, "Dragon"
|
|
|
|
* RA 11 31 24.2
|
|
|
|
* Dec +69 19 52
|
|
|
|
* V 3.84
|
|
|
|
* B-V +1.62
|
|
|
|
*
|
|
|
|
* Theory of operation
|
2005-06-20 17:54:21 +02:00
|
|
|
*
|
2005-04-17 00:20:36 +02:00
|
|
|
* The driver is initialized through platform_device. Structures which
|
|
|
|
* define the configuration needed by the board are defined in a
|
|
|
|
* board structure in arch/ppc/platforms (though I do not
|
|
|
|
* discount the possibility that other architectures could one
|
2005-09-24 04:54:21 +02:00
|
|
|
* day be supported.
|
2005-04-17 00:20:36 +02:00
|
|
|
*
|
|
|
|
* The Gianfar Ethernet Controller uses a ring of buffer
|
|
|
|
* descriptors. The beginning is indicated by a register
|
2005-06-20 17:54:21 +02:00
|
|
|
* pointing to the physical address of the start of the ring.
|
|
|
|
* The end is determined by a "wrap" bit being set in the
|
2005-04-17 00:20:36 +02:00
|
|
|
* last descriptor of the ring.
|
|
|
|
*
|
|
|
|
* When a packet is received, the RXF bit in the
|
2005-06-20 17:54:21 +02:00
|
|
|
* IEVENT register is set, triggering an interrupt when the
|
2005-04-17 00:20:36 +02:00
|
|
|
* corresponding bit in the IMASK register is also set (if
|
|
|
|
* interrupt coalescing is active, then the interrupt may not
|
|
|
|
* happen immediately, but will wait until either a set number
|
2005-09-24 04:54:21 +02:00
|
|
|
* of frames or amount of time have passed). In NAPI, the
|
2005-04-17 00:20:36 +02:00
|
|
|
* interrupt handler will signal there is work to be done, and
|
|
|
|
* exit. Without NAPI, the packet(s) will be handled
|
|
|
|
* immediately. Both methods will start at the last known empty
|
2005-06-20 17:54:21 +02:00
|
|
|
* descriptor, and process every subsequent descriptor until there
|
2005-04-17 00:20:36 +02:00
|
|
|
* are none left with data (NAPI will stop after a set number of
|
|
|
|
* packets to give time to other tasks, but will eventually
|
|
|
|
* process all the packets). The data arrives inside a
|
|
|
|
* pre-allocated skb, and so after the skb is passed up to the
|
|
|
|
* stack, a new skb must be allocated, and the address field in
|
|
|
|
* the buffer descriptor must be updated to indicate this new
|
|
|
|
* skb.
|
|
|
|
*
|
|
|
|
* When the kernel requests that a packet be transmitted, the
|
|
|
|
* driver starts where it left off last time, and points the
|
|
|
|
* descriptor at the buffer which was passed in. The driver
|
|
|
|
* then informs the DMA engine that there are packets ready to
|
|
|
|
* be transmitted. Once the controller is finished transmitting
|
|
|
|
* the packet, an interrupt may be triggered (under the same
|
|
|
|
* conditions as for reception, but depending on the TXF bit).
|
|
|
|
* The driver then cleans up the buffer.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/errno.h>
|
2005-09-24 04:54:21 +02:00
|
|
|
#include <linux/unistd.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include <linux/skbuff.h>
|
2005-06-20 17:54:21 +02:00
|
|
|
#include <linux/if_vlan.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/mm.h>
|
2005-10-29 20:07:23 +02:00
|
|
|
#include <linux/platform_device.h>
|
2005-06-20 17:54:21 +02:00
|
|
|
#include <linux/ip.h>
|
|
|
|
#include <linux/tcp.h>
|
|
|
|
#include <linux/udp.h>
|
2006-01-11 20:26:25 +01:00
|
|
|
#include <linux/in.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/irq.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
|
|
|
#include <linux/crc32.h>
|
2005-09-24 04:54:21 +02:00
|
|
|
#include <linux/mii.h>
|
|
|
|
#include <linux/phy.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#include "gianfar.h"
|
2005-09-24 04:54:21 +02:00
|
|
|
#include "gianfar_mii.h"
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#define TX_TIMEOUT (1*HZ)
|
|
|
|
#define SKB_ALLOC_TIMEOUT 1000000
|
|
|
|
#undef BRIEF_GFAR_ERRORS
|
|
|
|
#undef VERBOSE_GFAR_ERRORS
|
|
|
|
|
|
|
|
#ifdef CONFIG_GFAR_NAPI
|
|
|
|
#define RECEIVE(x) netif_receive_skb(x)
|
|
|
|
#else
|
|
|
|
#define RECEIVE(x) netif_rx(x)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
const char gfar_driver_name[] = "Gianfar Ethernet";
|
2005-11-11 19:38:59 +01:00
|
|
|
const char gfar_driver_version[] = "1.3";
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
static int gfar_enet_open(struct net_device *dev);
|
|
|
|
static int gfar_start_xmit(struct sk_buff *skb, struct net_device *dev);
|
|
|
|
static void gfar_timeout(struct net_device *dev);
|
|
|
|
static int gfar_close(struct net_device *dev);
|
|
|
|
struct sk_buff *gfar_new_skb(struct net_device *dev, struct rxbd8 *bdp);
|
|
|
|
static struct net_device_stats *gfar_get_stats(struct net_device *dev);
|
|
|
|
static int gfar_set_mac_address(struct net_device *dev);
|
|
|
|
static int gfar_change_mtu(struct net_device *dev, int new_mtu);
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
static irqreturn_t gfar_error(int irq, void *dev_id);
|
|
|
|
static irqreturn_t gfar_transmit(int irq, void *dev_id);
|
|
|
|
static irqreturn_t gfar_interrupt(int irq, void *dev_id);
|
2005-04-17 00:20:36 +02:00
|
|
|
static void adjust_link(struct net_device *dev);
|
|
|
|
static void init_registers(struct net_device *dev);
|
|
|
|
static int init_phy(struct net_device *dev);
|
2005-11-09 23:32:44 +01:00
|
|
|
static int gfar_probe(struct platform_device *pdev);
|
|
|
|
static int gfar_remove(struct platform_device *pdev);
|
2005-09-24 04:54:21 +02:00
|
|
|
static void free_skb_resources(struct gfar_private *priv);
|
2005-04-17 00:20:36 +02:00
|
|
|
static void gfar_set_multi(struct net_device *dev);
|
|
|
|
static void gfar_set_hash_for_addr(struct net_device *dev, u8 *addr);
|
|
|
|
#ifdef CONFIG_GFAR_NAPI
|
|
|
|
static int gfar_poll(struct net_device *dev, int *budget);
|
|
|
|
#endif
|
2005-06-20 17:54:21 +02:00
|
|
|
int gfar_clean_rx_ring(struct net_device *dev, int rx_work_limit);
|
2005-04-17 00:20:36 +02:00
|
|
|
static int gfar_process_frame(struct net_device *dev, struct sk_buff *skb, int length);
|
2005-06-20 17:54:21 +02:00
|
|
|
static void gfar_vlan_rx_register(struct net_device *netdev,
|
|
|
|
struct vlan_group *grp);
|
|
|
|
static void gfar_vlan_rx_kill_vid(struct net_device *netdev, uint16_t vid);
|
2005-11-11 19:38:59 +01:00
|
|
|
void gfar_halt(struct net_device *dev);
|
|
|
|
void gfar_start(struct net_device *dev);
|
|
|
|
static void gfar_clear_exact_match(struct net_device *dev);
|
|
|
|
static void gfar_set_mac_for_addr(struct net_device *dev, int num, u8 *addr);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-09-13 20:30:00 +02:00
|
|
|
extern const struct ethtool_ops gfar_ethtool_ops;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
MODULE_AUTHOR("Freescale Semiconductor, Inc");
|
|
|
|
MODULE_DESCRIPTION("Gianfar Ethernet Driver");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
/* Returns 1 if incoming frames use an FCB */
|
|
|
|
static inline int gfar_uses_fcb(struct gfar_private *priv)
|
2005-06-20 17:54:21 +02:00
|
|
|
{
|
2005-11-11 19:38:59 +01:00
|
|
|
return (priv->vlan_enable || priv->rx_csum_enable);
|
2005-06-20 17:54:21 +02:00
|
|
|
}
|
2005-09-24 04:54:21 +02:00
|
|
|
|
|
|
|
/* Set up the ethernet device structure, private data,
|
|
|
|
* and anything else we need before we start */
|
2005-11-09 23:32:44 +01:00
|
|
|
static int gfar_probe(struct platform_device *pdev)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
u32 tempval;
|
|
|
|
struct net_device *dev = NULL;
|
|
|
|
struct gfar_private *priv = NULL;
|
|
|
|
struct gianfar_platform_data *einfo;
|
|
|
|
struct resource *r;
|
|
|
|
int idx;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
einfo = (struct gianfar_platform_data *) pdev->dev.platform_data;
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (NULL == einfo) {
|
2005-04-17 00:20:36 +02:00
|
|
|
printk(KERN_ERR "gfar %d: Missing additional data!\n",
|
|
|
|
pdev->id);
|
|
|
|
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create an ethernet device instance */
|
|
|
|
dev = alloc_etherdev(sizeof (*priv));
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (NULL == dev)
|
2005-04-17 00:20:36 +02:00
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* Set the info in the priv to the current info */
|
|
|
|
priv->einfo = einfo;
|
|
|
|
|
|
|
|
/* fill out IRQ fields */
|
|
|
|
if (einfo->device_flags & FSL_GIANFAR_DEV_HAS_MULTI_INTR) {
|
|
|
|
priv->interruptTransmit = platform_get_irq_byname(pdev, "tx");
|
|
|
|
priv->interruptReceive = platform_get_irq_byname(pdev, "rx");
|
|
|
|
priv->interruptError = platform_get_irq_byname(pdev, "error");
|
2006-01-19 18:56:29 +01:00
|
|
|
if (priv->interruptTransmit < 0 || priv->interruptReceive < 0 || priv->interruptError < 0)
|
|
|
|
goto regs_fail;
|
2005-04-17 00:20:36 +02:00
|
|
|
} else {
|
|
|
|
priv->interruptTransmit = platform_get_irq(pdev, 0);
|
2006-01-19 18:56:29 +01:00
|
|
|
if (priv->interruptTransmit < 0)
|
|
|
|
goto regs_fail;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* get a pointer to the register memory */
|
|
|
|
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
2006-02-01 22:18:03 +01:00
|
|
|
priv->regs = ioremap(r->start, sizeof (struct gfar));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (NULL == priv->regs) {
|
2005-04-17 00:20:36 +02:00
|
|
|
err = -ENOMEM;
|
|
|
|
goto regs_fail;
|
|
|
|
}
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_lock_init(&priv->txlock);
|
|
|
|
spin_lock_init(&priv->rxlock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
platform_set_drvdata(pdev, dev);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Stop the DMA engine now, in case it was running before */
|
|
|
|
/* (The firmware could have used it, and left it running). */
|
|
|
|
/* To do this, we write Graceful Receive Stop and Graceful */
|
|
|
|
/* Transmit Stop, and then wait until the corresponding bits */
|
|
|
|
/* in IEVENT indicate the stops have completed. */
|
|
|
|
tempval = gfar_read(&priv->regs->dmactrl);
|
|
|
|
tempval &= ~(DMACTRL_GRS | DMACTRL_GTS);
|
|
|
|
gfar_write(&priv->regs->dmactrl, tempval);
|
|
|
|
|
|
|
|
tempval = gfar_read(&priv->regs->dmactrl);
|
|
|
|
tempval |= (DMACTRL_GRS | DMACTRL_GTS);
|
|
|
|
gfar_write(&priv->regs->dmactrl, tempval);
|
|
|
|
|
|
|
|
while (!(gfar_read(&priv->regs->ievent) & (IEVENT_GRSC | IEVENT_GTSC)))
|
|
|
|
cpu_relax();
|
|
|
|
|
|
|
|
/* Reset MAC layer */
|
|
|
|
gfar_write(&priv->regs->maccfg1, MACCFG1_SOFT_RESET);
|
|
|
|
|
|
|
|
tempval = (MACCFG1_TX_FLOW | MACCFG1_RX_FLOW);
|
|
|
|
gfar_write(&priv->regs->maccfg1, tempval);
|
|
|
|
|
|
|
|
/* Initialize MACCFG2. */
|
|
|
|
gfar_write(&priv->regs->maccfg2, MACCFG2_INIT_SETTINGS);
|
|
|
|
|
|
|
|
/* Initialize ECNTRL */
|
|
|
|
gfar_write(&priv->regs->ecntrl, ECNTRL_INIT_SETTINGS);
|
|
|
|
|
|
|
|
/* Copy the station address into the dev structure, */
|
|
|
|
memcpy(dev->dev_addr, einfo->mac_addr, MAC_ADDR_LEN);
|
|
|
|
|
|
|
|
/* Set the dev->base_addr to the gfar reg region */
|
|
|
|
dev->base_addr = (unsigned long) (priv->regs);
|
|
|
|
|
|
|
|
SET_MODULE_OWNER(dev);
|
2005-11-09 23:32:44 +01:00
|
|
|
SET_NETDEV_DEV(dev, &pdev->dev);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Fill in the dev structure */
|
|
|
|
dev->open = gfar_enet_open;
|
|
|
|
dev->hard_start_xmit = gfar_start_xmit;
|
|
|
|
dev->tx_timeout = gfar_timeout;
|
|
|
|
dev->watchdog_timeo = TX_TIMEOUT;
|
|
|
|
#ifdef CONFIG_GFAR_NAPI
|
|
|
|
dev->poll = gfar_poll;
|
|
|
|
dev->weight = GFAR_DEV_WEIGHT;
|
|
|
|
#endif
|
|
|
|
dev->stop = gfar_close;
|
|
|
|
dev->get_stats = gfar_get_stats;
|
|
|
|
dev->change_mtu = gfar_change_mtu;
|
|
|
|
dev->mtu = 1500;
|
|
|
|
dev->set_multicast_list = gfar_set_multi;
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
dev->ethtool_ops = &gfar_ethtool_ops;
|
|
|
|
|
|
|
|
if (priv->einfo->device_flags & FSL_GIANFAR_DEV_HAS_CSUM) {
|
|
|
|
priv->rx_csum_enable = 1;
|
|
|
|
dev->features |= NETIF_F_IP_CSUM;
|
|
|
|
} else
|
|
|
|
priv->rx_csum_enable = 0;
|
|
|
|
|
|
|
|
priv->vlgrp = NULL;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
if (priv->einfo->device_flags & FSL_GIANFAR_DEV_HAS_VLAN) {
|
|
|
|
dev->vlan_rx_register = gfar_vlan_rx_register;
|
|
|
|
dev->vlan_rx_kill_vid = gfar_vlan_rx_kill_vid;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
dev->features |= NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_RX;
|
|
|
|
|
|
|
|
priv->vlan_enable = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (priv->einfo->device_flags & FSL_GIANFAR_DEV_HAS_EXTENDED_HASH) {
|
|
|
|
priv->extended_hash = 1;
|
|
|
|
priv->hash_width = 9;
|
|
|
|
|
|
|
|
priv->hash_regs[0] = &priv->regs->igaddr0;
|
|
|
|
priv->hash_regs[1] = &priv->regs->igaddr1;
|
|
|
|
priv->hash_regs[2] = &priv->regs->igaddr2;
|
|
|
|
priv->hash_regs[3] = &priv->regs->igaddr3;
|
|
|
|
priv->hash_regs[4] = &priv->regs->igaddr4;
|
|
|
|
priv->hash_regs[5] = &priv->regs->igaddr5;
|
|
|
|
priv->hash_regs[6] = &priv->regs->igaddr6;
|
|
|
|
priv->hash_regs[7] = &priv->regs->igaddr7;
|
|
|
|
priv->hash_regs[8] = &priv->regs->gaddr0;
|
|
|
|
priv->hash_regs[9] = &priv->regs->gaddr1;
|
|
|
|
priv->hash_regs[10] = &priv->regs->gaddr2;
|
|
|
|
priv->hash_regs[11] = &priv->regs->gaddr3;
|
|
|
|
priv->hash_regs[12] = &priv->regs->gaddr4;
|
|
|
|
priv->hash_regs[13] = &priv->regs->gaddr5;
|
|
|
|
priv->hash_regs[14] = &priv->regs->gaddr6;
|
|
|
|
priv->hash_regs[15] = &priv->regs->gaddr7;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
priv->extended_hash = 0;
|
|
|
|
priv->hash_width = 8;
|
|
|
|
|
|
|
|
priv->hash_regs[0] = &priv->regs->gaddr0;
|
|
|
|
priv->hash_regs[1] = &priv->regs->gaddr1;
|
|
|
|
priv->hash_regs[2] = &priv->regs->gaddr2;
|
|
|
|
priv->hash_regs[3] = &priv->regs->gaddr3;
|
|
|
|
priv->hash_regs[4] = &priv->regs->gaddr4;
|
|
|
|
priv->hash_regs[5] = &priv->regs->gaddr5;
|
|
|
|
priv->hash_regs[6] = &priv->regs->gaddr6;
|
|
|
|
priv->hash_regs[7] = &priv->regs->gaddr7;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (priv->einfo->device_flags & FSL_GIANFAR_DEV_HAS_PADDING)
|
|
|
|
priv->padding = DEFAULT_PADDING;
|
|
|
|
else
|
|
|
|
priv->padding = 0;
|
|
|
|
|
|
|
|
if (dev->features & NETIF_F_IP_CSUM)
|
|
|
|
dev->hard_header_len += GMAC_FCB_LEN;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
priv->rx_buffer_size = DEFAULT_RX_BUFFER_SIZE;
|
|
|
|
priv->tx_ring_size = DEFAULT_TX_RING_SIZE;
|
|
|
|
priv->rx_ring_size = DEFAULT_RX_RING_SIZE;
|
|
|
|
|
|
|
|
priv->txcoalescing = DEFAULT_TX_COALESCE;
|
|
|
|
priv->txcount = DEFAULT_TXCOUNT;
|
|
|
|
priv->txtime = DEFAULT_TXTIME;
|
|
|
|
priv->rxcoalescing = DEFAULT_RX_COALESCE;
|
|
|
|
priv->rxcount = DEFAULT_RXCOUNT;
|
|
|
|
priv->rxtime = DEFAULT_RXTIME;
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Enable most messages by default */
|
|
|
|
priv->msg_enable = (NETIF_MSG_IFUP << 1 ) - 1;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
err = register_netdev(dev);
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
printk(KERN_ERR "%s: Cannot register net device, aborting.\n",
|
|
|
|
dev->name);
|
|
|
|
goto register_fail;
|
|
|
|
}
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
/* Create all the sysfs files */
|
|
|
|
gfar_init_sysfs(dev);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Print out the device info */
|
|
|
|
printk(KERN_INFO DEVICE_NAME, dev->name);
|
|
|
|
for (idx = 0; idx < 6; idx++)
|
|
|
|
printk("%2.2x%c", dev->dev_addr[idx], idx == 5 ? ' ' : ':');
|
|
|
|
printk("\n");
|
|
|
|
|
|
|
|
/* Even more device info helps when determining which kernel */
|
2005-11-11 19:38:59 +01:00
|
|
|
/* provided which set of benchmarks. */
|
2005-04-17 00:20:36 +02:00
|
|
|
#ifdef CONFIG_GFAR_NAPI
|
|
|
|
printk(KERN_INFO "%s: Running with NAPI enabled\n", dev->name);
|
|
|
|
#else
|
|
|
|
printk(KERN_INFO "%s: Running with NAPI disabled\n", dev->name);
|
|
|
|
#endif
|
|
|
|
printk(KERN_INFO "%s: %d/%d RX/TX BD ring size\n",
|
|
|
|
dev->name, priv->rx_ring_size, priv->tx_ring_size);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
register_fail:
|
2006-02-01 22:18:03 +01:00
|
|
|
iounmap(priv->regs);
|
2005-04-17 00:20:36 +02:00
|
|
|
regs_fail:
|
|
|
|
free_netdev(dev);
|
2005-09-24 04:54:21 +02:00
|
|
|
return err;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
static int gfar_remove(struct platform_device *pdev)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2005-11-09 23:32:44 +01:00
|
|
|
struct net_device *dev = platform_get_drvdata(pdev);
|
2005-04-17 00:20:36 +02:00
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
platform_set_drvdata(pdev, NULL);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-02-01 22:18:03 +01:00
|
|
|
iounmap(priv->regs);
|
2005-04-17 00:20:36 +02:00
|
|
|
free_netdev(dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
/* Initializes driver's PHY state, and attaches to the PHY.
|
|
|
|
* Returns 0 on success.
|
2005-04-17 00:20:36 +02:00
|
|
|
*/
|
|
|
|
static int init_phy(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2005-09-24 04:54:21 +02:00
|
|
|
uint gigabit_support =
|
|
|
|
priv->einfo->device_flags & FSL_GIANFAR_DEV_HAS_GIGABIT ?
|
|
|
|
SUPPORTED_1000baseT_Full : 0;
|
|
|
|
struct phy_device *phydev;
|
2006-01-11 20:27:33 +01:00
|
|
|
char phy_id[BUS_ID_SIZE];
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
priv->oldlink = 0;
|
|
|
|
priv->oldspeed = 0;
|
|
|
|
priv->oldduplex = -1;
|
|
|
|
|
2006-01-11 20:27:33 +01:00
|
|
|
snprintf(phy_id, BUS_ID_SIZE, PHY_ID_FMT, priv->einfo->bus_id, priv->einfo->phy_id);
|
|
|
|
|
|
|
|
phydev = phy_connect(dev, phy_id, &adjust_link, 0);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (IS_ERR(phydev)) {
|
|
|
|
printk(KERN_ERR "%s: Could not attach to PHY\n", dev->name);
|
|
|
|
return PTR_ERR(phydev);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
/* Remove any features not supported by the controller */
|
|
|
|
phydev->supported &= (GFAR_SUPPORTED | gigabit_support);
|
|
|
|
phydev->advertising = phydev->supported;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
priv->phydev = phydev;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void init_registers(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* Clear IEVENT */
|
|
|
|
gfar_write(&priv->regs->ievent, IEVENT_INIT_CLEAR);
|
|
|
|
|
|
|
|
/* Initialize IMASK */
|
|
|
|
gfar_write(&priv->regs->imask, IMASK_INIT_CLEAR);
|
|
|
|
|
|
|
|
/* Init hash registers to zero */
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_write(&priv->regs->igaddr0, 0);
|
|
|
|
gfar_write(&priv->regs->igaddr1, 0);
|
|
|
|
gfar_write(&priv->regs->igaddr2, 0);
|
|
|
|
gfar_write(&priv->regs->igaddr3, 0);
|
|
|
|
gfar_write(&priv->regs->igaddr4, 0);
|
|
|
|
gfar_write(&priv->regs->igaddr5, 0);
|
|
|
|
gfar_write(&priv->regs->igaddr6, 0);
|
|
|
|
gfar_write(&priv->regs->igaddr7, 0);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
gfar_write(&priv->regs->gaddr0, 0);
|
|
|
|
gfar_write(&priv->regs->gaddr1, 0);
|
|
|
|
gfar_write(&priv->regs->gaddr2, 0);
|
|
|
|
gfar_write(&priv->regs->gaddr3, 0);
|
|
|
|
gfar_write(&priv->regs->gaddr4, 0);
|
|
|
|
gfar_write(&priv->regs->gaddr5, 0);
|
|
|
|
gfar_write(&priv->regs->gaddr6, 0);
|
|
|
|
gfar_write(&priv->regs->gaddr7, 0);
|
|
|
|
|
|
|
|
/* Zero out the rmon mib registers if it has them */
|
|
|
|
if (priv->einfo->device_flags & FSL_GIANFAR_DEV_HAS_RMON) {
|
2006-02-01 22:18:03 +01:00
|
|
|
memset_io(&(priv->regs->rmon), 0, sizeof (struct rmon_mib));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Mask off the CAM interrupts */
|
|
|
|
gfar_write(&priv->regs->rmon.cam1, 0xffffffff);
|
|
|
|
gfar_write(&priv->regs->rmon.cam2, 0xffffffff);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize the max receive buffer length */
|
|
|
|
gfar_write(&priv->regs->mrblr, priv->rx_buffer_size);
|
|
|
|
|
|
|
|
/* Initialize the Minimum Frame Length Register */
|
|
|
|
gfar_write(&priv->regs->minflr, MINFLR_INIT_SETTINGS);
|
|
|
|
|
|
|
|
/* Assign the TBI an address which won't conflict with the PHYs */
|
|
|
|
gfar_write(&priv->regs->tbipa, TBIPA_VALUE);
|
|
|
|
}
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
|
|
|
|
/* Halt the receive and transmit queues */
|
|
|
|
void gfar_halt(struct net_device *dev)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2006-02-01 22:18:03 +01:00
|
|
|
struct gfar __iomem *regs = priv->regs;
|
2005-04-17 00:20:36 +02:00
|
|
|
u32 tempval;
|
|
|
|
|
|
|
|
/* Mask all interrupts */
|
|
|
|
gfar_write(®s->imask, IMASK_INIT_CLEAR);
|
|
|
|
|
|
|
|
/* Clear all interrupts */
|
|
|
|
gfar_write(®s->ievent, IEVENT_INIT_CLEAR);
|
|
|
|
|
|
|
|
/* Stop the DMA, and wait for it to stop */
|
|
|
|
tempval = gfar_read(&priv->regs->dmactrl);
|
|
|
|
if ((tempval & (DMACTRL_GRS | DMACTRL_GTS))
|
|
|
|
!= (DMACTRL_GRS | DMACTRL_GTS)) {
|
|
|
|
tempval |= (DMACTRL_GRS | DMACTRL_GTS);
|
|
|
|
gfar_write(&priv->regs->dmactrl, tempval);
|
|
|
|
|
|
|
|
while (!(gfar_read(&priv->regs->ievent) &
|
|
|
|
(IEVENT_GRSC | IEVENT_GTSC)))
|
|
|
|
cpu_relax();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Disable Rx and Tx */
|
|
|
|
tempval = gfar_read(®s->maccfg1);
|
|
|
|
tempval &= ~(MACCFG1_RX_EN | MACCFG1_TX_EN);
|
|
|
|
gfar_write(®s->maccfg1, tempval);
|
2005-06-20 17:54:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void stop_gfar(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2006-02-01 22:18:03 +01:00
|
|
|
struct gfar __iomem *regs = priv->regs;
|
2005-06-20 17:54:21 +02:00
|
|
|
unsigned long flags;
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
phy_stop(priv->phydev);
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Lock it down */
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_lock_irqsave(&priv->txlock, flags);
|
|
|
|
spin_lock(&priv->rxlock);
|
2005-06-20 17:54:21 +02:00
|
|
|
|
|
|
|
gfar_halt(dev);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_unlock(&priv->rxlock);
|
|
|
|
spin_unlock_irqrestore(&priv->txlock, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Free the IRQs */
|
|
|
|
if (priv->einfo->device_flags & FSL_GIANFAR_DEV_HAS_MULTI_INTR) {
|
|
|
|
free_irq(priv->interruptError, dev);
|
|
|
|
free_irq(priv->interruptTransmit, dev);
|
|
|
|
free_irq(priv->interruptReceive, dev);
|
|
|
|
} else {
|
2005-09-24 04:54:21 +02:00
|
|
|
free_irq(priv->interruptTransmit, dev);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
free_skb_resources(priv);
|
|
|
|
|
|
|
|
dma_free_coherent(NULL,
|
|
|
|
sizeof(struct txbd8)*priv->tx_ring_size
|
|
|
|
+ sizeof(struct rxbd8)*priv->rx_ring_size,
|
|
|
|
priv->tx_bd_base,
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_read(®s->tbase0));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* If there are any tx skbs or rx skbs still around, free them.
|
|
|
|
* Then free tx_skbuff and rx_skbuff */
|
2005-09-24 04:54:21 +02:00
|
|
|
static void free_skb_resources(struct gfar_private *priv)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct rxbd8 *rxbdp;
|
|
|
|
struct txbd8 *txbdp;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Go through all the buffer descriptors and free their data buffers */
|
|
|
|
txbdp = priv->tx_bd_base;
|
|
|
|
|
|
|
|
for (i = 0; i < priv->tx_ring_size; i++) {
|
|
|
|
|
|
|
|
if (priv->tx_skbuff[i]) {
|
|
|
|
dma_unmap_single(NULL, txbdp->bufPtr,
|
|
|
|
txbdp->length,
|
|
|
|
DMA_TO_DEVICE);
|
|
|
|
dev_kfree_skb_any(priv->tx_skbuff[i]);
|
|
|
|
priv->tx_skbuff[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(priv->tx_skbuff);
|
|
|
|
|
|
|
|
rxbdp = priv->rx_bd_base;
|
|
|
|
|
|
|
|
/* rx_skbuff is not guaranteed to be allocated, so only
|
|
|
|
* free it and its contents if it is allocated */
|
|
|
|
if(priv->rx_skbuff != NULL) {
|
|
|
|
for (i = 0; i < priv->rx_ring_size; i++) {
|
|
|
|
if (priv->rx_skbuff[i]) {
|
|
|
|
dma_unmap_single(NULL, rxbdp->bufPtr,
|
2005-11-11 19:38:59 +01:00
|
|
|
priv->rx_buffer_size,
|
2005-04-17 00:20:36 +02:00
|
|
|
DMA_FROM_DEVICE);
|
|
|
|
|
|
|
|
dev_kfree_skb_any(priv->rx_skbuff[i]);
|
|
|
|
priv->rx_skbuff[i] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rxbdp->status = 0;
|
|
|
|
rxbdp->length = 0;
|
|
|
|
rxbdp->bufPtr = 0;
|
|
|
|
|
|
|
|
rxbdp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(priv->rx_skbuff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
void gfar_start(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2006-02-01 22:18:03 +01:00
|
|
|
struct gfar __iomem *regs = priv->regs;
|
2005-06-20 17:54:21 +02:00
|
|
|
u32 tempval;
|
|
|
|
|
|
|
|
/* Enable Rx and Tx in MACCFG1 */
|
|
|
|
tempval = gfar_read(®s->maccfg1);
|
|
|
|
tempval |= (MACCFG1_RX_EN | MACCFG1_TX_EN);
|
|
|
|
gfar_write(®s->maccfg1, tempval);
|
|
|
|
|
|
|
|
/* Initialize DMACTRL to have WWR and WOP */
|
|
|
|
tempval = gfar_read(&priv->regs->dmactrl);
|
|
|
|
tempval |= DMACTRL_INIT_SETTINGS;
|
|
|
|
gfar_write(&priv->regs->dmactrl, tempval);
|
|
|
|
|
|
|
|
/* Make sure we aren't stopped */
|
|
|
|
tempval = gfar_read(&priv->regs->dmactrl);
|
|
|
|
tempval &= ~(DMACTRL_GRS | DMACTRL_GTS);
|
|
|
|
gfar_write(&priv->regs->dmactrl, tempval);
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
/* Clear THLT/RHLT, so that the DMA starts polling now */
|
|
|
|
gfar_write(®s->tstat, TSTAT_CLEAR_THALT);
|
|
|
|
gfar_write(®s->rstat, RSTAT_CLEAR_RHALT);
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Unmask the interrupts we look for */
|
|
|
|
gfar_write(®s->imask, IMASK_DEFAULT);
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Bring the controller up and running */
|
|
|
|
int startup_gfar(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct txbd8 *txbdp;
|
|
|
|
struct rxbd8 *rxbdp;
|
|
|
|
dma_addr_t addr;
|
|
|
|
unsigned long vaddr;
|
|
|
|
int i;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2006-02-01 22:18:03 +01:00
|
|
|
struct gfar __iomem *regs = priv->regs;
|
2005-04-17 00:20:36 +02:00
|
|
|
int err = 0;
|
2005-06-20 17:54:21 +02:00
|
|
|
u32 rctrl = 0;
|
2005-11-11 19:38:59 +01:00
|
|
|
u32 attrs = 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
gfar_write(®s->imask, IMASK_INIT_CLEAR);
|
|
|
|
|
|
|
|
/* Allocate memory for the buffer descriptors */
|
2005-06-20 17:54:21 +02:00
|
|
|
vaddr = (unsigned long) dma_alloc_coherent(NULL,
|
2005-04-17 00:20:36 +02:00
|
|
|
sizeof (struct txbd8) * priv->tx_ring_size +
|
|
|
|
sizeof (struct rxbd8) * priv->rx_ring_size,
|
|
|
|
&addr, GFP_KERNEL);
|
|
|
|
|
|
|
|
if (vaddr == 0) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_ifup(priv))
|
|
|
|
printk(KERN_ERR "%s: Could not allocate buffer descriptors!\n",
|
|
|
|
dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->tx_bd_base = (struct txbd8 *) vaddr;
|
|
|
|
|
|
|
|
/* enet DMA only understands physical addresses */
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_write(®s->tbase0, addr);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Start the rx descriptor ring where the tx ring leaves off */
|
|
|
|
addr = addr + sizeof (struct txbd8) * priv->tx_ring_size;
|
|
|
|
vaddr = vaddr + sizeof (struct txbd8) * priv->tx_ring_size;
|
|
|
|
priv->rx_bd_base = (struct rxbd8 *) vaddr;
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_write(®s->rbase0, addr);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Setup the skbuff rings */
|
|
|
|
priv->tx_skbuff =
|
|
|
|
(struct sk_buff **) kmalloc(sizeof (struct sk_buff *) *
|
|
|
|
priv->tx_ring_size, GFP_KERNEL);
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (NULL == priv->tx_skbuff) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_ifup(priv))
|
|
|
|
printk(KERN_ERR "%s: Could not allocate tx_skbuff\n",
|
|
|
|
dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
err = -ENOMEM;
|
|
|
|
goto tx_skb_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < priv->tx_ring_size; i++)
|
|
|
|
priv->tx_skbuff[i] = NULL;
|
|
|
|
|
|
|
|
priv->rx_skbuff =
|
|
|
|
(struct sk_buff **) kmalloc(sizeof (struct sk_buff *) *
|
|
|
|
priv->rx_ring_size, GFP_KERNEL);
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (NULL == priv->rx_skbuff) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_ifup(priv))
|
|
|
|
printk(KERN_ERR "%s: Could not allocate rx_skbuff\n",
|
|
|
|
dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
err = -ENOMEM;
|
|
|
|
goto rx_skb_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < priv->rx_ring_size; i++)
|
|
|
|
priv->rx_skbuff[i] = NULL;
|
|
|
|
|
|
|
|
/* Initialize some variables in our dev structure */
|
|
|
|
priv->dirty_tx = priv->cur_tx = priv->tx_bd_base;
|
|
|
|
priv->cur_rx = priv->rx_bd_base;
|
|
|
|
priv->skb_curtx = priv->skb_dirtytx = 0;
|
|
|
|
priv->skb_currx = 0;
|
|
|
|
|
|
|
|
/* Initialize Transmit Descriptor Ring */
|
|
|
|
txbdp = priv->tx_bd_base;
|
|
|
|
for (i = 0; i < priv->tx_ring_size; i++) {
|
|
|
|
txbdp->status = 0;
|
|
|
|
txbdp->length = 0;
|
|
|
|
txbdp->bufPtr = 0;
|
|
|
|
txbdp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the last descriptor in the ring to indicate wrap */
|
|
|
|
txbdp--;
|
|
|
|
txbdp->status |= TXBD_WRAP;
|
|
|
|
|
|
|
|
rxbdp = priv->rx_bd_base;
|
|
|
|
for (i = 0; i < priv->rx_ring_size; i++) {
|
|
|
|
struct sk_buff *skb = NULL;
|
|
|
|
|
|
|
|
rxbdp->status = 0;
|
|
|
|
|
|
|
|
skb = gfar_new_skb(dev, rxbdp);
|
|
|
|
|
|
|
|
priv->rx_skbuff[i] = skb;
|
|
|
|
|
|
|
|
rxbdp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the last descriptor in the ring to wrap */
|
|
|
|
rxbdp--;
|
|
|
|
rxbdp->status |= RXBD_WRAP;
|
|
|
|
|
|
|
|
/* If the device has multiple interrupts, register for
|
|
|
|
* them. Otherwise, only register for the one */
|
|
|
|
if (priv->einfo->device_flags & FSL_GIANFAR_DEV_HAS_MULTI_INTR) {
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Install our interrupt handlers for Error,
|
2005-04-17 00:20:36 +02:00
|
|
|
* Transmit, and Receive */
|
|
|
|
if (request_irq(priv->interruptError, gfar_error,
|
|
|
|
0, "enet_error", dev) < 0) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_intr(priv))
|
|
|
|
printk(KERN_ERR "%s: Can't get IRQ %d\n",
|
|
|
|
dev->name, priv->interruptError);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
err = -1;
|
|
|
|
goto err_irq_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (request_irq(priv->interruptTransmit, gfar_transmit,
|
|
|
|
0, "enet_tx", dev) < 0) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_intr(priv))
|
|
|
|
printk(KERN_ERR "%s: Can't get IRQ %d\n",
|
|
|
|
dev->name, priv->interruptTransmit);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
err = -1;
|
|
|
|
|
|
|
|
goto tx_irq_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (request_irq(priv->interruptReceive, gfar_receive,
|
|
|
|
0, "enet_rx", dev) < 0) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_intr(priv))
|
|
|
|
printk(KERN_ERR "%s: Can't get IRQ %d (receive0)\n",
|
|
|
|
dev->name, priv->interruptReceive);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
err = -1;
|
|
|
|
goto rx_irq_fail;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (request_irq(priv->interruptTransmit, gfar_interrupt,
|
|
|
|
0, "gfar_interrupt", dev) < 0) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_intr(priv))
|
|
|
|
printk(KERN_ERR "%s: Can't get IRQ %d\n",
|
|
|
|
dev->name, priv->interruptError);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
err = -1;
|
|
|
|
goto err_irq_fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
phy_start(priv->phydev);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Configure the coalescing support */
|
|
|
|
if (priv->txcoalescing)
|
|
|
|
gfar_write(®s->txic,
|
|
|
|
mk_ic_value(priv->txcount, priv->txtime));
|
|
|
|
else
|
|
|
|
gfar_write(®s->txic, 0);
|
|
|
|
|
|
|
|
if (priv->rxcoalescing)
|
|
|
|
gfar_write(®s->rxic,
|
|
|
|
mk_ic_value(priv->rxcount, priv->rxtime));
|
|
|
|
else
|
|
|
|
gfar_write(®s->rxic, 0);
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
if (priv->rx_csum_enable)
|
|
|
|
rctrl |= RCTRL_CHECKSUMMING;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
if (priv->extended_hash) {
|
2005-06-20 17:54:21 +02:00
|
|
|
rctrl |= RCTRL_EXTHASH;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
gfar_clear_exact_match(dev);
|
|
|
|
rctrl |= RCTRL_EMEN;
|
|
|
|
}
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
if (priv->vlan_enable)
|
|
|
|
rctrl |= RCTRL_VLAN;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
if (priv->padding) {
|
|
|
|
rctrl &= ~RCTRL_PAL_MASK;
|
|
|
|
rctrl |= RCTRL_PADDING(priv->padding);
|
|
|
|
}
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Init rctrl based on our settings */
|
|
|
|
gfar_write(&priv->regs->rctrl, rctrl);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
if (dev->features & NETIF_F_IP_CSUM)
|
|
|
|
gfar_write(&priv->regs->tctrl, TCTRL_INIT_CSUM);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
/* Set the extraction length and index */
|
|
|
|
attrs = ATTRELI_EL(priv->rx_stash_size) |
|
|
|
|
ATTRELI_EI(priv->rx_stash_index);
|
|
|
|
|
|
|
|
gfar_write(&priv->regs->attreli, attrs);
|
|
|
|
|
|
|
|
/* Start with defaults, and add stashing or locking
|
|
|
|
* depending on the approprate variables */
|
|
|
|
attrs = ATTR_INIT_SETTINGS;
|
|
|
|
|
|
|
|
if (priv->bd_stash_en)
|
|
|
|
attrs |= ATTR_BDSTASH;
|
|
|
|
|
|
|
|
if (priv->rx_stash_size != 0)
|
|
|
|
attrs |= ATTR_BUFSTASH;
|
|
|
|
|
|
|
|
gfar_write(&priv->regs->attr, attrs);
|
|
|
|
|
|
|
|
gfar_write(&priv->regs->fifo_tx_thr, priv->fifo_threshold);
|
|
|
|
gfar_write(&priv->regs->fifo_tx_starve, priv->fifo_starve);
|
|
|
|
gfar_write(&priv->regs->fifo_tx_starve_shutoff, priv->fifo_starve_off);
|
|
|
|
|
|
|
|
/* Start the controller */
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_start(dev);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
rx_irq_fail:
|
|
|
|
free_irq(priv->interruptTransmit, dev);
|
|
|
|
tx_irq_fail:
|
|
|
|
free_irq(priv->interruptError, dev);
|
|
|
|
err_irq_fail:
|
|
|
|
rx_skb_fail:
|
|
|
|
free_skb_resources(priv);
|
|
|
|
tx_skb_fail:
|
|
|
|
dma_free_coherent(NULL,
|
|
|
|
sizeof(struct txbd8)*priv->tx_ring_size
|
|
|
|
+ sizeof(struct rxbd8)*priv->rx_ring_size,
|
|
|
|
priv->tx_bd_base,
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_read(®s->tbase0));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Called when something needs to use the ethernet device */
|
|
|
|
/* Returns 0 for success. */
|
|
|
|
static int gfar_enet_open(struct net_device *dev)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* Initialize a bunch of registers */
|
|
|
|
init_registers(dev);
|
|
|
|
|
|
|
|
gfar_set_mac_address(dev);
|
|
|
|
|
|
|
|
err = init_phy(dev);
|
|
|
|
|
|
|
|
if(err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = startup_gfar(dev);
|
|
|
|
|
|
|
|
netif_start_queue(dev);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
static inline struct txfcb *gfar_add_fcb(struct sk_buff *skb, struct txbd8 *bdp)
|
2005-06-20 17:54:21 +02:00
|
|
|
{
|
|
|
|
struct txfcb *fcb = (struct txfcb *)skb_push (skb, GMAC_FCB_LEN);
|
|
|
|
|
|
|
|
memset(fcb, 0, GMAC_FCB_LEN);
|
|
|
|
|
|
|
|
return fcb;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void gfar_tx_checksum(struct sk_buff *skb, struct txfcb *fcb)
|
|
|
|
{
|
2005-11-11 19:38:59 +01:00
|
|
|
u8 flags = 0;
|
2005-06-20 17:54:21 +02:00
|
|
|
|
|
|
|
/* If we're here, it's a IP packet with a TCP or UDP
|
|
|
|
* payload. We set it to checksum, using a pseudo-header
|
|
|
|
* we provide
|
|
|
|
*/
|
2005-11-11 19:38:59 +01:00
|
|
|
flags = TXFCB_DEFAULT;
|
2005-06-20 17:54:21 +02:00
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
/* Tell the controller what the protocol is */
|
|
|
|
/* And provide the already calculated phcs */
|
|
|
|
if (skb->nh.iph->protocol == IPPROTO_UDP) {
|
|
|
|
flags |= TXFCB_UDP;
|
|
|
|
fcb->phcs = skb->h.uh->check;
|
|
|
|
} else
|
|
|
|
fcb->phcs = skb->h.th->check;
|
2005-06-20 17:54:21 +02:00
|
|
|
|
|
|
|
/* l3os is the distance between the start of the
|
|
|
|
* frame (skb->data) and the start of the IP hdr.
|
|
|
|
* l4os is the distance between the start of the
|
|
|
|
* l3 hdr and the l4 hdr */
|
|
|
|
fcb->l3os = (u16)(skb->nh.raw - skb->data - GMAC_FCB_LEN);
|
|
|
|
fcb->l4os = (u16)(skb->h.raw - skb->nh.raw);
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
fcb->flags = flags;
|
2005-06-20 17:54:21 +02:00
|
|
|
}
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
void inline gfar_tx_vlan(struct sk_buff *skb, struct txfcb *fcb)
|
2005-06-20 17:54:21 +02:00
|
|
|
{
|
2005-11-11 19:38:59 +01:00
|
|
|
fcb->flags |= TXFCB_VLN;
|
2005-06-20 17:54:21 +02:00
|
|
|
fcb->vlctl = vlan_tx_tag_get(skb);
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* This is called by the kernel when a frame is ready for transmission. */
|
|
|
|
/* It is pointed to by the dev->hard_start_xmit function pointer */
|
|
|
|
static int gfar_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2005-06-20 17:54:21 +02:00
|
|
|
struct txfcb *fcb = NULL;
|
2005-04-17 00:20:36 +02:00
|
|
|
struct txbd8 *txbdp;
|
2005-11-11 19:38:59 +01:00
|
|
|
u16 status;
|
2006-04-20 23:44:29 +02:00
|
|
|
unsigned long flags;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Update transmit stats */
|
|
|
|
priv->stats.tx_bytes += skb->len;
|
|
|
|
|
|
|
|
/* Lock priv now */
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_lock_irqsave(&priv->txlock, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Point at the first free tx descriptor */
|
|
|
|
txbdp = priv->cur_tx;
|
|
|
|
|
|
|
|
/* Clear all but the WRAP status flags */
|
2005-11-11 19:38:59 +01:00
|
|
|
status = txbdp->status & TXBD_WRAP;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Set up checksumming */
|
2005-11-11 19:38:59 +01:00
|
|
|
if (likely((dev->features & NETIF_F_IP_CSUM)
|
2006-08-30 01:44:56 +02:00
|
|
|
&& (CHECKSUM_PARTIAL == skb->ip_summed))) {
|
2005-06-20 17:54:21 +02:00
|
|
|
fcb = gfar_add_fcb(skb, txbdp);
|
2005-11-11 19:38:59 +01:00
|
|
|
status |= TXBD_TOE;
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_tx_checksum(skb, fcb);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (priv->vlan_enable &&
|
|
|
|
unlikely(priv->vlgrp && vlan_tx_tag_present(skb))) {
|
2005-11-11 19:38:59 +01:00
|
|
|
if (unlikely(NULL == fcb)) {
|
2005-06-20 17:54:21 +02:00
|
|
|
fcb = gfar_add_fcb(skb, txbdp);
|
2005-11-11 19:38:59 +01:00
|
|
|
status |= TXBD_TOE;
|
|
|
|
}
|
2005-06-20 17:54:21 +02:00
|
|
|
|
|
|
|
gfar_tx_vlan(skb, fcb);
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Set buffer length and pointer */
|
|
|
|
txbdp->length = skb->len;
|
2005-06-20 17:54:21 +02:00
|
|
|
txbdp->bufPtr = dma_map_single(NULL, skb->data,
|
2005-04-17 00:20:36 +02:00
|
|
|
skb->len, DMA_TO_DEVICE);
|
|
|
|
|
|
|
|
/* Save the skb pointer so we can free it later */
|
|
|
|
priv->tx_skbuff[priv->skb_curtx] = skb;
|
|
|
|
|
|
|
|
/* Update the current skb pointer (wrapping if this was the last) */
|
|
|
|
priv->skb_curtx =
|
|
|
|
(priv->skb_curtx + 1) & TX_RING_MOD_MASK(priv->tx_ring_size);
|
|
|
|
|
|
|
|
/* Flag the BD as interrupt-causing */
|
2005-11-11 19:38:59 +01:00
|
|
|
status |= TXBD_INTERRUPT;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Flag the BD as ready to go, last in frame, and */
|
|
|
|
/* in need of CRC */
|
2005-11-11 19:38:59 +01:00
|
|
|
status |= (TXBD_READY | TXBD_LAST | TXBD_CRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
dev->trans_start = jiffies;
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
txbdp->status = status;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* If this was the last BD in the ring, the next one */
|
|
|
|
/* is at the beginning of the ring */
|
|
|
|
if (txbdp->status & TXBD_WRAP)
|
|
|
|
txbdp = priv->tx_bd_base;
|
|
|
|
else
|
|
|
|
txbdp++;
|
|
|
|
|
|
|
|
/* If the next BD still needs to be cleaned up, then the bds
|
|
|
|
are full. We need to tell the kernel to stop sending us stuff. */
|
|
|
|
if (txbdp == priv->dirty_tx) {
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
|
|
|
|
priv->stats.tx_fifo_errors++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Update the current txbd to the next one */
|
|
|
|
priv->cur_tx = txbdp;
|
|
|
|
|
|
|
|
/* Tell the DMA to go go go */
|
|
|
|
gfar_write(&priv->regs->tstat, TSTAT_CLEAR_THALT);
|
|
|
|
|
|
|
|
/* Unlock priv */
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_unlock_irqrestore(&priv->txlock, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stops the kernel queue, and halts the controller */
|
|
|
|
static int gfar_close(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
stop_gfar(dev);
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
/* Disconnect from the PHY */
|
|
|
|
phy_disconnect(priv->phydev);
|
|
|
|
priv->phydev = NULL;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* returns a net_device_stats structure pointer */
|
|
|
|
static struct net_device_stats * gfar_get_stats(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
return &(priv->stats);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Changes the mac address if the controller is not running. */
|
|
|
|
int gfar_set_mac_address(struct net_device *dev)
|
|
|
|
{
|
2005-11-11 19:38:59 +01:00
|
|
|
gfar_set_mac_for_addr(dev, 0, dev->dev_addr);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Enables and disables VLAN insertion/extraction */
|
|
|
|
static void gfar_vlan_rx_register(struct net_device *dev,
|
|
|
|
struct vlan_group *grp)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
unsigned long flags;
|
|
|
|
u32 tempval;
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_lock_irqsave(&priv->rxlock, flags);
|
2005-06-20 17:54:21 +02:00
|
|
|
|
|
|
|
priv->vlgrp = grp;
|
|
|
|
|
|
|
|
if (grp) {
|
|
|
|
/* Enable VLAN tag insertion */
|
|
|
|
tempval = gfar_read(&priv->regs->tctrl);
|
|
|
|
tempval |= TCTRL_VLINS;
|
|
|
|
|
|
|
|
gfar_write(&priv->regs->tctrl, tempval);
|
2006-09-13 19:24:59 +02:00
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Enable VLAN tag extraction */
|
|
|
|
tempval = gfar_read(&priv->regs->rctrl);
|
|
|
|
tempval |= RCTRL_VLEX;
|
|
|
|
gfar_write(&priv->regs->rctrl, tempval);
|
|
|
|
} else {
|
|
|
|
/* Disable VLAN tag insertion */
|
|
|
|
tempval = gfar_read(&priv->regs->tctrl);
|
|
|
|
tempval &= ~TCTRL_VLINS;
|
|
|
|
gfar_write(&priv->regs->tctrl, tempval);
|
|
|
|
|
|
|
|
/* Disable VLAN tag extraction */
|
|
|
|
tempval = gfar_read(&priv->regs->rctrl);
|
|
|
|
tempval &= ~RCTRL_VLEX;
|
|
|
|
gfar_write(&priv->regs->rctrl, tempval);
|
|
|
|
}
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_unlock_irqrestore(&priv->rxlock, flags);
|
2005-06-20 17:54:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void gfar_vlan_rx_kill_vid(struct net_device *dev, uint16_t vid)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
unsigned long flags;
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_lock_irqsave(&priv->rxlock, flags);
|
2005-06-20 17:54:21 +02:00
|
|
|
|
|
|
|
if (priv->vlgrp)
|
|
|
|
priv->vlgrp->vlan_devices[vid] = NULL;
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_unlock_irqrestore(&priv->rxlock, flags);
|
2005-06-20 17:54:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
static int gfar_change_mtu(struct net_device *dev, int new_mtu)
|
|
|
|
{
|
|
|
|
int tempsize, tempval;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
int oldsize = priv->rx_buffer_size;
|
2005-06-20 17:54:21 +02:00
|
|
|
int frame_size = new_mtu + ETH_HLEN;
|
|
|
|
|
|
|
|
if (priv->vlan_enable)
|
|
|
|
frame_size += VLAN_ETH_HLEN;
|
|
|
|
|
|
|
|
if (gfar_uses_fcb(priv))
|
|
|
|
frame_size += GMAC_FCB_LEN;
|
|
|
|
|
|
|
|
frame_size += priv->padding;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if ((frame_size < 64) || (frame_size > JUMBO_FRAME_SIZE)) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_drv(priv))
|
|
|
|
printk(KERN_ERR "%s: Invalid MTU setting\n",
|
|
|
|
dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
tempsize =
|
|
|
|
(frame_size & ~(INCREMENTAL_BUFFER_SIZE - 1)) +
|
|
|
|
INCREMENTAL_BUFFER_SIZE;
|
|
|
|
|
|
|
|
/* Only stop and start the controller if it isn't already
|
2005-11-11 19:38:59 +01:00
|
|
|
* stopped, and we changed something */
|
2005-04-17 00:20:36 +02:00
|
|
|
if ((oldsize != tempsize) && (dev->flags & IFF_UP))
|
|
|
|
stop_gfar(dev);
|
|
|
|
|
|
|
|
priv->rx_buffer_size = tempsize;
|
|
|
|
|
|
|
|
dev->mtu = new_mtu;
|
|
|
|
|
|
|
|
gfar_write(&priv->regs->mrblr, priv->rx_buffer_size);
|
|
|
|
gfar_write(&priv->regs->maxfrm, priv->rx_buffer_size);
|
|
|
|
|
|
|
|
/* If the mtu is larger than the max size for standard
|
|
|
|
* ethernet frames (ie, a jumbo frame), then set maccfg2
|
|
|
|
* to allow huge frames, and to check the length */
|
|
|
|
tempval = gfar_read(&priv->regs->maccfg2);
|
|
|
|
|
|
|
|
if (priv->rx_buffer_size > DEFAULT_RX_BUFFER_SIZE)
|
|
|
|
tempval |= (MACCFG2_HUGEFRAME | MACCFG2_LENGTHCHECK);
|
|
|
|
else
|
|
|
|
tempval &= ~(MACCFG2_HUGEFRAME | MACCFG2_LENGTHCHECK);
|
|
|
|
|
|
|
|
gfar_write(&priv->regs->maccfg2, tempval);
|
|
|
|
|
|
|
|
if ((oldsize != tempsize) && (dev->flags & IFF_UP))
|
|
|
|
startup_gfar(dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* gfar_timeout gets called when a packet has not been
|
|
|
|
* transmitted after a set amount of time.
|
|
|
|
* For now, assume that clearing out all the structures, and
|
|
|
|
* starting over will fix the problem. */
|
|
|
|
static void gfar_timeout(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
priv->stats.tx_errors++;
|
|
|
|
|
|
|
|
if (dev->flags & IFF_UP) {
|
|
|
|
stop_gfar(dev);
|
|
|
|
startup_gfar(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
netif_schedule(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Interrupt Handler for Transmit complete */
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
static irqreturn_t gfar_transmit(int irq, void *dev_id)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct net_device *dev = (struct net_device *) dev_id;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
struct txbd8 *bdp;
|
|
|
|
|
|
|
|
/* Clear IEVENT */
|
|
|
|
gfar_write(&priv->regs->ievent, IEVENT_TX_MASK);
|
|
|
|
|
|
|
|
/* Lock priv */
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_lock(&priv->txlock);
|
2005-04-17 00:20:36 +02:00
|
|
|
bdp = priv->dirty_tx;
|
|
|
|
while ((bdp->status & TXBD_READY) == 0) {
|
|
|
|
/* If dirty_tx and cur_tx are the same, then either the */
|
|
|
|
/* ring is empty or full now (it could only be full in the beginning, */
|
|
|
|
/* obviously). If it is empty, we are done. */
|
|
|
|
if ((bdp == priv->cur_tx) && (netif_queue_stopped(dev) == 0))
|
|
|
|
break;
|
|
|
|
|
|
|
|
priv->stats.tx_packets++;
|
|
|
|
|
|
|
|
/* Deferred means some collisions occurred during transmit, */
|
|
|
|
/* but we eventually sent the packet. */
|
|
|
|
if (bdp->status & TXBD_DEF)
|
|
|
|
priv->stats.collisions++;
|
|
|
|
|
|
|
|
/* Free the sk buffer associated with this TxBD */
|
|
|
|
dev_kfree_skb_irq(priv->tx_skbuff[priv->skb_dirtytx]);
|
|
|
|
priv->tx_skbuff[priv->skb_dirtytx] = NULL;
|
|
|
|
priv->skb_dirtytx =
|
|
|
|
(priv->skb_dirtytx +
|
|
|
|
1) & TX_RING_MOD_MASK(priv->tx_ring_size);
|
|
|
|
|
|
|
|
/* update bdp to point at next bd in the ring (wrapping if necessary) */
|
|
|
|
if (bdp->status & TXBD_WRAP)
|
|
|
|
bdp = priv->tx_bd_base;
|
|
|
|
else
|
|
|
|
bdp++;
|
|
|
|
|
|
|
|
/* Move dirty_tx to be the next bd */
|
|
|
|
priv->dirty_tx = bdp;
|
|
|
|
|
|
|
|
/* We freed a buffer, so now we can restart transmission */
|
|
|
|
if (netif_queue_stopped(dev))
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
} /* while ((bdp->status & TXBD_READY) == 0) */
|
|
|
|
|
|
|
|
/* If we are coalescing the interrupts, reset the timer */
|
|
|
|
/* Otherwise, clear it */
|
|
|
|
if (priv->txcoalescing)
|
|
|
|
gfar_write(&priv->regs->txic,
|
|
|
|
mk_ic_value(priv->txcount, priv->txtime));
|
|
|
|
else
|
|
|
|
gfar_write(&priv->regs->txic, 0);
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_unlock(&priv->txlock);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct sk_buff * gfar_new_skb(struct net_device *dev, struct rxbd8 *bdp)
|
|
|
|
{
|
2005-11-11 19:38:59 +01:00
|
|
|
unsigned int alignamount;
|
2005-04-17 00:20:36 +02:00
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
struct sk_buff *skb = NULL;
|
|
|
|
unsigned int timeout = SKB_ALLOC_TIMEOUT;
|
|
|
|
|
|
|
|
/* We have to allocate the skb, so keep trying till we succeed */
|
|
|
|
while ((!skb) && timeout--)
|
|
|
|
skb = dev_alloc_skb(priv->rx_buffer_size + RXBUF_ALIGNMENT);
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (NULL == skb)
|
2005-04-17 00:20:36 +02:00
|
|
|
return NULL;
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
alignamount = RXBUF_ALIGNMENT -
|
|
|
|
(((unsigned) skb->data) & (RXBUF_ALIGNMENT - 1));
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* We need the data buffer to be aligned properly. We will reserve
|
|
|
|
* as many bytes as needed to align the data properly
|
|
|
|
*/
|
2005-11-11 19:38:59 +01:00
|
|
|
skb_reserve(skb, alignamount);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
skb->dev = dev;
|
|
|
|
|
|
|
|
bdp->bufPtr = dma_map_single(NULL, skb->data,
|
2005-11-11 19:38:59 +01:00
|
|
|
priv->rx_buffer_size, DMA_FROM_DEVICE);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
bdp->length = 0;
|
|
|
|
|
|
|
|
/* Mark the buffer empty */
|
|
|
|
bdp->status |= (RXBD_EMPTY | RXBD_INTERRUPT);
|
|
|
|
|
|
|
|
return skb;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void count_errors(unsigned short status, struct gfar_private *priv)
|
|
|
|
{
|
|
|
|
struct net_device_stats *stats = &priv->stats;
|
|
|
|
struct gfar_extra_stats *estats = &priv->extra_stats;
|
|
|
|
|
|
|
|
/* If the packet was truncated, none of the other errors
|
|
|
|
* matter */
|
|
|
|
if (status & RXBD_TRUNCATED) {
|
|
|
|
stats->rx_length_errors++;
|
|
|
|
|
|
|
|
estats->rx_trunc++;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* Count the errors, if there were any */
|
|
|
|
if (status & (RXBD_LARGE | RXBD_SHORT)) {
|
|
|
|
stats->rx_length_errors++;
|
|
|
|
|
|
|
|
if (status & RXBD_LARGE)
|
|
|
|
estats->rx_large++;
|
|
|
|
else
|
|
|
|
estats->rx_short++;
|
|
|
|
}
|
|
|
|
if (status & RXBD_NONOCTET) {
|
|
|
|
stats->rx_frame_errors++;
|
|
|
|
estats->rx_nonoctet++;
|
|
|
|
}
|
|
|
|
if (status & RXBD_CRCERR) {
|
|
|
|
estats->rx_crcerr++;
|
|
|
|
stats->rx_crc_errors++;
|
|
|
|
}
|
|
|
|
if (status & RXBD_OVERRUN) {
|
|
|
|
estats->rx_overrun++;
|
|
|
|
stats->rx_crc_errors++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
irqreturn_t gfar_receive(int irq, void *dev_id)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct net_device *dev = (struct net_device *) dev_id;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
#ifdef CONFIG_GFAR_NAPI
|
|
|
|
u32 tempval;
|
2006-04-20 23:44:29 +02:00
|
|
|
#else
|
|
|
|
unsigned long flags;
|
2005-04-17 00:20:36 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Clear IEVENT, so rx interrupt isn't called again
|
|
|
|
* because of this interrupt */
|
|
|
|
gfar_write(&priv->regs->ievent, IEVENT_RX_MASK);
|
|
|
|
|
|
|
|
/* support NAPI */
|
|
|
|
#ifdef CONFIG_GFAR_NAPI
|
|
|
|
if (netif_rx_schedule_prep(dev)) {
|
|
|
|
tempval = gfar_read(&priv->regs->imask);
|
|
|
|
tempval &= IMASK_RX_DISABLED;
|
|
|
|
gfar_write(&priv->regs->imask, tempval);
|
|
|
|
|
|
|
|
__netif_rx_schedule(dev);
|
|
|
|
} else {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: receive called twice (%x)[%x]\n",
|
|
|
|
dev->name, gfar_read(&priv->regs->ievent),
|
|
|
|
gfar_read(&priv->regs->imask));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_lock_irqsave(&priv->rxlock, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
gfar_clean_rx_ring(dev, priv->rx_ring_size);
|
|
|
|
|
|
|
|
/* If we are coalescing interrupts, update the timer */
|
|
|
|
/* Otherwise, clear it */
|
|
|
|
if (priv->rxcoalescing)
|
|
|
|
gfar_write(&priv->regs->rxic,
|
|
|
|
mk_ic_value(priv->rxcount, priv->rxtime));
|
|
|
|
else
|
|
|
|
gfar_write(&priv->regs->rxic, 0);
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_unlock_irqrestore(&priv->rxlock, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
static inline int gfar_rx_vlan(struct sk_buff *skb,
|
|
|
|
struct vlan_group *vlgrp, unsigned short vlctl)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_GFAR_NAPI
|
|
|
|
return vlan_hwaccel_receive_skb(skb, vlgrp, vlctl);
|
|
|
|
#else
|
|
|
|
return vlan_hwaccel_rx(skb, vlgrp, vlctl);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void gfar_rx_checksum(struct sk_buff *skb, struct rxfcb *fcb)
|
|
|
|
{
|
|
|
|
/* If valid headers were found, and valid sums
|
|
|
|
* were verified, then we tell the kernel that no
|
|
|
|
* checksumming is necessary. Otherwise, it is */
|
2005-11-11 19:38:59 +01:00
|
|
|
if ((fcb->flags & RXFCB_CSUM_MASK) == (RXFCB_CIP | RXFCB_CTU))
|
2005-06-20 17:54:21 +02:00
|
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
|
|
else
|
|
|
|
skb->ip_summed = CHECKSUM_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static inline struct rxfcb *gfar_get_fcb(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct rxfcb *fcb = (struct rxfcb *)skb->data;
|
|
|
|
|
|
|
|
/* Remove the FCB from the skb */
|
|
|
|
skb_pull(skb, GMAC_FCB_LEN);
|
|
|
|
|
|
|
|
return fcb;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* gfar_process_frame() -- handle one incoming packet if skb
|
|
|
|
* isn't NULL. */
|
|
|
|
static int gfar_process_frame(struct net_device *dev, struct sk_buff *skb,
|
|
|
|
int length)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2005-06-20 17:54:21 +02:00
|
|
|
struct rxfcb *fcb = NULL;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (NULL == skb) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_WARNING "%s: Missing skb!!.\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
priv->stats.rx_dropped++;
|
|
|
|
priv->extra_stats.rx_skbmissing++;
|
|
|
|
} else {
|
2005-06-20 17:54:21 +02:00
|
|
|
int ret;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Prep the skb for the packet */
|
|
|
|
skb_put(skb, length);
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
/* Grab the FCB if there is one */
|
|
|
|
if (gfar_uses_fcb(priv))
|
|
|
|
fcb = gfar_get_fcb(skb);
|
|
|
|
|
|
|
|
/* Remove the padded bytes, if there are any */
|
|
|
|
if (priv->padding)
|
|
|
|
skb_pull(skb, priv->padding);
|
|
|
|
|
|
|
|
if (priv->rx_csum_enable)
|
|
|
|
gfar_rx_checksum(skb, fcb);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Tell the skb what kind of packet this is */
|
|
|
|
skb->protocol = eth_type_trans(skb, dev);
|
|
|
|
|
|
|
|
/* Send the packet up the stack */
|
2005-11-11 19:38:59 +01:00
|
|
|
if (unlikely(priv->vlgrp && (fcb->flags & RXFCB_VLN)))
|
2005-06-20 17:54:21 +02:00
|
|
|
ret = gfar_rx_vlan(skb, priv->vlgrp, fcb->vlctl);
|
|
|
|
else
|
|
|
|
ret = RECEIVE(skb);
|
|
|
|
|
|
|
|
if (NET_RX_DROP == ret)
|
2005-04-17 00:20:36 +02:00
|
|
|
priv->extra_stats.kernel_dropped++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* gfar_clean_rx_ring() -- Processes each frame in the rx ring
|
2005-06-20 17:54:21 +02:00
|
|
|
* until the budget/quota has been reached. Returns the number
|
2005-04-17 00:20:36 +02:00
|
|
|
* of frames handled
|
|
|
|
*/
|
2005-06-20 17:54:21 +02:00
|
|
|
int gfar_clean_rx_ring(struct net_device *dev, int rx_work_limit)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct rxbd8 *bdp;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
u16 pkt_len;
|
|
|
|
int howmany = 0;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* Get the first full descriptor */
|
|
|
|
bdp = priv->cur_rx;
|
|
|
|
|
|
|
|
while (!((bdp->status & RXBD_EMPTY) || (--rx_work_limit < 0))) {
|
|
|
|
skb = priv->rx_skbuff[priv->skb_currx];
|
|
|
|
|
|
|
|
if (!(bdp->status &
|
|
|
|
(RXBD_LARGE | RXBD_SHORT | RXBD_NONOCTET
|
|
|
|
| RXBD_CRCERR | RXBD_OVERRUN | RXBD_TRUNCATED))) {
|
|
|
|
/* Increment the number of packets */
|
|
|
|
priv->stats.rx_packets++;
|
|
|
|
howmany++;
|
|
|
|
|
|
|
|
/* Remove the FCS from the packet length */
|
|
|
|
pkt_len = bdp->length - 4;
|
|
|
|
|
|
|
|
gfar_process_frame(dev, skb, pkt_len);
|
|
|
|
|
|
|
|
priv->stats.rx_bytes += pkt_len;
|
|
|
|
} else {
|
|
|
|
count_errors(bdp->status, priv);
|
|
|
|
|
|
|
|
if (skb)
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
|
|
|
|
priv->rx_skbuff[priv->skb_currx] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev->last_rx = jiffies;
|
|
|
|
|
|
|
|
/* Clear the status flags for this buffer */
|
|
|
|
bdp->status &= ~RXBD_STATS;
|
|
|
|
|
|
|
|
/* Add another skb for the future */
|
|
|
|
skb = gfar_new_skb(dev, bdp);
|
|
|
|
priv->rx_skbuff[priv->skb_currx] = skb;
|
|
|
|
|
|
|
|
/* Update to the next pointer */
|
|
|
|
if (bdp->status & RXBD_WRAP)
|
|
|
|
bdp = priv->rx_bd_base;
|
|
|
|
else
|
|
|
|
bdp++;
|
|
|
|
|
|
|
|
/* update to point at the next skb */
|
|
|
|
priv->skb_currx =
|
|
|
|
(priv->skb_currx +
|
|
|
|
1) & RX_RING_MOD_MASK(priv->rx_ring_size);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Update the current rxbd pointer to be the next one */
|
|
|
|
priv->cur_rx = bdp;
|
|
|
|
|
|
|
|
return howmany;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_GFAR_NAPI
|
|
|
|
static int gfar_poll(struct net_device *dev, int *budget)
|
|
|
|
{
|
|
|
|
int howmany;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
int rx_work_limit = *budget;
|
|
|
|
|
|
|
|
if (rx_work_limit > dev->quota)
|
|
|
|
rx_work_limit = dev->quota;
|
|
|
|
|
|
|
|
howmany = gfar_clean_rx_ring(dev, rx_work_limit);
|
|
|
|
|
|
|
|
dev->quota -= howmany;
|
|
|
|
rx_work_limit -= howmany;
|
|
|
|
*budget -= howmany;
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
if (rx_work_limit > 0) {
|
2005-04-17 00:20:36 +02:00
|
|
|
netif_rx_complete(dev);
|
|
|
|
|
|
|
|
/* Clear the halt bit in RSTAT */
|
|
|
|
gfar_write(&priv->regs->rstat, RSTAT_CLEAR_RHALT);
|
|
|
|
|
|
|
|
gfar_write(&priv->regs->imask, IMASK_DEFAULT);
|
|
|
|
|
|
|
|
/* If we are coalescing interrupts, update the timer */
|
|
|
|
/* Otherwise, clear it */
|
|
|
|
if (priv->rxcoalescing)
|
|
|
|
gfar_write(&priv->regs->rxic,
|
|
|
|
mk_ic_value(priv->rxcount, priv->rxtime));
|
|
|
|
else
|
|
|
|
gfar_write(&priv->regs->rxic, 0);
|
|
|
|
}
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
/* Return 1 if there's more work to do */
|
|
|
|
return (rx_work_limit > 0) ? 0 : 1;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* The interrupt handler for devices with one interrupt */
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
static irqreturn_t gfar_interrupt(int irq, void *dev_id)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct net_device *dev = dev_id;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* Save ievent for future reference */
|
|
|
|
u32 events = gfar_read(&priv->regs->ievent);
|
|
|
|
|
|
|
|
/* Clear IEVENT */
|
|
|
|
gfar_write(&priv->regs->ievent, events);
|
|
|
|
|
|
|
|
/* Check for reception */
|
|
|
|
if ((events & IEVENT_RXF0) || (events & IEVENT_RXB0))
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
gfar_receive(irq, dev_id);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Check for transmit completion */
|
|
|
|
if ((events & IEVENT_TXF) || (events & IEVENT_TXB))
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
gfar_transmit(irq, dev_id);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Update error statistics */
|
|
|
|
if (events & IEVENT_TXE) {
|
|
|
|
priv->stats.tx_errors++;
|
|
|
|
|
|
|
|
if (events & IEVENT_LC)
|
|
|
|
priv->stats.tx_window_errors++;
|
|
|
|
if (events & IEVENT_CRL)
|
|
|
|
priv->stats.tx_aborted_errors++;
|
|
|
|
if (events & IEVENT_XFUN) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_tx_err(priv))
|
|
|
|
printk(KERN_WARNING "%s: tx underrun. dropped packet\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
priv->stats.tx_dropped++;
|
|
|
|
priv->extra_stats.tx_underrun++;
|
|
|
|
|
|
|
|
/* Reactivate the Tx Queues */
|
|
|
|
gfar_write(&priv->regs->tstat, TSTAT_CLEAR_THALT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (events & IEVENT_BSY) {
|
|
|
|
priv->stats.rx_errors++;
|
|
|
|
priv->extra_stats.rx_bsy++;
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
gfar_receive(irq, dev_id);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#ifndef CONFIG_GFAR_NAPI
|
|
|
|
/* Clear the halt bit in RSTAT */
|
|
|
|
gfar_write(&priv->regs->rstat, RSTAT_CLEAR_RHALT);
|
|
|
|
#endif
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: busy error (rhalt: %x)\n",
|
|
|
|
dev->name,
|
|
|
|
gfar_read(&priv->regs->rstat));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
if (events & IEVENT_BABR) {
|
|
|
|
priv->stats.rx_errors++;
|
|
|
|
priv->extra_stats.rx_babr++;
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: babbling error\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
if (events & IEVENT_EBERR) {
|
|
|
|
priv->extra_stats.eberr++;
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: EBERR\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2005-06-20 17:54:21 +02:00
|
|
|
if ((events & IEVENT_RXC) && (netif_msg_rx_err(priv)))
|
|
|
|
printk(KERN_DEBUG "%s: control frame\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (events & IEVENT_BABT) {
|
|
|
|
priv->extra_stats.tx_babt++;
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: babt error\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Called every time the controller might need to be made
|
|
|
|
* aware of new link state. The PHY code conveys this
|
2005-09-24 04:54:21 +02:00
|
|
|
* information through variables in the phydev structure, and this
|
2005-04-17 00:20:36 +02:00
|
|
|
* function converts those variables into the appropriate
|
|
|
|
* register values, and can bring down the device if needed.
|
|
|
|
*/
|
|
|
|
static void adjust_link(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2006-02-01 22:18:03 +01:00
|
|
|
struct gfar __iomem *regs = priv->regs;
|
2005-09-24 04:54:21 +02:00
|
|
|
unsigned long flags;
|
|
|
|
struct phy_device *phydev = priv->phydev;
|
|
|
|
int new_state = 0;
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_lock_irqsave(&priv->txlock, flags);
|
2005-09-24 04:54:21 +02:00
|
|
|
if (phydev->link) {
|
|
|
|
u32 tempval = gfar_read(®s->maccfg2);
|
2005-11-11 19:38:59 +01:00
|
|
|
u32 ecntrl = gfar_read(®s->ecntrl);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Now we make sure that we can be in full duplex mode.
|
|
|
|
* If not, we operate in half-duplex mode. */
|
2005-09-24 04:54:21 +02:00
|
|
|
if (phydev->duplex != priv->oldduplex) {
|
|
|
|
new_state = 1;
|
|
|
|
if (!(phydev->duplex))
|
2005-04-17 00:20:36 +02:00
|
|
|
tempval &= ~(MACCFG2_FULL_DUPLEX);
|
2005-09-24 04:54:21 +02:00
|
|
|
else
|
2005-04-17 00:20:36 +02:00
|
|
|
tempval |= MACCFG2_FULL_DUPLEX;
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
priv->oldduplex = phydev->duplex;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (phydev->speed != priv->oldspeed) {
|
|
|
|
new_state = 1;
|
|
|
|
switch (phydev->speed) {
|
2005-04-17 00:20:36 +02:00
|
|
|
case 1000:
|
|
|
|
tempval =
|
|
|
|
((tempval & ~(MACCFG2_IF)) | MACCFG2_GMII);
|
|
|
|
break;
|
|
|
|
case 100:
|
|
|
|
case 10:
|
|
|
|
tempval =
|
|
|
|
((tempval & ~(MACCFG2_IF)) | MACCFG2_MII);
|
2005-11-11 19:38:59 +01:00
|
|
|
|
|
|
|
/* Reduced mode distinguishes
|
|
|
|
* between 10 and 100 */
|
|
|
|
if (phydev->speed == SPEED_100)
|
|
|
|
ecntrl |= ECNTRL_R100;
|
|
|
|
else
|
|
|
|
ecntrl &= ~(ECNTRL_R100);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
default:
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_link(priv))
|
|
|
|
printk(KERN_WARNING
|
2005-09-24 04:54:21 +02:00
|
|
|
"%s: Ack! Speed (%d) is not 10/100/1000!\n",
|
|
|
|
dev->name, phydev->speed);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
priv->oldspeed = phydev->speed;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
gfar_write(®s->maccfg2, tempval);
|
2005-11-11 19:38:59 +01:00
|
|
|
gfar_write(®s->ecntrl, ecntrl);
|
2005-09-24 04:54:21 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!priv->oldlink) {
|
2005-09-24 04:54:21 +02:00
|
|
|
new_state = 1;
|
2005-04-17 00:20:36 +02:00
|
|
|
priv->oldlink = 1;
|
|
|
|
netif_schedule(dev);
|
|
|
|
}
|
2005-09-24 04:54:21 +02:00
|
|
|
} else if (priv->oldlink) {
|
|
|
|
new_state = 1;
|
|
|
|
priv->oldlink = 0;
|
|
|
|
priv->oldspeed = 0;
|
|
|
|
priv->oldduplex = -1;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
if (new_state && netif_msg_link(priv))
|
|
|
|
phy_print_status(phydev);
|
|
|
|
|
2006-04-20 23:44:29 +02:00
|
|
|
spin_unlock_irqrestore(&priv->txlock, flags);
|
2005-09-24 04:54:21 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Update the hash table based on the current list of multicast
|
|
|
|
* addresses we subscribe to. Also, change the promiscuity of
|
|
|
|
* the device based on the flags (this function is called
|
|
|
|
* whenever dev->flags is changed */
|
|
|
|
static void gfar_set_multi(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct dev_mc_list *mc_ptr;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
2006-02-01 22:18:03 +01:00
|
|
|
struct gfar __iomem *regs = priv->regs;
|
2005-04-17 00:20:36 +02:00
|
|
|
u32 tempval;
|
|
|
|
|
|
|
|
if(dev->flags & IFF_PROMISC) {
|
|
|
|
/* Set RCTRL to PROM */
|
|
|
|
tempval = gfar_read(®s->rctrl);
|
|
|
|
tempval |= RCTRL_PROM;
|
|
|
|
gfar_write(®s->rctrl, tempval);
|
|
|
|
} else {
|
|
|
|
/* Set RCTRL to not PROM */
|
|
|
|
tempval = gfar_read(®s->rctrl);
|
|
|
|
tempval &= ~(RCTRL_PROM);
|
|
|
|
gfar_write(®s->rctrl, tempval);
|
|
|
|
}
|
2006-09-13 19:24:59 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if(dev->flags & IFF_ALLMULTI) {
|
|
|
|
/* Set the hash to rx all multicast frames */
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_write(®s->igaddr0, 0xffffffff);
|
|
|
|
gfar_write(®s->igaddr1, 0xffffffff);
|
|
|
|
gfar_write(®s->igaddr2, 0xffffffff);
|
|
|
|
gfar_write(®s->igaddr3, 0xffffffff);
|
|
|
|
gfar_write(®s->igaddr4, 0xffffffff);
|
|
|
|
gfar_write(®s->igaddr5, 0xffffffff);
|
|
|
|
gfar_write(®s->igaddr6, 0xffffffff);
|
|
|
|
gfar_write(®s->igaddr7, 0xffffffff);
|
2005-04-17 00:20:36 +02:00
|
|
|
gfar_write(®s->gaddr0, 0xffffffff);
|
|
|
|
gfar_write(®s->gaddr1, 0xffffffff);
|
|
|
|
gfar_write(®s->gaddr2, 0xffffffff);
|
|
|
|
gfar_write(®s->gaddr3, 0xffffffff);
|
|
|
|
gfar_write(®s->gaddr4, 0xffffffff);
|
|
|
|
gfar_write(®s->gaddr5, 0xffffffff);
|
|
|
|
gfar_write(®s->gaddr6, 0xffffffff);
|
|
|
|
gfar_write(®s->gaddr7, 0xffffffff);
|
|
|
|
} else {
|
2005-11-11 19:38:59 +01:00
|
|
|
int em_num;
|
|
|
|
int idx;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* zero out the hash */
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_write(®s->igaddr0, 0x0);
|
|
|
|
gfar_write(®s->igaddr1, 0x0);
|
|
|
|
gfar_write(®s->igaddr2, 0x0);
|
|
|
|
gfar_write(®s->igaddr3, 0x0);
|
|
|
|
gfar_write(®s->igaddr4, 0x0);
|
|
|
|
gfar_write(®s->igaddr5, 0x0);
|
|
|
|
gfar_write(®s->igaddr6, 0x0);
|
|
|
|
gfar_write(®s->igaddr7, 0x0);
|
2005-04-17 00:20:36 +02:00
|
|
|
gfar_write(®s->gaddr0, 0x0);
|
|
|
|
gfar_write(®s->gaddr1, 0x0);
|
|
|
|
gfar_write(®s->gaddr2, 0x0);
|
|
|
|
gfar_write(®s->gaddr3, 0x0);
|
|
|
|
gfar_write(®s->gaddr4, 0x0);
|
|
|
|
gfar_write(®s->gaddr5, 0x0);
|
|
|
|
gfar_write(®s->gaddr6, 0x0);
|
|
|
|
gfar_write(®s->gaddr7, 0x0);
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
/* If we have extended hash tables, we need to
|
|
|
|
* clear the exact match registers to prepare for
|
|
|
|
* setting them */
|
|
|
|
if (priv->extended_hash) {
|
|
|
|
em_num = GFAR_EM_NUM + 1;
|
|
|
|
gfar_clear_exact_match(dev);
|
|
|
|
idx = 1;
|
|
|
|
} else {
|
|
|
|
idx = 0;
|
|
|
|
em_num = 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if(dev->mc_count == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Parse the list, and set the appropriate bits */
|
|
|
|
for(mc_ptr = dev->mc_list; mc_ptr; mc_ptr = mc_ptr->next) {
|
2005-11-11 19:38:59 +01:00
|
|
|
if (idx < em_num) {
|
|
|
|
gfar_set_mac_for_addr(dev, idx,
|
|
|
|
mc_ptr->dmi_addr);
|
|
|
|
idx++;
|
|
|
|
} else
|
|
|
|
gfar_set_hash_for_addr(dev, mc_ptr->dmi_addr);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
|
|
|
|
/* Clears each of the exact match registers to zero, so they
|
|
|
|
* don't interfere with normal reception */
|
|
|
|
static void gfar_clear_exact_match(struct net_device *dev)
|
|
|
|
{
|
|
|
|
int idx;
|
|
|
|
u8 zero_arr[MAC_ADDR_LEN] = {0,0,0,0,0,0};
|
|
|
|
|
|
|
|
for(idx = 1;idx < GFAR_EM_NUM + 1;idx++)
|
|
|
|
gfar_set_mac_for_addr(dev, idx, (u8 *)zero_arr);
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Set the appropriate hash bit for the given addr */
|
|
|
|
/* The algorithm works like so:
|
|
|
|
* 1) Take the Destination Address (ie the multicast address), and
|
|
|
|
* do a CRC on it (little endian), and reverse the bits of the
|
|
|
|
* result.
|
|
|
|
* 2) Use the 8 most significant bits as a hash into a 256-entry
|
|
|
|
* table. The table is controlled through 8 32-bit registers:
|
|
|
|
* gaddr0-7. gaddr0's MSB is entry 0, and gaddr7's LSB is
|
|
|
|
* gaddr7. This means that the 3 most significant bits in the
|
|
|
|
* hash index which gaddr register to use, and the 5 other bits
|
|
|
|
* indicate which bit (assuming an IBM numbering scheme, which
|
|
|
|
* for PowerPC (tm) is usually the case) in the register holds
|
|
|
|
* the entry. */
|
|
|
|
static void gfar_set_hash_for_addr(struct net_device *dev, u8 *addr)
|
|
|
|
{
|
|
|
|
u32 tempval;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
u32 result = ether_crc(MAC_ADDR_LEN, addr);
|
2005-06-20 17:54:21 +02:00
|
|
|
int width = priv->hash_width;
|
|
|
|
u8 whichbit = (result >> (32 - width)) & 0x1f;
|
|
|
|
u8 whichreg = result >> (32 - width + 5);
|
2005-04-17 00:20:36 +02:00
|
|
|
u32 value = (1 << (31-whichbit));
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
tempval = gfar_read(priv->hash_regs[whichreg]);
|
2005-04-17 00:20:36 +02:00
|
|
|
tempval |= value;
|
2005-06-20 17:54:21 +02:00
|
|
|
gfar_write(priv->hash_regs[whichreg], tempval);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2005-11-11 19:38:59 +01:00
|
|
|
|
|
|
|
/* There are multiple MAC Address register pairs on some controllers
|
|
|
|
* This function sets the numth pair to a given address
|
|
|
|
*/
|
|
|
|
static void gfar_set_mac_for_addr(struct net_device *dev, int num, u8 *addr)
|
|
|
|
{
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
int idx;
|
|
|
|
char tmpbuf[MAC_ADDR_LEN];
|
|
|
|
u32 tempval;
|
2006-02-01 22:18:03 +01:00
|
|
|
u32 __iomem *macptr = &priv->regs->macstnaddr1;
|
2005-11-11 19:38:59 +01:00
|
|
|
|
|
|
|
macptr += num*2;
|
|
|
|
|
|
|
|
/* Now copy it into the mac registers backwards, cuz */
|
|
|
|
/* little endian is silly */
|
|
|
|
for (idx = 0; idx < MAC_ADDR_LEN; idx++)
|
|
|
|
tmpbuf[MAC_ADDR_LEN - 1 - idx] = addr[idx];
|
|
|
|
|
|
|
|
gfar_write(macptr, *((u32 *) (tmpbuf)));
|
|
|
|
|
|
|
|
tempval = *((u32 *) (tmpbuf + 4));
|
|
|
|
|
|
|
|
gfar_write(macptr+1, tempval);
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* GFAR error interrupt handler */
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
static irqreturn_t gfar_error(int irq, void *dev_id)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct net_device *dev = dev_id;
|
|
|
|
struct gfar_private *priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* Save ievent for future reference */
|
|
|
|
u32 events = gfar_read(&priv->regs->ievent);
|
|
|
|
|
|
|
|
/* Clear IEVENT */
|
|
|
|
gfar_write(&priv->regs->ievent, IEVENT_ERR_MASK);
|
|
|
|
|
|
|
|
/* Hmm... */
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv) || netif_msg_tx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: error interrupt (ievent=0x%08x imask=0x%08x)\n",
|
|
|
|
dev->name, events, gfar_read(&priv->regs->imask));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Update the error counters */
|
|
|
|
if (events & IEVENT_TXE) {
|
|
|
|
priv->stats.tx_errors++;
|
|
|
|
|
|
|
|
if (events & IEVENT_LC)
|
|
|
|
priv->stats.tx_window_errors++;
|
|
|
|
if (events & IEVENT_CRL)
|
|
|
|
priv->stats.tx_aborted_errors++;
|
|
|
|
if (events & IEVENT_XFUN) {
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_tx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: underrun. packet dropped.\n",
|
|
|
|
dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
priv->stats.tx_dropped++;
|
|
|
|
priv->extra_stats.tx_underrun++;
|
|
|
|
|
|
|
|
/* Reactivate the Tx Queues */
|
|
|
|
gfar_write(&priv->regs->tstat, TSTAT_CLEAR_THALT);
|
|
|
|
}
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_tx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: Transmit Error\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
if (events & IEVENT_BSY) {
|
|
|
|
priv->stats.rx_errors++;
|
|
|
|
priv->extra_stats.rx_bsy++;
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
gfar_receive(irq, dev_id);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#ifndef CONFIG_GFAR_NAPI
|
|
|
|
/* Clear the halt bit in RSTAT */
|
|
|
|
gfar_write(&priv->regs->rstat, RSTAT_CLEAR_RHALT);
|
|
|
|
#endif
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: busy error (rhalt: %x)\n",
|
|
|
|
dev->name,
|
|
|
|
gfar_read(&priv->regs->rstat));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
if (events & IEVENT_BABR) {
|
|
|
|
priv->stats.rx_errors++;
|
|
|
|
priv->extra_stats.rx_babr++;
|
|
|
|
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: babbling error\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
if (events & IEVENT_EBERR) {
|
|
|
|
priv->extra_stats.eberr++;
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_rx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: EBERR\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2005-06-20 17:54:21 +02:00
|
|
|
if ((events & IEVENT_RXC) && netif_msg_rx_status(priv))
|
|
|
|
if (netif_msg_rx_status(priv))
|
|
|
|
printk(KERN_DEBUG "%s: control frame\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (events & IEVENT_BABT) {
|
|
|
|
priv->extra_stats.tx_babt++;
|
2005-06-20 17:54:21 +02:00
|
|
|
if (netif_msg_tx_err(priv))
|
|
|
|
printk(KERN_DEBUG "%s: babt error\n", dev->name);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Structure for a device driver */
|
2005-11-09 23:32:44 +01:00
|
|
|
static struct platform_driver gfar_driver = {
|
2005-04-17 00:20:36 +02:00
|
|
|
.probe = gfar_probe,
|
|
|
|
.remove = gfar_remove,
|
2005-11-09 23:32:44 +01:00
|
|
|
.driver = {
|
|
|
|
.name = "fsl-gianfar",
|
|
|
|
},
|
2005-04-17 00:20:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __init gfar_init(void)
|
|
|
|
{
|
2005-09-24 04:54:21 +02:00
|
|
|
int err = gfar_mdio_init();
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
err = platform_driver_register(&gfar_driver);
|
2005-09-24 04:54:21 +02:00
|
|
|
|
|
|
|
if (err)
|
|
|
|
gfar_mdio_exit();
|
2006-09-13 19:24:59 +02:00
|
|
|
|
2005-09-24 04:54:21 +02:00
|
|
|
return err;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit gfar_exit(void)
|
|
|
|
{
|
2005-11-09 23:32:44 +01:00
|
|
|
platform_driver_unregister(&gfar_driver);
|
2005-09-24 04:54:21 +02:00
|
|
|
gfar_mdio_exit();
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(gfar_init);
|
|
|
|
module_exit(gfar_exit);
|
|
|
|
|