b44.c

来自「linux 内核源代码」· C语言 代码 · 共 2,335 行 · 第 1/4 页

C
2,335
字号
/* b44.c: Broadcom 44xx/47xx Fast Ethernet device driver. * * Copyright (C) 2002 David S. Miller (davem@redhat.com) * Copyright (C) 2004 Pekka Pietikainen (pp@ee.oulu.fi) * Copyright (C) 2004 Florian Schirmer (jolt@tuxbox.org) * Copyright (C) 2006 Felix Fietkau (nbd@openwrt.org) * Copyright (C) 2006 Broadcom Corporation. * Copyright (C) 2007 Michael Buesch <mb@bu3sch.de> * * Distribute under GPL. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/types.h>#include <linux/netdevice.h>#include <linux/ethtool.h>#include <linux/mii.h>#include <linux/if_ether.h>#include <linux/if_vlan.h>#include <linux/etherdevice.h>#include <linux/pci.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/dma-mapping.h>#include <linux/ssb/ssb.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/irq.h>#include "b44.h"#define DRV_MODULE_NAME		"b44"#define PFX DRV_MODULE_NAME	": "#define DRV_MODULE_VERSION	"2.0"#define B44_DEF_MSG_ENABLE	  \	(NETIF_MSG_DRV		| \	 NETIF_MSG_PROBE	| \	 NETIF_MSG_LINK		| \	 NETIF_MSG_TIMER	| \	 NETIF_MSG_IFDOWN	| \	 NETIF_MSG_IFUP		| \	 NETIF_MSG_RX_ERR	| \	 NETIF_MSG_TX_ERR)/* length of time before we decide the hardware is borked, * and dev->tx_timeout() should be called to fix the problem */#define B44_TX_TIMEOUT			(5 * HZ)/* hardware minimum and maximum for a single frame's data payload */#define B44_MIN_MTU			60#define B44_MAX_MTU			1500#define B44_RX_RING_SIZE		512#define B44_DEF_RX_RING_PENDING		200#define B44_RX_RING_BYTES	(sizeof(struct dma_desc) * \				 B44_RX_RING_SIZE)#define B44_TX_RING_SIZE		512#define B44_DEF_TX_RING_PENDING		(B44_TX_RING_SIZE - 1)#define B44_TX_RING_BYTES	(sizeof(struct dma_desc) * \				 B44_TX_RING_SIZE)#define TX_RING_GAP(BP)	\	(B44_TX_RING_SIZE - (BP)->tx_pending)#define TX_BUFFS_AVAIL(BP)						\	(((BP)->tx_cons <= (BP)->tx_prod) ?				\	  (BP)->tx_cons + (BP)->tx_pending - (BP)->tx_prod :		\	  (BP)->tx_cons - (BP)->tx_prod - TX_RING_GAP(BP))#define NEXT_TX(N)		(((N) + 1) & (B44_TX_RING_SIZE - 1))#define RX_PKT_OFFSET		30#define RX_PKT_BUF_SZ		(1536 + RX_PKT_OFFSET + 64)/* minimum number of free TX descriptors required to wake up TX process */#define B44_TX_WAKEUP_THRESH		(B44_TX_RING_SIZE / 4)/* b44 internal pattern match filter info */#define B44_PATTERN_BASE	0x400#define B44_PATTERN_SIZE	0x80#define B44_PMASK_BASE		0x600#define B44_PMASK_SIZE		0x10#define B44_MAX_PATTERNS	16#define B44_ETHIPV6UDP_HLEN	62#define B44_ETHIPV4UDP_HLEN	42static char version[] __devinitdata =	DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION "\n";MODULE_AUTHOR("Felix Fietkau, Florian Schirmer, Pekka Pietikainen, David S. Miller");MODULE_DESCRIPTION("Broadcom 44xx/47xx 10/100 PCI ethernet driver");MODULE_LICENSE("GPL");MODULE_VERSION(DRV_MODULE_VERSION);static int b44_debug = -1;	/* -1 == use B44_DEF_MSG_ENABLE as value */module_param(b44_debug, int, 0);MODULE_PARM_DESC(b44_debug, "B44 bitmapped debugging message enable value");#ifdef CONFIG_B44_PCIstatic const struct pci_device_id b44_pci_tbl[] = {	{ PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_BCM4401) },	{ PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_BCM4401B0) },	{ PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_BCM4401B1) },	{ 0 } /* terminate list with empty entry */};MODULE_DEVICE_TABLE(pci, b44_pci_tbl);static struct pci_driver b44_pci_driver = {	.name		= DRV_MODULE_NAME,	.id_table	= b44_pci_tbl,};#endif /* CONFIG_B44_PCI */static const struct ssb_device_id b44_ssb_tbl[] = {	SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_ETHERNET, SSB_ANY_REV),	SSB_DEVTABLE_END};MODULE_DEVICE_TABLE(ssb, b44_ssb_tbl);static void b44_halt(struct b44 *);static void b44_init_rings(struct b44 *);#define B44_FULL_RESET		1#define B44_FULL_RESET_SKIP_PHY	2#define B44_PARTIAL_RESET	3static void b44_init_hw(struct b44 *, int);static int dma_desc_align_mask;static int dma_desc_sync_size;static int instance;static const char b44_gstrings[][ETH_GSTRING_LEN] = {#define _B44(x...)	# x,B44_STAT_REG_DECLARE#undef _B44};static inline void b44_sync_dma_desc_for_device(struct ssb_device *sdev,						dma_addr_t dma_base,						unsigned long offset,						enum dma_data_direction dir){	dma_sync_single_range_for_device(sdev->dev, dma_base,					 offset & dma_desc_align_mask,					 dma_desc_sync_size, dir);}static inline void b44_sync_dma_desc_for_cpu(struct ssb_device *sdev,					     dma_addr_t dma_base,					     unsigned long offset,					     enum dma_data_direction dir){	dma_sync_single_range_for_cpu(sdev->dev, dma_base,				      offset & dma_desc_align_mask,				      dma_desc_sync_size, dir);}static inline unsigned long br32(const struct b44 *bp, unsigned long reg){	return ssb_read32(bp->sdev, reg);}static inline void bw32(const struct b44 *bp,			unsigned long reg, unsigned long val){	ssb_write32(bp->sdev, reg, val);}static int b44_wait_bit(struct b44 *bp, unsigned long reg,			u32 bit, unsigned long timeout, const int clear){	unsigned long i;	for (i = 0; i < timeout; i++) {		u32 val = br32(bp, reg);		if (clear && !(val & bit))			break;		if (!clear && (val & bit))			break;		udelay(10);	}	if (i == timeout) {		printk(KERN_ERR PFX "%s: BUG!  Timeout waiting for bit %08x of register "		       "%lx to %s.\n",		       bp->dev->name,		       bit, reg,		       (clear ? "clear" : "set"));		return -ENODEV;	}	return 0;}static inline void __b44_cam_read(struct b44 *bp, unsigned char *data, int index){	u32 val;	bw32(bp, B44_CAM_CTRL, (CAM_CTRL_READ |			    (index << CAM_CTRL_INDEX_SHIFT)));	b44_wait_bit(bp, B44_CAM_CTRL, CAM_CTRL_BUSY, 100, 1);	val = br32(bp, B44_CAM_DATA_LO);	data[2] = (val >> 24) & 0xFF;	data[3] = (val >> 16) & 0xFF;	data[4] = (val >> 8) & 0xFF;	data[5] = (val >> 0) & 0xFF;	val = br32(bp, B44_CAM_DATA_HI);	data[0] = (val >> 8) & 0xFF;	data[1] = (val >> 0) & 0xFF;}static inline void __b44_cam_write(struct b44 *bp, unsigned char *data, int index){	u32 val;	val  = ((u32) data[2]) << 24;	val |= ((u32) data[3]) << 16;	val |= ((u32) data[4]) <<  8;	val |= ((u32) data[5]) <<  0;	bw32(bp, B44_CAM_DATA_LO, val);	val = (CAM_DATA_HI_VALID |	       (((u32) data[0]) << 8) |	       (((u32) data[1]) << 0));	bw32(bp, B44_CAM_DATA_HI, val);	bw32(bp, B44_CAM_CTRL, (CAM_CTRL_WRITE |			    (index << CAM_CTRL_INDEX_SHIFT)));	b44_wait_bit(bp, B44_CAM_CTRL, CAM_CTRL_BUSY, 100, 1);}static inline void __b44_disable_ints(struct b44 *bp){	bw32(bp, B44_IMASK, 0);}static void b44_disable_ints(struct b44 *bp){	__b44_disable_ints(bp);	/* Flush posted writes. */	br32(bp, B44_IMASK);}static void b44_enable_ints(struct b44 *bp){	bw32(bp, B44_IMASK, bp->imask);}static int __b44_readphy(struct b44 *bp, int phy_addr, int reg, u32 *val){	int err;	bw32(bp, B44_EMAC_ISTAT, EMAC_INT_MII);	bw32(bp, B44_MDIO_DATA, (MDIO_DATA_SB_START |			     (MDIO_OP_READ << MDIO_DATA_OP_SHIFT) |			     (phy_addr << MDIO_DATA_PMD_SHIFT) |			     (reg << MDIO_DATA_RA_SHIFT) |			     (MDIO_TA_VALID << MDIO_DATA_TA_SHIFT)));	err = b44_wait_bit(bp, B44_EMAC_ISTAT, EMAC_INT_MII, 100, 0);	*val = br32(bp, B44_MDIO_DATA) & MDIO_DATA_DATA;	return err;}static int __b44_writephy(struct b44 *bp, int phy_addr, int reg, u32 val){	bw32(bp, B44_EMAC_ISTAT, EMAC_INT_MII);	bw32(bp, B44_MDIO_DATA, (MDIO_DATA_SB_START |			     (MDIO_OP_WRITE << MDIO_DATA_OP_SHIFT) |			     (phy_addr << MDIO_DATA_PMD_SHIFT) |			     (reg << MDIO_DATA_RA_SHIFT) |			     (MDIO_TA_VALID << MDIO_DATA_TA_SHIFT) |			     (val & MDIO_DATA_DATA)));	return b44_wait_bit(bp, B44_EMAC_ISTAT, EMAC_INT_MII, 100, 0);}static inline int b44_readphy(struct b44 *bp, int reg, u32 *val){	if (bp->phy_addr == B44_PHY_ADDR_NO_PHY)		return 0;	return __b44_readphy(bp, bp->phy_addr, reg, val);}static inline int b44_writephy(struct b44 *bp, int reg, u32 val){	if (bp->phy_addr == B44_PHY_ADDR_NO_PHY)		return 0;	return __b44_writephy(bp, bp->phy_addr, reg, val);}/* miilib interface */static int b44_mii_read(struct net_device *dev, int phy_id, int location){	u32 val;	struct b44 *bp = netdev_priv(dev);	int rc = __b44_readphy(bp, phy_id, location, &val);	if (rc)		return 0xffffffff;	return val;}static void b44_mii_write(struct net_device *dev, int phy_id, int location,			 int val){	struct b44 *bp = netdev_priv(dev);	__b44_writephy(bp, phy_id, location, val);}static int b44_phy_reset(struct b44 *bp){	u32 val;	int err;	if (bp->phy_addr == B44_PHY_ADDR_NO_PHY)		return 0;	err = b44_writephy(bp, MII_BMCR, BMCR_RESET);	if (err)		return err;	udelay(100);	err = b44_readphy(bp, MII_BMCR, &val);	if (!err) {		if (val & BMCR_RESET) {			printk(KERN_ERR PFX "%s: PHY Reset would not complete.\n",			       bp->dev->name);			err = -ENODEV;		}	}	return 0;}static void __b44_set_flow_ctrl(struct b44 *bp, u32 pause_flags){	u32 val;	bp->flags &= ~(B44_FLAG_TX_PAUSE | B44_FLAG_RX_PAUSE);	bp->flags |= pause_flags;	val = br32(bp, B44_RXCONFIG);	if (pause_flags & B44_FLAG_RX_PAUSE)		val |= RXCONFIG_FLOW;	else		val &= ~RXCONFIG_FLOW;	bw32(bp, B44_RXCONFIG, val);	val = br32(bp, B44_MAC_FLOW);	if (pause_flags & B44_FLAG_TX_PAUSE)		val |= (MAC_FLOW_PAUSE_ENAB |			(0xc0 & MAC_FLOW_RX_HI_WATER));	else		val &= ~MAC_FLOW_PAUSE_ENAB;	bw32(bp, B44_MAC_FLOW, val);}static void b44_set_flow_ctrl(struct b44 *bp, u32 local, u32 remote){	u32 pause_enab = 0;	/* The driver supports only rx pause by default because	   the b44 mac tx pause mechanism generates excessive	   pause frames.	   Use ethtool to turn on b44 tx pause if necessary.	 */	if ((local & ADVERTISE_PAUSE_CAP) &&	    (local & ADVERTISE_PAUSE_ASYM)){		if ((remote & LPA_PAUSE_ASYM) &&		    !(remote & LPA_PAUSE_CAP))			pause_enab |= B44_FLAG_RX_PAUSE;	}	__b44_set_flow_ctrl(bp, pause_enab);}#ifdef SSB_DRIVER_MIPSextern char *nvram_get(char *name);static void b44_wap54g10_workaround(struct b44 *bp){	const char *str;	u32 val;	int err;	/*	 * workaround for bad hardware design in Linksys WAP54G v1.0	 * see https://dev.openwrt.org/ticket/146	 * check and reset bit "isolate"	 */	str = nvram_get("boardnum");	if (!str)		return;	if (simple_strtoul(str, NULL, 0) == 2) {		err = __b44_readphy(bp, 0, MII_BMCR, &val);		if (err)			goto error;		if (!(val & BMCR_ISOLATE))			return;		val &= ~BMCR_ISOLATE;		err = __b44_writephy(bp, 0, MII_BMCR, val);		if (err)			goto error;	}	return;error:	printk(KERN_WARNING PFX "PHY: cannot reset MII transceiver isolate bit.\n");}#elsestatic inline void b44_wap54g10_workaround(struct b44 *bp){}#endifstatic int b44_setup_phy(struct b44 *bp){	u32 val;	int err;	b44_wap54g10_workaround(bp);	if (bp->phy_addr == B44_PHY_ADDR_NO_PHY)		return 0;	if ((err = b44_readphy(bp, B44_MII_ALEDCTRL, &val)) != 0)		goto out;	if ((err = b44_writephy(bp, B44_MII_ALEDCTRL,				val & MII_ALEDCTRL_ALLMSK)) != 0)		goto out;	if ((err = b44_readphy(bp, B44_MII_TLEDCTRL, &val)) != 0)		goto out;	if ((err = b44_writephy(bp, B44_MII_TLEDCTRL,				val | MII_TLEDCTRL_ENABLE)) != 0)		goto out;	if (!(bp->flags & B44_FLAG_FORCE_LINK)) {		u32 adv = ADVERTISE_CSMA;		if (bp->flags & B44_FLAG_ADV_10HALF)			adv |= ADVERTISE_10HALF;		if (bp->flags & B44_FLAG_ADV_10FULL)			adv |= ADVERTISE_10FULL;		if (bp->flags & B44_FLAG_ADV_100HALF)			adv |= ADVERTISE_100HALF;		if (bp->flags & B44_FLAG_ADV_100FULL)			adv |= ADVERTISE_100FULL;		if (bp->flags & B44_FLAG_PAUSE_AUTO)			adv |= ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;		if ((err = b44_writephy(bp, MII_ADVERTISE, adv)) != 0)			goto out;		if ((err = b44_writephy(bp, MII_BMCR, (BMCR_ANENABLE |						       BMCR_ANRESTART))) != 0)			goto out;	} else {		u32 bmcr;		if ((err = b44_readphy(bp, MII_BMCR, &bmcr)) != 0)			goto out;		bmcr &= ~(BMCR_FULLDPLX | BMCR_ANENABLE | BMCR_SPEED100);		if (bp->flags & B44_FLAG_100_BASE_T)			bmcr |= BMCR_SPEED100;		if (bp->flags & B44_FLAG_FULL_DUPLEX)			bmcr |= BMCR_FULLDPLX;		if ((err = b44_writephy(bp, MII_BMCR, bmcr)) != 0)			goto out;		/* Since we will not be negotiating there is no safe way		 * to determine if the link partner supports flow control		 * or not.  So just disable it completely in this case.		 */		b44_set_flow_ctrl(bp, 0, 0);	}out:	return err;}static void b44_stats_update(struct b44 *bp){	unsigned long reg;	u32 *val;	val = &bp->hw_stats.tx_good_octets;	for (reg = B44_TX_GOOD_O; reg <= B44_TX_PAUSE; reg += 4UL) {		*val++ += br32(bp, reg);	}	/* Pad */	reg += 8*4UL;	for (reg = B44_RX_GOOD_O; reg <= B44_RX_NPAUSE; reg += 4UL) {		*val++ += br32(bp, reg);	}}static void b44_link_report(struct b44 *bp){	if (!netif_carrier_ok(bp->dev)) {		printk(KERN_INFO PFX "%s: Link is down.\n", bp->dev->name);	} else {		printk(KERN_INFO PFX "%s: Link is up at %d Mbps, %s duplex.\n",		       bp->dev->name,		       (bp->flags & B44_FLAG_100_BASE_T) ? 100 : 10,		       (bp->flags & B44_FLAG_FULL_DUPLEX) ? "full" : "half");		printk(KERN_INFO PFX "%s: Flow control is %s for TX and "		       "%s for RX.\n",		       bp->dev->name,		       (bp->flags & B44_FLAG_TX_PAUSE) ? "on" : "off",		       (bp->flags & B44_FLAG_RX_PAUSE) ? "on" : "off");	}}static void b44_check_phy(struct b44 *bp){	u32 bmsr, aux;	if (bp->phy_addr == B44_PHY_ADDR_NO_PHY) {		bp->flags |= B44_FLAG_100_BASE_T;		bp->flags |= B44_FLAG_FULL_DUPLEX;		if (!netif_carrier_ok(bp->dev)) {			u32 val = br32(bp, B44_TX_CTRL);			val |= TX_CTRL_DUPLEX;			bw32(bp, B44_TX_CTRL, val);			netif_carrier_on(bp->dev);			b44_link_report(bp);		}		return;	}	if (!b44_readphy(bp, MII_BMSR, &bmsr) &&	    !b44_readphy(bp, B44_MII_AUXCTRL, &aux) &&	    (bmsr != 0xffff)) {		if (aux & MII_AUXCTRL_SPEED)			bp->flags |= B44_FLAG_100_BASE_T;		else			bp->flags &= ~B44_FLAG_100_BASE_T;		if (aux & MII_AUXCTRL_DUPLEX)			bp->flags |= B44_FLAG_FULL_DUPLEX;		else			bp->flags &= ~B44_FLAG_FULL_DUPLEX;		if (!netif_carrier_ok(bp->dev) &&		    (bmsr & BMSR_LSTATUS)) {			u32 val = br32(bp, B44_TX_CTRL);			u32 local_adv, remote_adv;			if (bp->flags & B44_FLAG_FULL_DUPLEX)				val |= TX_CTRL_DUPLEX;			else				val &= ~TX_CTRL_DUPLEX;			bw32(bp, B44_TX_CTRL, val);			if (!(bp->flags & B44_FLAG_FORCE_LINK) &&			    !b44_readphy(bp, MII_ADVERTISE, &local_adv) &&			    !b44_readphy(bp, MII_LPA, &remote_adv))				b44_set_flow_ctrl(bp, local_adv, remote_adv);			/* Link now up */			netif_carrier_on(bp->dev);			b44_link_report(bp);		} else if (netif_carrier_ok(bp->dev) && !(bmsr & BMSR_LSTATUS)) {			/* Link now down */			netif_carrier_off(bp->dev);			b44_link_report(bp);		}		if (bmsr & BMSR_RFAULT)			printk(KERN_WARNING PFX "%s: Remote fault detected in PHY\n",			       bp->dev->name);		if (bmsr & BMSR_JCD)			printk(KERN_WARNING PFX "%s: Jabber detected in PHY\n",			       bp->dev->name);	}}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?