From: Anoop P A <anoop.pa@xxxxxxxxx> This driver add support for triple speed mac (TSMAC) commonly found in MSP71xx family of SoC's. It will make use of phylib. Signed-off-by: Anoop P A <anoop.pa@xxxxxxxxx> --- drivers/net/Kconfig | 1 + drivers/net/Makefile | 1 + drivers/net/pmcmsp_tsmac/Kconfig | 36 + drivers/net/pmcmsp_tsmac/Makefile | 5 + drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.c | 4266 +++++++++++++++++++++++ drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.h | 105 + drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_local.h | 924 +++++ drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_mdiobus.c | 205 ++ drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_user.c | 2687 ++++++++++++++ 9 files changed, 8230 insertions(+), 0 deletions(-) create mode 100644 drivers/net/pmcmsp_tsmac/Kconfig create mode 100644 drivers/net/pmcmsp_tsmac/Makefile create mode 100644 drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.c create mode 100644 drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.h create mode 100644 drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_local.h create mode 100644 drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_mdiobus.c create mode 100644 drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_user.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 58706c1..c17ab81 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2531,6 +2531,7 @@ config S6GMAC will be called s6gmac. source "drivers/net/stmmac/Kconfig" +source "drivers/net/pmcmsp_tsmac/Kconfig" config PCH_GBE tristate "PCH Gigabit Ethernet" diff --git a/drivers/net/Makefile b/drivers/net/Makefile index adc48c4..0d6454a 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_JME) += jme.o obj-$(CONFIG_BE2NET) += benet/ obj-$(CONFIG_VMXNET3) += vmxnet3/ obj-$(CONFIG_BNA) += bna/ +obj-$(CONFIG_PMC_MSP_TSMAC) += pmcmsp_tsmac/ gianfar_driver-objs := gianfar.o \ gianfar_ethtool.o \ diff --git a/drivers/net/pmcmsp_tsmac/Kconfig b/drivers/net/pmcmsp_tsmac/Kconfig new file mode 100644 index 0000000..3ec3acc --- /dev/null +++ b/drivers/net/pmcmsp_tsmac/Kconfig @@ -0,0 +1,36 @@ +config PMC_MSP_TSMAC + depends on MSP_HAS_TSMAC + select PHYLIB + select CRC32 + select MII + tristate "PMC-Sierra MSP Triple-Speed Ethernet Support" + help + This enables support for the integrated 10/100/1000 Ethernet + of PMC-Sierra's MSP7140/MSP7150/MSP82XX SoC. + +if PMC_MSP_TSMAC + +config DESC_ALL_DSPRAM + bool "TX/RX Descriptors in DSPRAM" + depends on DMA_TO_SPRAM + default n + help + Turning this on puts TX/RX descriptors in DSPRAM. Otherwise they are in + DRAM. + +config TSMAC_LINELOOPBACK_FEATURE + bool "lineLoopBack command" + default n + help + Turning this on includes the lineLoopBack command in the driver's proc + interface. Echoing 1 into the lineLoopBack results in all rx packets + being transmitted out the same port. + +config TSMAC_TEST_CMDS + bool "test commands" + default n + help + Turning this on includes the testing commands in the driver's proc + interface. These are used internally by PMC. + +endif diff --git a/drivers/net/pmcmsp_tsmac/Makefile b/drivers/net/pmcmsp_tsmac/Makefile new file mode 100644 index 0000000..0f9a6bd --- /dev/null +++ b/drivers/net/pmcmsp_tsmac/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the PMC MSP TSMAC Ethernet drivers +# +obj-$(CONFIG_PMC_MSP_TSMAC) += pmcmsp_tsmac_mdiobus.o pmcmsp_tsmac.o pmcmsp_tsmac_user.o + diff --git a/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.c b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.c new file mode 100644 index 0000000..d819401 --- /dev/null +++ b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.c @@ -0,0 +1,4266 @@ +/****************************************************************************** +** Copyright 2006-2011 PMC-Sierra, Inc +** +** PMC-SIERRA DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER +** RESULTING FROM THE USE OF THIS SOFTWARE +** +** FILE NAME: pmcmsp_tsmac.c +** +** DESCRIPTION: Linux 2.6 driver for TSMAC 3 speed ethernet device. +** +** 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; +** +******************************************************************************/ + +#include <linux/kernel.h> +#include <msp_prom.h> +#include "pmcmsp_tsmac.h" +#include "pmcmsp_tsmac_local.h" + +/* + * Definition of MTU is the packet size minus CRC and MAC header, so + * 64 - 18 = 46 is the minimum MTU + */ +#define MIN_MTU_SIZE 46 +#define PMC_FAST +#define MAX_MTU_SIZE 1766 +/* PKT_SIZE = MTU_SIZE + MAC Header (14) + VLAN header (4) + CRC (4) */ +#define MAX_PKT_SIZE (MAX_MTU_SIZE + 22) +/* align the IP header to the 16 byte boundary */ +#define IP_HDR_ALIGN 2 +#define RXALIGN_PAD 4 +/* +* Pad so all possible RXALIGN can't overrun end of buffer. Typically RXALIGN +* is IP_HDR_ALIGN +*/ +#define TSMAC_BUFSIZE roundup((MAX_PKT_SIZE+RXALIGN_PAD), 4) + +/* roundup may report isse with preprocessor +#if (TSMAC_BUFSIZE >= 2048) +#error TSMAC_BUFSIZE exceeds maximum supported by hardware +#endif +*/ +/* PMON environment */ +#define TSMAC_VAR_MACADDR "ethaddr" + +/* NAPI quota per interface */ +#define TSMAC_NAPI_WEIGHT 64 + +#define VQ_INC_FREQUENCY 16 + +#ifdef CONFIG_TSMAC_VQ_TOKEN_CNT_WORKAROUND +/* frequency (number of packets) of incrementing the VQ token count */ + +/* + * Frequency (number of increments) of checking and correcting the VQ token + * count + */ +#define VQ_CORRECT_FREQUENCY 100 +#endif + +/* TSMAC register starting addresses */ +#define MSP_DMA_START ((u32)&((struct msp_regs *)0)->dma) +#define MSP_MAC_START ((u32)&((struct msp_regs *)0)->mac) +#define MSP_GPMII_START ((u32)&((struct msp_regs *)0)->gpmii) + +static int PMC_FAST tsmac_rx_poll(struct napi_struct *napi, int budget); +/* TSMAC driver information */ +const char version[] = "pmcmsp_tsmac.c:v2.0 01/09/2007, PMC-Sierra\n"; +const char cardname[] = "pmcmsp_tsmac"; +const char drv_version[] = "Revision: 1.1.2.2 "; +const char drv_reldate[] = "$Date: 2010/07/15 07:38:59 $"; +const char drv_file[] = __FILE__; + +/* reset values for the supported HW units */ +static u32 tsmac_rstpats[TSMAC_MAX_UNITS] = { + TSMAC_EA_RST, + TSMAC_EB_RST, + TSMAC_EC_RST +}; + +/** + * tsmac_ls_bit_pattern() - get the correct bit pattern for the link speed + * @speed: link speed + * + * This function returns the corresponding bit patters of @speed, which is + * used to configure the TSMAC GPMII registers. + */ +static int tsmac_ls_bit_pattern(int speed) +{ + if (speed == SPEED_1000) + return 0x2; /* bit pattern for link speed 1000 */ + if (speed == SPEED_100) + return 0x1; /* bit pattern for link speed 100 */ + + return 0x0; /* bit pattern for link speed 10 */ +} + +/** + * tsmac_duplex_bit_pattern() - get the correct bit pattern for the duplex mode + * @duplex: duplex mode + * + * This function returns the corresponding bit patterns of @duplex mode, which + * is used to configure the TSMAC GPMII registers. + */ +static int tsmac_duplex_bit_pattern(int duplex) +{ + if (duplex == DUPLEX_FULL) + return 0x0; /* bit pattern for full duplex */ + + return 0x1; /* bit pattern for half duplex */ +} + +/* + * Coherent path flush + */ +static inline void tsmac_coherent_flush(void) +{ + u32 __iomem *coherent; + u32 dummy_read; + + /* memory barrier to ensure read below not moved by compiler */ + barrier(); + + /* + * Do a dummy read of coherent path SDRAM to ensure that share control + * structure has made it all the way to SDRAM + */ + coherent = (u32 __iomem *)0xB7F00000; + + dummy_read = __raw_readl(coherent); + dummy_read++; +} + +/* + * DSPRAM path flush via the coherent path + */ +static inline void tsmac_dspram_flush(void) +{ +#ifdef CONFIG_DESC_ALL_DSPRAM + u32 __iomem *dspram; + u32 dummy_read; + + /* memory barrier to ensure read below not moved by compiler */ + barrier(); + + /* + * Do a dummy read of coherent path to ensure that share control + * structure has made it all the way to DSPRAM + */ + dspram = (u32 __iomem *)0xB8100000; + + dummy_read = __raw_readl(dspram); + dummy_read++; +#else /* do nothing */ +#endif +} + +/* + * CPU to TSMAC coherent path flush + */ +static inline void tsmac_cpu_to_tsmac_flush(unsigned int dev_id) +{ + u32 __iomem *tsmac; + u32 dummy_read; + + /* memory barrier to ensure read below not moved by compiler */ + barrier(); + + if (dev_id == 0) + tsmac = (u32 __iomem *)0xB860801C; + else if (dev_id == 2) + tsmac = (u32 __iomem *)0xB890801C; + else if (dev_id == 1) + tsmac = (u32 __iomem *)0xB870801C; + else + return; + + /* + * Do a dummy read of coherent path from CPU to TSMAC to ensure that + * previous write to the TSMAC has made through + */ + dummy_read = __raw_readl(tsmac); + dummy_read++; +} + +/* assume not using fastpath by default */ +#define CONFIG_USE_FASTPATH +#ifdef CONFIG_USE_FASTPATH +/* + * Fastpath flush. + */ +static inline void tsmac_fastpath_flush(void) +{ + u32 __iomem *fastpath; + u32 dummy_read; + + /* memory barrier to ensure read below not moved by compiler */ + barrier(); + + /* + * Do a dummy read of fast path SDRAM to ensure that share control + * structure has made it all the way to SDRAM + */ + fastpath = (u32 __iomem *)0x81000000; + + dummy_read = __raw_readl(fastpath); + dummy_read++; +} +#endif + +/* + * Allocates descriptor memory from dspram or offchip + */ +static inline void *tsmac_mem_alloc(size_t size) +{ +#ifdef CONFIG_DESC_ALL_DSPRAM /* scratch pad */ + /* + * Spinlocks should NOT be allocated from DSPRAM. Spinlocks use the + * Store Conditional (SC) instruction, which when executed to DSPRAM + * will never update its destination register or updates the register + * with an incorrect value. Implications are: a GPR may be written with + * wrong data, or; if the SC fails to write its GPR, the core will hang + * when executing an instruction dependant on that GPR. + * + */ + return msp_spram_alloc(size); +#else /* offchip DDR */ + + void *m = kmalloc(size, GFP_KERNEL | GFP_DMA); + if (m) + m = (void *)KSEG1ADDR(m); + + return m; +#endif +} + +/* + * Free descriptor memory + */ +static inline void tsmac_mem_free(void *m) +{ +#ifdef CONFIG_DESC_ALL_DSPRAM /* scratch pad */ + msp_spram_free(m); +#else /* offchip DDR */ + m = (void *)KSEG0ADDR(m); + kfree(m); +#endif +} + +/* + * Convert virutal address to physical address for DMA usage + */ +static inline dma_addr_t tsmac_dma_addr(void *addr) +{ +#ifdef CONFIG_DESC_ALL_DSPRAM /* scratch pad */ + return (dma_addr_t) spram2dma(addr); +#else + return (dma_addr_t) CPHYSADDR(addr); +#endif +} + +/* + * Allocate and align a max length skb. The sk_buff data buffer is mapped into + * the given buffer descriptor + */ +static TSMAC_ATTR_INLINE int tsmac_buffer_prepare(struct net_device *dev, + struct tsmac_private *lp, + struct Q_Desc *desc, + unsigned int index) +{ + struct sk_buff *skb; + dma_addr_t dma_skb; + + /* allocate skb */ + skb = dev_alloc_skb(TSMAC_BUFSIZE); + if (unlikely(skb == NULL)) + return -ENOMEM; + + lp->rx.skb_pp[index] = skb; + + /* + * Update fields in the given buffer descriptor and invalidate packet + * buffer + */ +#ifdef CONFIG_CACHE_OPTIMIZATION + dma_skb = pmc_skb_inv(skb); +#else + dma_skb = dma_map_single(lp->dev, skb->data, TSMAC_BUFSIZE, + DMA_FROM_DEVICE); +#endif + desc->FDBuffPtr = ((u32) dma_skb) & FD_RxBuff_Mask; + + /* align and fill out fields specific to our device */ + skb_reserve(skb, IP_HDR_ALIGN); + + skb->dev = dev; + + return 0; +} + +/* + * Release and unmap skb assigned by tsmac_buffer_prepare() + */ +static TSMAC_ATTR_INLINE void tsmac_buffer_clear(struct sk_buff **skb_pp, + unsigned int index) +{ + struct sk_buff *skb = skb_pp[index]; + if (likely(skb != NULL)) + dev_kfree_skb_any(skb); + + skb_pp[index] = NULL; +} + +/* + * This routine frees all skb memory associated with TX/RX descriptors + */ +static void tsmac_free_queues(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_tx *tx; + struct tsmac_rx *rx; + unsigned int qnum, desc_index; + struct Q_Desc *desc; + + /* free RX skb and reset RX descriptor ring */ + rx = &lp->rx; + desc = &rx->desc_p[0]; + if (desc != NULL) { + for (desc_index = 0; desc_index < rx->size; desc_index++) { + desc = &rx->desc_p[desc_index]; + desc->FDCtl = 0; + tsmac_buffer_clear(rx->skb_pp, desc_index); + } + /* reset RX descriptor ring */ + rx->index = 0; + } + + /* free TX skb and reset TX descriptor ring */ + for (qnum = TSMAC_DESC_PRI_HI; qnum < TSMAC_NUM_TX_CH; qnum++) { + tx = &lp->tx[qnum]; + desc = &tx->desc_p[0]; + if (desc != NULL) { + for (desc_index = 0; desc_index < tx->size; + desc_index++) { + desc = &tx->desc_p[desc_index]; + desc->FDCtl = 0; + tsmac_buffer_clear(tx->skb_pp, desc_index); + } + /* reset TX descriptor ring */ + tx->head = 0; + tx->tail = 0; + tx->qcnt = 0; + } + } +} + +/* + * Initialize the TX/RX queues by setting up TX/RX descriptor ring linked list + * and allocate skb memory. Since this routine allocates memory, care must + * be taken to free these memory (tsmac_free_queues) when they are no longer + * needed + */ +static int tsmac_init_queues(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_tx *tx; + struct tsmac_rx *rx; + unsigned int qnum, desc_index, next_desc; + struct Q_Desc *desc; + + rx = &lp->rx; + rx->index = 0; + desc_index = rx->size - 1; + do { + next_desc = desc_index + 1; + if (next_desc >= rx->size) + next_desc = 0; + + if (tsmac_buffer_prepare(dev, lp, &rx->desc_p[desc_index], + desc_index)) { + printk(KERN_ERR "Cannot allocate skbuff for %s RX " + "descriptors!\n", dev->name); + goto alloc_skb_fail; + } + + /* set descriptors */ + desc = &rx->desc_p[desc_index]; + desc->FDNext = tsmac_dma_addr(&rx->desc_p[next_desc]); + desc->FDStat = 0; + /* TODO: barrier and flush? */ + desc->FDCtl = (FD_DMA_Own | (TSMAC_BUFSIZE - RXALIGN_PAD)); + } while (desc_index--); + + /* setup descriptors for each TX channel */ + for (qnum = TSMAC_DESC_PRI_HI; qnum < TSMAC_NUM_TX_CH; qnum++) { + tx = &lp->tx[qnum]; + + /* initialize head, tail, and queue count */ + tx->head = 0; + tx->tail = 0; + tx->qcnt = 0; + + /* initialize TX descriptors */ + desc_index = tx->size - 1; + do { + next_desc = desc_index + 1; + if (next_desc >= tx->size) + next_desc = 0; + + tx->skb_pp[desc_index] = NULL; + + desc = &tx->desc_p[desc_index]; + desc->FDNext = tsmac_dma_addr(&tx->desc_p[next_desc]); + desc->FDBuffPtr = 0; + desc->FDCtl = 0; + desc->FDStat = 0; + } while (desc_index--); + } + + return TSMAC_SUCCESS; + + alloc_skb_fail: + /* free allocated resources */ + tsmac_free_queues(dev); + return TSMAC_Q_INIT_ERROR; +} + +/* + * This routine frees all memory associated with the TX and RX descriptor + * rings. Note that before calling this routine, tsmac_free_queues should be + * called first to free the skb memory otherwise memory leaks + */ +static void tsmac_free_desc(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_tx *tx; + struct tsmac_rx *rx; + unsigned int qnum; + + /* free RX descriptor ring */ + rx = &lp->rx; + if (rx->desc_base != NULL) { + tsmac_mem_free(rx->desc_base); + tsmac_mem_free(rx->skb_base); + rx->desc_base = NULL; + rx->desc_p = NULL; + rx->skb_base = NULL; + rx->skb_pp = NULL; + } + + /* free TX descriptor ring */ + for (qnum = TSMAC_DESC_PRI_HI; qnum < TSMAC_NUM_TX_CH; qnum++) { + tx = &lp->tx[qnum]; + if (tx->desc_base != NULL) { + tsmac_mem_free(tx->desc_base); + tsmac_mem_free(tx->skb_base); + tx->desc_base = NULL; + tx->desc_p = NULL; + tx->skb_base = NULL; + tx->skb_pp = NULL; + } + } +} + +/* + * Allocate memory for TX/RX descriptors + */ +static int tsmac_alloc_desc(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_tx *tx; + struct tsmac_rx *rx; + int qnum; + size_t size_desc_rx, size_desc_tx[TSMAC_NUM_TX_CH]; + size_t size_skb_rx, size_skb_tx[TSMAC_NUM_TX_CH]; + + size_desc_rx = lp->rx.size * sizeof(struct Q_Desc); + size_skb_rx = lp->rx.size * sizeof(struct sk_buff *); + + size_desc_tx[TSMAC_DESC_PRI_HI] = lp->tx[TSMAC_DESC_PRI_HI].size * + sizeof(struct Q_Desc); + size_skb_tx[TSMAC_DESC_PRI_HI] = lp->tx[TSMAC_DESC_PRI_HI].size * + sizeof(struct sk_buff *); + size_desc_tx[TSMAC_DESC_PRI_LO] = lp->tx[TSMAC_DESC_PRI_LO].size * + sizeof(struct Q_Desc); + size_skb_tx[TSMAC_DESC_PRI_LO] = lp->tx[TSMAC_DESC_PRI_LO].size * + sizeof(struct sk_buff *); + + /* allocate memory for RX descriptors and skb pointers */ + rx = &lp->rx; + if (rx->desc_base == NULL) { + rx->desc_base = tsmac_mem_alloc(size_desc_rx); + if (rx->desc_base == NULL) { + printk(KERN_ERR "Cannot allocate space for %s RX " + "descriptors!\n", dev->name); + goto alloc_desc_fail; + } + memset(rx->desc_base, 0, size_desc_rx); + + rx->skb_base = tsmac_mem_alloc(size_skb_rx); + if (rx->skb_base == NULL) { + printk(KERN_ERR "Cannot allocate space for %s RX " + "skb address hodler!\n", dev->name); + goto alloc_desc_fail; + } + memset(rx->skb_base, 0, size_skb_rx); + + } + rx->desc_p = (struct Q_Desc *)((u32) rx->desc_base); + rx->skb_pp = (struct sk_buff **)((u32) rx->skb_base); + + /* allocate memory for each channel of the TX descriptors */ + for (qnum = TSMAC_DESC_PRI_HI; qnum < TSMAC_NUM_TX_CH; qnum++) { + tx = &lp->tx[qnum]; + if (tx->desc_base == NULL) { + tx->desc_base = tsmac_mem_alloc(size_desc_tx[qnum]); + if (tx->desc_base == NULL) { + printk(KERN_ERR "Cannot allocate space for %s " + "TX descriptors!\n", dev->name); + goto alloc_desc_fail; + } + memset(tx->desc_base, 0, size_desc_tx[qnum]); + + tx->skb_base = tsmac_mem_alloc(size_skb_tx[qnum]); + if (tx->skb_base == NULL) { + printk(KERN_ERR "Cannot allocate space for %s " + "skb address!\n", dev->name); + goto alloc_desc_fail; + } + memset(tx->skb_base, 0, size_skb_tx[qnum]); + } + tx->desc_p = (struct Q_Desc *)((u32) tx->desc_base); + tx->skb_pp = (struct sk_buff **)((u32) tx->skb_base); + } + + return TSMAC_SUCCESS; + + alloc_desc_fail: + /* free resoruces */ + tsmac_free_desc(dev); + return TSMAC_Q_INIT_ERROR; +} + +/* + * Configure the TSMAC clocking based on MII mode and link speed. Note this + * routine should only be called when the MAC subsystem is held in reset + */ +static void tsmac_config_clks(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 bit_shift; + u32 tmp_reg = 0; + u32 ctl_cmd; + + switch (lp->unit) { + case 0: + bit_shift = SYS_MACA_Shift; + break; + case 1: + bit_shift = SYS_MACB_Shift; + break; + case 2: + default: + bit_shift = SYS_MACC_Shift; + break; + } + + tmp_reg = (tsmac_ls_bit_pattern(lp->speed) << SYS_LinkSpeed_Shift); + /* always use TX_CLK_IN for RMII */ + tmp_reg |= (TSMAC_RMII_TX_CLK_IN << SYS_RMII_Clk_Shift); + + if (lp->speed == SPEED_1000) { + /* use fast sys clk for Gbps */ + tmp_reg |= (TSMAC_SYS_CLK_FAST << SYS_Sclk_Sel_Shift); + tmp_reg |= (lp->mii_type << SYS_Mode_Shift); + } else { + /* use slow sys clk for 10/100 Mbps */ + tmp_reg |= (TSMAC_SYS_CLK_SLOW << SYS_Sclk_Sel_Shift); + if (lp->mii_type == TSMAC_MT_GMII) + tmp_reg |= (TSMAC_MT_MII << SYS_Mode_Shift); + else + tmp_reg |= (lp->mii_type << SYS_Mode_Shift); + } + + tmp_reg <<= bit_shift; + + /* configure clock manager */ + ctl_cmd = tsmac_read((void *)TSMAC_CTRL_OUTPUT); + ctl_cmd &= ~(0xFE << bit_shift); + ctl_cmd |= tmp_reg; + + /* config system register */ + tsmac_write(ctl_cmd, (void *)TSMAC_CTRL_OUTPUT); +} + +/* + * Reset the MAC subsystem + */ +static void tsmac_mac_reset(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + int i; + u32 rstpat; + + /* stop TX/RX after completion of any current packets */ + tsmac_write(MAC_HaltReq, &lp->reg_map->mac.mac_ctl); + + /* + * Flush various data path including CPU -> TSMAC, CPU -> DDR, and + * CPU -> DSPRAM + */ + tsmac_cpu_to_tsmac_flush(lp->unit); + tsmac_coherent_flush(); + tsmac_dspram_flush(); + + /* wait to make sure finish transactions on the current packet */ + mdelay(100); + + mutex_lock(&lp->bus.mdio_lock); + + /* assert subsystem warm reset */ + rstpat = tsmac_rstpats[lp->unit]; + tsmac_write(rstpat, (void *)RST_SET_REG); + + /* set clock registers before taking out of reset */ + tsmac_config_clks(dev); + + /* allow clocks to stabilize, then bring the subsystem out of reset */ + mdelay(1); + tsmac_write(rstpat, (void *)RST_CLR_REG); + for (i = 0; i < 10; i++) { + if ((tsmac_read((void *)RST_STS_REG) & rstpat) == 0) + break; + ndelay(100); + } + + mutex_unlock(&lp->bus.mdio_lock); +} + +/* + * Initialize the GPMII registers. Enabling the TX/RX datapaths + * is deferred to tsmac_start_txrx(). + */ +static void tsmac_init_gpmii(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 gpmii_cmd; + + /* + * Setup GPMII Configuration Mode register. + * - MII mode is based on the connection type or the user + * specified setting. + * - Duplex setting and link speed selection are based on the + * user specified setting or the auto negotiation result. + */ + if ((lp->mii_type == TSMAC_MT_GMII) && (lp->speed != SPEED_1000)) + gpmii_cmd = TSMAC_MT_MII & GPMII_Mode_Mask; + else + gpmii_cmd = lp->mii_type & GPMII_Mode_Mask; + + gpmii_cmd |= (tsmac_ls_bit_pattern(lp->speed) + << GPMII_LinkSpeed_Shift); + gpmii_cmd |= (tsmac_duplex_bit_pattern(lp->duplex) + << GPMII_Dplx_Shift); + + /* configure GPMII */ + tsmac_write(gpmii_cmd, &lp->reg_map->gpmii.conf_mode); + + gpmii_cmd = 0; + if (lp->duplex == DUPLEX_FULL) + gpmii_cmd |= GPMII_Force_Crs_Col_En; + if (gpmii_cmd) + tsmac_write(gpmii_cmd, &lp->reg_map->gpmii.conf_general); +} + +/* + * Toggle enable/disable status of the flood control logic + */ +static int set_floodctl_reg(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 saved_rxctl; + u8 flood_enable; + + /* update device details */ + flood_enable = lp->vqnflood.flood_enable; + + /* RXEN should be disabled while enabling/disabling flood control */ + saved_rxctl = tsmac_read(&lp->reg_map->mac.rx_ctl); + tsmac_write(saved_rxctl & ~Rx_En, &lp->reg_map->mac.rx_ctl); + +#if defined(TSMAC_FLOOD_WORKAROUND) /* always enable flood control */ + saved_rxctl |= Rx_FloodEn; +#else + if (flood_enable == 1) + saved_rxctl |= Rx_FloodEn; + else + saved_rxctl &= ~Rx_FloodEn; +#endif /* TSMAC_FLOOD_WORKAROUND */ + tsmac_write(saved_rxctl, &lp->reg_map->mac.rx_ctl); + return 0; +} + +#ifdef TSMAC_FLOOD_WORKAROUND +/* + * Configure flood control to pass all packets (no dropping) + */ +static void flood_pass_all(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 i; + + /* clear classifier RAM */ + for (i = L2_ARC_Class_Min; i <= L2_ARC_Class_Max; i += 4) { + tsmac_write(i, &lp->reg_map->mac.arc_addr); + tsmac_write(0, &lp->reg_map->mac.arc_data); + } + + /* disable L2 rules */ + tsmac_write(0, &lp->reg_map->mac.l2_rule_ena); + + /* set default VQ to "0" */ + tsmac_write(0, &lp->reg_map->mac.vq_conf); + + /* don't drop packets in VQ0 */ + tsmac_write(VQ_TC_Drop_Disable, &lp->reg_map->mac.vq_token_cnt[0]); +} +#endif /* TSMAC_FLOOD_WORKAROUND */ + +/* + * Set network device multicast address to ARC table + */ +static void tsmac_set_multicast_list(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 reg; + + if (dev->flags & IFF_PROMISC) { + /* Enable promiscuous mode */ + tsmac_write(ARC_CompEn | ARC_BroadAcc | ARC_GroupAcc | + ARC_StationAcc, &lp->reg_map->mac.arc_ctl); + } else if ((dev->flags & IFF_ALLMULTI) || + dev->mc.count > ARC_ENTRY_MAX - 3) { + /* Disable promiscuous mode, use normal mode. */ + tsmac_write(ARC_CompEn | ARC_BroadAcc | ARC_GroupAcc, + &lp->reg_map->mac.arc_ctl); + } else if (dev->mc.count) { + struct netdev_hw_addr *ha; + int i = 0; + int ena_bits = ARC_Ena_Bit(ARC_ENTRY_MAC); + + tsmac_write(0, &lp->reg_map->mac.arc_ctl); + /* Walk the address list, and load the filter */ + netdev_for_each_mc_addr(ha, dev) { + /* use free ARC location */ + tsmac_set_arc_entry(dev, ARC_ENTRY_PAUSE_RX + 1 + i, + ha->addr); + ena_bits |= ARC_Ena_Bit(ARC_ENTRY_PAUSE_RX + 1 + i); + i++; + } + reg = tsmac_read(&lp->reg_map->mac.arc_ena) | ena_bits; + tsmac_write(reg, &lp->reg_map->mac.arc_ena); + tsmac_write(ARC_CompEn | ARC_BroadAcc, + &lp->reg_map->mac.arc_ctl); + } else { + reg = tsmac_read(&lp->reg_map->mac.arc_ena) | + ARC_Ena_Bit(ARC_ENTRY_MAC); + tsmac_write(reg, &lp->reg_map->mac.arc_ena); + tsmac_write(ARC_CompEn | ARC_BroadAcc, + &lp->reg_map->mac.arc_ctl); + } +} + +/* + * Configure the TSMAC registers, including ARC table. + * TX/RX datapath enabling is deferred to tsmac_start_txrx(). + */ +static int tsmac_init_tsmac(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 max_len, tmp; + u8 tmp_addr[6]; + + /* set maximum frame size */ + max_len = dev->mtu + 18; + tsmac_write(((max_len + 4) << 16) | max_len, + &lp->reg_map->mac.max_length); + + /* clear entire ARC table (MAC filtering, PAUSE, Classification) */ + for (tmp = 0; tmp <= L2_ARC_Class_Max; tmp += 4) { + tsmac_write(tmp, &lp->reg_map->mac.arc_addr); + tsmac_write(0, &lp->reg_map->mac.arc_data); + } + + /* load device MAC address to ARC table entry and enable it */ + tsmac_set_arc_entry(dev, ARC_ENTRY_MAC, dev->dev_addr); + tmp = tsmac_read(&lp->reg_map->mac.arc_ena) | + ARC_Ena_Bit(ARC_ENTRY_MAC); + tsmac_write(tmp, &lp->reg_map->mac.arc_ena); + + /* load special multicast address to ARC table entry and enable it */ + tmp_addr[0] = 0x01; + tmp_addr[1] = 0x80; + tmp_addr[2] = 0xC2; + tmp_addr[3] = 0x00; + tmp_addr[4] = 0x00; + tmp_addr[5] = 0x01; + tsmac_set_arc_entry(dev, ARC_ENTRY_PAUSE_RX, tmp_addr); + tmp = tsmac_read(&lp->reg_map->mac.arc_ena) | + ARC_Ena_Bit(ARC_ENTRY_PAUSE_RX); + tsmac_write(tmp, &lp->reg_map->mac.arc_ena); + + /* Set multicast flags according to what was set previously */ + tsmac_set_multicast_list(dev); + + /* ensure that MAC control register HALTIMM and HALTREQ bits are 0 */ + tsmac_write(0x00000000, &lp->reg_map->mac.mac_ctl); + + /* set flow control */ + tsmac_set_pause_param(dev); + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) { + flood_pass_all(dev); + return 0; + } +#endif /* TSMAC_FLOOD_WORKAROUND */ + return tsmac_set_vqnpause(dev); +} + +/* + * Initialize the TSMAC DMA. + */ +static void tsmac_init_dma(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + dma_addr_t dma_desc; + + /* set Tx Poll count of each Tx channel */ + tsmac_write(TXPOLLCNT_CH0, &lp->reg_map->dma.txpollcnt_ch0); + tsmac_write(TXPOLLCNT_CH1, &lp->reg_map->dma.txpollcnt_ch1); + + /* enable interrupts */ + tsmac_write(INT_EN_CMD, &lp->reg_map->dma.int_ena); + + /* configure IP Header offset of vlan and non-vlan packets */ + lp->iphdr_offset.vlan = IPHDR_OFFSET_IPV4_VLAN; + lp->iphdr_offset.nvlan = IPHDR_OFFSET_IPV4_NVLAN; + tsmac_write((lp->iphdr_offset.vlan << DMA_OffsetVLAN_Shift) | + lp->iphdr_offset.nvlan, &lp->reg_map->dma.iphdr_offset); + + /* + * Configure TX descriptor pointer registers for channel 0 and 1 with + * the address of the first descriptor in the TX descriptor linked + * list + */ + dma_desc = tsmac_dma_addr(&lp->tx[0].desc_p[0]); + tsmac_write(((u32) dma_desc) & DMA_TxDesc_AddrMask, + &lp->reg_map->dma.txdesc_ch0); + dma_desc = tsmac_dma_addr(&lp->tx[1].desc_p[0]); + tsmac_write(((u32) dma_desc) & DMA_TxDesc_AddrMask, + &lp->reg_map->dma.txdesc_ch1); + + /* configure RX descriptor pointer registers */ + dma_desc = tsmac_dma_addr(&lp->rx.desc_p[0]); + tsmac_write(((u32) dma_desc) & DMA_RxDesc_AddrMask, + &lp->reg_map->dma.rxdesc); + + /* + * configure DMA to align 2 byte for the recieved packets that + * it writes into system memory + */ + tsmac_write(DMA_CTL_CMD, &lp->reg_map->dma.dma_ctl); +} + +/* + * Flip the switches in the correct order to enable + * the Tx/Rx datapaths in each subsystem. + */ +static int tsmac_enable_txrx(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 cmd; + int n = 0; + + /* enable the TX path in the order of GPMII, MAC, DMA */ + cmd = tsmac_read(&lp->reg_map->gpmii.conf_general); + cmd |= GPMII_TxDataPath_En; + tsmac_write(cmd, &lp->reg_map->gpmii.conf_general); + tsmac_write(TX_CFG(TX_CTL_DIS, lp->flow_ctl.enable), + &lp->reg_map->mac.tx_ctl); + tsmac_write(DMA_TxInit_DescList, &lp->reg_map->dma.dma_init); + + /* enable the rx path in the order of DMA, MAC, GPMII */ + tsmac_write(DMA_RxInit_DescList, &lp->reg_map->dma.dma_init); + tsmac_write(RX_CTL_ENA, &lp->reg_map->mac.rx_ctl); + tsmac_write(tsmac_read(&lp->reg_map->gpmii.conf_general) + | GPMII_RxDataPath_En, &lp->reg_map->gpmii.conf_general); + /* + * As per the TSMAC eng doc, wait until RxDataPath_En = 1 + */ + cmd = tsmac_read(&lp->reg_map->gpmii.conf_general); + while (n < 10 && !(cmd & GPMII_RxDataPath_En)) { + ++n; + mdelay(10); + cmd = tsmac_read(&lp->reg_map->gpmii.conf_general); + } + if (n >= 10) { + printk(KERN_WARNING "Receive datapath not enabled.\n"); + return -EBUSY; + } + return 0; +} + +/* + * Initialize the MAC, DMA, and GPMII control registers. Activation order + * should be: + * + * TX - GPMII, MAC, DMA + * RX - DMA, MAC, GPMII + */ +static int tsmac_mac_init(struct net_device *dev) +{ + int ret; + + /* set some device structure parameters */ + dev->tx_queue_len = TX_RING_SIZE_DEF; + + /* initialize DMA */ + tsmac_init_dma(dev); + + ret = tsmac_init_tsmac(dev); + if (ret) + goto out; + + /* initialize GPMII */ + tsmac_init_gpmii(dev); + + out: + return ret; +} + +/** + * tsmac_check_phy_driver() - re-attach if the generic PHY driver is used + * @dev: mac interface + * + * If the PHY/Switch connected to the interface is currently using a generic + * PHY driver, then disconnect and reconnect again to see if there is better + * driver available (0xffffffff is the phy_id of generic PHY driver). + * Return 0 if the generic PHY driver is never used or the appropriate driver + * is attached to replace generic PHY driver. Return -1 if there is an error + * occur while attaching the appropriate PHY driver. + */ +static int tsmac_check_phy_driver(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct phy_driver *phydrv; + + if (lp->phyptr == NULL) + return 0; + + phydrv = to_phy_driver(lp->phyptr->dev.driver); + + /* only check for new driver is the attached one is generic */ + if (phydrv->phy_id != 0xffffffff) + return 0; + + if (lp->conn_type == MSP_CT_ETHPHY) { + phy_disconnect(lp->phyptr); + + lp->phyptr = tsmac_mii_probe(dev, &tsmac_adjust_link); + + if (lp->phyptr == NULL) + return -1; + + phydrv = to_phy_driver(lp->phyptr->dev.driver); + } else if (lp->conn_type == MSP_CT_ETHSWITCH) { + char bus_id[MII_BUS_ID_SIZE]; + char bus_unit[4]; + sprintf(bus_unit, "%x", lp->bus_unit); + phy_detach(lp->phyptr); + + snprintf(bus_id, MII_BUS_ID_SIZE, PHY_ID_FMT, bus_unit, + lp->phy_addr); + lp->phyptr = phy_attach(dev, bus_id, 0, + PHY_INTERFACE_MODE_GMII); + } + + return 0; +} + +/* + * This routine seizes all possible TX/RX traffic so the user can schedule the + * tsmac_restart later to restart the device + */ +static void tsmac_shutdown(struct net_device *dev) +{ + printk(KERN_INFO "TSMAC (tsmac_shutdown) %s: Device shutdown\n", + dev->name); + + /* disable device based interrupt line */ + disable_irq(dev->irq); + + /* turn off carrier so the polling can quit */ + netif_carrier_off(dev); + + /* stop the egress queue */ + netif_stop_queue(dev); + + /* + * Don't stop the RX polling routine here since it can wait for a + * long time and this routine can be running in an interrupt context + * or softirq + */ +} + +/* + * This routine serves as a recovery mechanism. The user should always schedule + * a workqueue for this routine (since it can sleep) and before calling this + * routine, tsmac_shutdown should be called first + */ +static void tsmac_restart(struct work_struct *work) +{ + struct tsmac_private *lp = container_of(work, struct tsmac_private, + restart_task.work); + struct net_device *dev = lp->ndev; + unsigned long flags; + + printk(KERN_INFO "TSMAC (tsmac_restart) %s: Device restart\n", + dev->name); + + spin_lock(&lp->restart_lock); + + /* + * If the device is already being closed, we should quit tsmac_restart + */ + if (atomic_read(&lp->close_flag)) + goto restart_exit; + + /* stop RX polling routine */ + napi_disable(&lp->napi); + + /* we need the locks since TX runs at the bottom half */ + spin_lock_irqsave(&lp->tx_lock, flags); + + if (lp->conn_type == MSP_CT_ETHPHY) + phy_stop(lp->phyptr); + else + netif_carrier_off(dev); + + /* prevent control plane from accessing registers */ + spin_lock(&lp->control_lock); + /* reset the MAC */ + tsmac_mac_reset(dev); + /* allow control plane to access registers */ + spin_unlock(&lp->control_lock); + + /* Try to restart the adaptor. */ + tsmac_free_queues(dev); + tsmac_init_queues(dev); + + tsmac_mac_init(dev); + + if (tsmac_check_phy_driver(dev)) + goto restart_exit; + + if (lp->conn_type == MSP_CT_ETHPHY) + /* PAL takes care of netif_carrier_on */ + phy_start(lp->phyptr); + else + /* assume carrier is up */ + netif_carrier_on(dev); + + /* enable TX/RX path in the correct order */ + tsmac_enable_txrx(dev); + + /* wake up the egress queue */ + dev->trans_start = jiffies; + netif_wake_queue(dev); + + /* start RX polling */ + napi_enable(&lp->napi); + + /* re-enable IRQ */ + enable_irq(dev->irq); + + /* unlock TX and restore IRQ */ + spin_unlock_irqrestore(&lp->tx_lock, flags); + + restart_exit: + spin_unlock(&lp->restart_lock); + atomic_dec(&lp->restart_pending_cnt); +} + +/* + * Interrupt handler + */ +static irqreturn_t tsmac_interrupt(int irq, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + u32 status; + + /* collect irq status */ + status = tsmac_read(&lp->reg_map->dma.int_src); + + /* nothing */ + if (status == 0) + return IRQ_NONE; + + /* bad address read/write or system bus error */ + if (status & (IntEn_BadAddrRd | IntEn_BadAddrWr | IntEn_SysBusErr)) { + printk(KERN_WARNING "TSMAC (tsmac_interrupt) %s: bad address " + "or system bus err\n", dev->name); + /* restart the device and return */ + tsmac_shutdown(dev); + if (schedule_delayed_work_on(atomic_read(&lp->timer_task_cpu), + &lp->restart_task, 0)) + atomic_inc(&lp->restart_pending_cnt); + + tsmac_write(status, &lp->reg_map->dma.int_src); + return IRQ_HANDLED; + } + + /* RX or RX exhausted */ + if (status & (IntSrc_MACRx | IntSrc_RxDescEx)) { + /* disable RX receive and RX exhausted interrupts */ + tsmac_write((INT_EN_CMD & ~IntEn_RxDescEx), + &lp->reg_map->dma.int_ena); + tsmac_write(RX_CTL_DIS, &lp->reg_map->mac.rx_ctl); + + /* + * If not already scheduled, schedule the NAPI RX polling + * routine + */ + if (napi_schedule_prep(&lp->napi)) + __napi_schedule(&lp->napi); + + /* + * Acknowledge RX receive interrupt is needed otherwise + * it might be triggered right away even if interrupt + * has been disabled due to the way the HW is architected + */ + tsmac_write(IntSrc_MACRx, &lp->reg_map->dma.int_src); + + lp->sw_stats.rx_ints++; + } + + /* + * Only take actions when TX complete interrupt from Channel 0 (high + * priority) is received. TX complete interrupt from Channel 1 (low + * priority) can be ignored safely + */ + if (status & IntSrc_MACTx_CH0) { + /* disable TX complete interrupt */ + tsmac_write(TX_CFG(TX_CTL_DIS, lp->flow_ctl.enable), + &lp->reg_map->mac.tx_ctl); + + /* wake up Linux egress queue */ + netif_wake_queue(dev); + + lp->sw_stats.tx_ints++; + } + + /* acknowledge all interrupts except RX receive and RX exhausted */ + tsmac_write(status & (~IntSrc_MACRx) & (~IntSrc_RxDescEx), + &lp->reg_map->dma.int_src); + + /* + * Flush the CPU to TSMAC path here to make sure the TSMAC gets all + * important commands + */ + tsmac_cpu_to_tsmac_flush(lp->unit); + + return IRQ_HANDLED; +} + +/* + * Software-aided TCP/UDP checksum calculator using previously calculated + * checksum based on IPv4 packets (done in HW) + */ +static TSMAC_ATTR_INLINE int tsmac_ipv6_chksum_calc(u16 *data_ptr, + const u16 hw_chksum, + const int vlan_flag) +{ + u32 accum; + u16 chksum, len, payload_len; + u16 *cur_ptr, *end_ptr; + u8 vlan_offset; + + if (unlikely(data_ptr == NULL)) + return 1; + + if (vlan_flag) /* VLAN packet, add 4-byte offset */ + vlan_offset = 2; + else + vlan_offset = 0; + + /* IPv6 payload length is TCP header + TCP data */ + payload_len = data_ptr[2 + vlan_offset]; + + /* HW calculated only valid if len >= 20 bytes */ + if (unlikely(payload_len < 20)) + return 1; + + accum = hw_chksum; + + /* add next header */ + accum += (data_ptr[3 + vlan_offset] >> 8); + + /* add upper 16-bit of SA0 */ + accum += data_ptr[4 + vlan_offset]; + + /* add lower 16-bit of SA0 in format SA0[15:8], 8b0 */ + accum += (data_ptr[5 + vlan_offset] & 0xFF00); + + /* add upper 16 bit of SA1 */ + accum += data_ptr[6 + vlan_offset]; + + /* add 20 since IPv6 uses payload length instead of IP packet length */ + accum += 20; + + /* add the missing 38 bytes of data */ + len = payload_len; + len -= 20; + len >>= 1; + + cur_ptr = &data_ptr[len + 11 + vlan_offset]; + end_ptr = &data_ptr[20 + (payload_len >> 1) + vlan_offset]; + + while (cur_ptr < end_ptr) + accum += *cur_ptr++; + + /* for the odd byte */ + if (payload_len & 0x0001) { + accum -= data_ptr[len + 11 + vlan_offset] & 0xFF00; + accum += (*cur_ptr) & 0xFF00; + } + + /* + * Keep only the last 16 bits of the 32 bit sum and add back the + * carries + */ + while (accum >> 16) + accum = (accum & 0xFFFF) + (accum >> 16); + + chksum = accum; + + if (chksum == 0xFFFF) /* checksum correct */ + return 0; + else + return 1; +} + +/* + * Verify L4 TCP/UDP checksum. The HW calculates the checksum with IPv4 pseudo + * header. Therefore, if it's an IPv6 packet, the driver needs to cover the + * missing fields + */ +static TSMAC_ATTR_INLINE void tsmac_chksum_verify(struct sk_buff *skb, + struct Q_Desc *desc) +{ + u16 checksum; + unsigned char ip_version; + + skb->ip_summed = CHECKSUM_NONE; + + /* hardware calculated IPv4-based TCP/UDP checksum */ + checksum = (desc->FDCtl & FD_RxChksum_Mask) >> FD_RxChksum_Shift; + + /* validate TCP/UDP checksum based on protocol */ + switch (skb->protocol) { + case (ETH_P_IP): /* IPv4 */ + if (likely(checksum == 0xFFFF)) /* checksum correct */ + skb->ip_summed = CHECKSUM_UNNECESSARY; + break; + + case (ETH_P_IPV6): /* IPv6 */ + if (tsmac_ipv6_chksum_calc((u16 *) skb->data, checksum, 0) == 0) + skb->ip_summed = CHECKSUM_UNNECESSARY; + break; + + case (ETH_P_8021Q): /* VLAN */ + /* + * Obtain IP version using the version field in the IP header. + * Since VLAN packet has 4-byte header between the Ethernet + * header and the IP header, the version field starts on the + * 5th byte of skb->data + */ + ip_version = ((unsigned char *)skb->data)[4]; + if ((ip_version & 0xF0) == 0x40) { /* IPv4 */ + if ((desc->FDCtl & FD_RxChksum_Mask) == + FD_RxChksum_Mask) + skb->ip_summed = CHECKSUM_UNNECESSARY; + } else if ((ip_version & 0xF0) == 0x60) { /* IPv6 */ + if (tsmac_ipv6_chksum_calc((u16 *) skb->data, + checksum, 1) == 0) + skb->ip_summed = CHECKSUM_UNNECESSARY; + } + break; + + default: /* unknow protocol, let kernel handle it */ + break; + } +} + +/* + * Increment VQ token count by num_tokens. + */ +static TSMAC_ATTR_INLINE void tsmac_rx_token_inc(struct tsmac_private *lp, + const u32 vq_num, + const u16 num_tokens) +{ + void *addr; + u32 data; + + if (num_tokens == 0) + return; + + addr = &lp->reg_map->mac.vq_token_cnt[vq_num]; + data = num_tokens; + + tsmac_write(data, addr); +} + +#ifdef CONFIG_TSMAC_VQ_TOKEN_CNT_WORKAROUND +/* + * Read the current VQ token count and check the number of low-priority packets + * in the descriptor ring + */ +static unsigned int tsmac_rx_token_check(struct tsmac_private *lp, + const u32 vq_num, + unsigned int rx_index) +{ + int i; + struct Q_Desc *desc = &lp->rx.desc_p[rx_index]; + unsigned int init_cnt; + unsigned int lp_pkt_cnt = 0; /* low priority packet count */ + int error_cnt; + + /* + * If the number of low-priority packets + the current VQ token count + * > initial VQ token count, we have token count overflow error due to + * the HW bug. In this case, we force the VQ token count value back to + * its initial value + */ + + init_cnt = lp->vqnflood.vq_config[vq_num].vq_token_count; + + /* + * Go through the RX descriptor ring and count the number of + * low-priority packets in the ring + * + * Note that we should always start with the oldest packet in the ring + * to minimize the counting error + */ + for (i = 0; i < lp->rx.size; i++) { + if ((desc->FDCtl & FD_DMA_Own) != FD_DMA_Own) { + if ((desc->FDStat & Rx_VQ_Mask) == vq_num) + lp_pkt_cnt++; + } + rx_index++; + if (rx_index >= lp->rx.size) + rx_index = 0; + desc = &lp->rx.desc_p[rx_index]; + } + + /* read current VQ token count */ + lp_pkt_cnt += tsmac_read(&lp->reg_map->mac.vq_token_cnt[vq_num]) & + VQ_TC_Token_Cnt_Mask; + + error_cnt = lp_pkt_cnt - init_cnt + VQ_INC_FREQUENCY; + if (error_cnt > 0) { + if (error_cnt >= VQ_INC_FREQUENCY) + return 0; + else + return VQ_INC_FREQUENCY - error_cnt; + } else { + /* no error */ + return VQ_INC_FREQUENCY; + } +} +#endif /* CONFIG_TSMAC_VQ_TOKEN_CNT_WORKAROUND */ + +/* + * Control the frequency of updating the VQ token + */ +static TSMAC_ATTR_INLINE void tsmac_rx_token_ctrl(struct tsmac_private *lp, + const u32 vq_num, + const unsigned int rx_index) +{ +#ifdef CONFIG_TSMAC_VQ_TOKEN_CNT_WORKAROUND + /* + * Only do VQ token count check and update when drop disable is NOT + * set. When drop disable is set the VQ token count value will be + * ignored in the HW so there's no point to update it here + */ + if (lp->vqnflood.vq_config[vq_num].vq_drop_disable == 0) { + struct tsmac_rx_vq_token *vq_token = &lp->vq_token[vq_num]; + /* number of tokens to be incremented */ + unsigned int inc_token; + + vq_token->pkt_cnt++; + + /* + * Update the token count once every VQ_INC_FREQUENCY packets + * to minimize collision rate + */ + if (vq_token->pkt_cnt >= VQ_INC_FREQUENCY) { + vq_token->pkt_cnt = 0; + vq_token->update_cnt++; + + /* + * Check for possible VQ token errors once every + * VQ_CORRECT_FREQUENCY increments + */ + if (vq_token->update_cnt >= VQ_CORRECT_FREQUENCY) { + /* + * Normally, if there's no error detected in + * the VQ token count, we increment the VQ + * token count by VQ_INC_FREQUENCY. + * + * If there's error detected, we compensate + * the error by NOT incrementing or doing less + * increment + */ + vq_token->update_cnt = 0; + inc_token = tsmac_rx_token_check(lp, vq_num, + rx_index); + + } else { + inc_token = VQ_INC_FREQUENCY; + } + + /* update token count */ + tsmac_rx_token_inc(lp, vq_num, inc_token); + } + } +#else + /* + * Only do VQ token count check and update when drop disable is NOT + * set. When drop disable is set the VQ token count value will be + * ignored in the HW so there's no point to update it here + */ + if (lp->vqnflood.vq_config[vq_num].vq_drop_disable == 0) { + struct tsmac_rx_vq_token *vq_token = &lp->vq_token[vq_num]; + vq_token->pkt_cnt++; + + if (vq_token->pkt_cnt >= VQ_INC_FREQUENCY) { + /* update token count */ + tsmac_rx_token_inc(lp, vq_num, vq_token->pkt_cnt); + vq_token->pkt_cnt = 0; + } + } +#endif +} + +void tsmac_rx_register_hook(struct net_device *dev, + tsmac_hook_function fp, void *data) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* Assume that the rx_hook function and the private data are all + * un-registered, user cannot invoke the register for multiple t + * imes to update the registration, SHOULD unregister first. + */ + + /* Protecting multiple writers with the lp->control lock, and as + * sign the private data prior the rx_hook function. + */ + spin_lock_bh(&lp->control_lock); + rcu_assign_pointer(lp->rx_priv, data); + spin_unlock_bh(&lp->control_lock); + synchronize_rcu(); + + /* Protecting multiple writers with the lp->control lock, now en + * able the rx_hook function. + */ + spin_lock_bh(&lp->control_lock); + rcu_assign_pointer(lp->tsmac_rx_hook, fp); + spin_unlock_bh(&lp->control_lock); + synchronize_rcu(); +} +EXPORT_SYMBOL(tsmac_rx_register_hook); + +void tsmac_rx_unregister_hook(struct net_device *dev, + tsmac_hook_function fp, void **data) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* Protecting multiple writers with the lp->control lock, and de + * tach the rx_hook function prior the private data. + */ + spin_lock_bh(&lp->control_lock); + rcu_assign_pointer(lp->tsmac_rx_hook, NULL); + spin_unlock_bh(&lp->control_lock); + synchronize_rcu(); + + /* Protecting multiple writers with the lp->control lock, and re + * lease the private data, notice that the data MAY be allocated + * by 3rd party function and need to be passed back for cleanup. + */ + spin_lock_bh(&lp->control_lock); + if (NULL != data) + *data = rcu_dereference(lp->rx_priv); + + rcu_assign_pointer(lp->rx_priv, NULL); + spin_unlock_bh(&lp->control_lock); + synchronize_rcu(); +} +EXPORT_SYMBOL(tsmac_rx_unregister_hook); + +void tsmac_tx_register_hook(struct net_device *dev, + tsmac_hook_function fp, void *data) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* Assume that the tx_hook function and the private data are all + * un-registered, user cannot invoke the register for multiple t + * imes to update the registration, SHOULD unregister first. + */ + + /* Protecting multiple writers with the lp->control lock, and as + * sign the private data prior the tx_hook function. + */ + spin_lock_bh(&lp->control_lock); + rcu_assign_pointer(lp->tx_priv, data); + spin_unlock_bh(&lp->control_lock); + synchronize_rcu(); + + /* Protecting multiple writers with the lp->control lock, now en + * able the tx_hook function. + */ + spin_lock_bh(&lp->control_lock); + rcu_assign_pointer(lp->tsmac_tx_hook, fp); + spin_unlock_bh(&lp->control_lock); + synchronize_rcu(); +} +EXPORT_SYMBOL(tsmac_tx_register_hook); + +void tsmac_tx_unregister_hook(struct net_device *dev, + tsmac_hook_function fp, void **data) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* Protecting multiple writers with the lp->control lock, and de + * tach the tx_hook function prior the private data. + */ + spin_lock_bh(&lp->control_lock); + rcu_assign_pointer(lp->tsmac_tx_hook, NULL); + spin_unlock_bh(&lp->control_lock); + synchronize_rcu(); + + /* Protecting multiple writers with the lp->control lock, and re + * lease the private data, notice that the data MAY be allocated + * by 3rd party function and need to be passed back for cleanup. + */ + spin_lock_bh(&lp->control_lock); + if (NULL != data) + *data = rcu_dereference(lp->tx_priv); + + rcu_assign_pointer(lp->tx_priv, NULL); + spin_unlock_bh(&lp->control_lock); + synchronize_rcu(); +} +EXPORT_SYMBOL(tsmac_tx_unregister_hook); + +/* + * skb manipulation, set up skb data structure and verify L4 checksum before + * sending it up to the network stack + */ +static TSMAC_ATTR_INLINE int tsmac_rx_skb(struct net_device *dev, + struct Q_Desc *desc, + struct sk_buff *skb, + const u16 pkt_len) +{ + struct tsmac_private *lp = netdev_priv(dev); + tsmac_hook_function tsmac_rx_hook = NULL; + + /* initialize skb length */ + skb_put(skb, pkt_len); + + /* strip out 4-byte CRC */ + pskb_trim_rcsum(skb, skb->len - 4); + + rcu_read_lock(); + tsmac_rx_hook = rcu_dereference(lp->tsmac_rx_hook); + + if (unlikely(tsmac_rx_hook)) { + void *priv = rcu_dereference(lp->rx_priv); + + if (unlikely(tsmac_rx_hook(&skb, dev, priv) < 0)) { + rcu_read_unlock(); + return -1; + } + } + rcu_read_unlock(); + + /* update skb protocol field */ + skb->protocol = eth_type_trans(skb, dev); + + /* verify L4 TCP/UDP checksum */ + tsmac_chksum_verify(skb, desc); + + return 0; +} + +static TSMAC_ATTR_INLINE void tsmac_rx_loopback(struct net_device *, + struct sk_buff *); +/* + * Go through the RX descriptor ring to process each received packet and send + * it up to the network stack until either 1) running out of quota or 2) RX + * descriptor ring is empty + */ +static TSMAC_ATTR_INLINE unsigned int tsmac_rx_get_pkts(struct net_device *dev, + struct tsmac_private + *lp, + const unsigned int + work_limit) +{ + struct Q_Desc *desc; + + unsigned int desc_index; + u32 desc_ctl; + unsigned int pkt_processed = 0; + const unsigned int rx_size = lp->rx.size; + struct sk_buff *skb; + struct net_device_stats *const lx_stats = &lp->lx_stats; + struct tsmac_stats_sw *const sw_stats = &lp->sw_stats; + + desc_index = lp->rx.index; + desc = &lp->rx.desc_p[desc_index]; + desc_ctl = desc->FDCtl; + + /* loop until the descriptor ring is empty or running out of quota */ + while (((desc_ctl & FD_DMA_Own) == 0) && (pkt_processed < work_limit)) { + /* get descriptor information */ + const u32 desc_status = desc->FDStat; + const u16 pkt_len = desc_ctl & FD_RxBuffLn_Mask; + const u32 vq_num = desc_status & Rx_VQ_Mask; + const bool sop = ((desc_ctl & FD_SOP) != 0); + + /* + * if we've got a good packet, the skb address needs to be + * saved before a new skb is allocated + */ + skb = lp->rx.skb_pp[desc_index]; + prefetch(skb->data - NET_IP_ALIGN); + prefetch(skb->data - NET_IP_ALIGN + L1_CACHE_BYTES); + + /* received error packet, update error statistics */ + if (unlikely(!sop || ((desc_ctl & FD_EOP) == 0) || + ((desc_status & Rx_Good) == 0) || + (pkt_len > MAX_PKT_SIZE))) { + skb = NULL; + + if (sop) { + lx_stats->rx_errors++; + + if (desc_status & Rx_LenErr) + lx_stats->rx_length_errors++; + if (desc_status & Rx_LongErr) + sw_stats->rx_long_errors++; + if (desc_status & Rx_CRCErr) + lx_stats->rx_crc_errors++; + if (desc_status & Rx_AlignErr) + lx_stats->rx_frame_errors++; + if (desc_ctl & FD_RxTrunc) + sw_stats->rx_trunc_errors++; + } + + goto tsmac_rx_next_desc; + } + + /* + * If a replacement skb cannot be allocated, drop the packet + * and reuse the existing buffer + */ + if (unlikely(tsmac_buffer_prepare(dev, lp, desc, + desc_index) != 0)) { + skb = NULL; + lx_stats->rx_dropped++; + goto tsmac_rx_next_desc; + } +#ifdef CONFIG_USE_FASTPATH + /* DDR coherent flush if skb is using fast path */ + tsmac_coherent_flush(); +#endif + + /* skb manipulation */ + if (unlikely(tsmac_rx_skb(dev, desc, skb, pkt_len) < 0)) { + skb = NULL; + lx_stats->rx_dropped++; + goto tsmac_rx_next_desc; + } + + /* update statistics */ + lx_stats->rx_packets++; + sw_stats->rx_bytes += pkt_len; + if (desc_status & Rx_MCast) + lx_stats->multicast++; + sw_stats->rx_vq_frames[vq_num]++; + +#ifdef CONFIG_TSMAC_TEST_CMDS + /* checksum failed bad packet or not supported + * by controller */ + if (skb->ip_summed == CHECKSUM_NONE) { + if (skb->protocol == ETH_P_8021Q) + sw_stats->rx_nochksum_vlan++; + else + sw_stats->rx_nochksum_nonvlan++; + } +#endif + + dev->last_rx = jiffies; + +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE + if (lp->loopback_enable) + tsmac_rx_loopback(dev, skb); +#endif + + tsmac_rx_next_desc: + /* + * Put memory barrier here to make sure descriptor control + * register is last set + */ + barrier(); + + /* give the descriptor back to DMA */ + desc->FDCtl = (FD_DMA_Own | (TSMAC_BUFSIZE - RXALIGN_PAD)); + + /* advance to the next descriptor */ + desc_index++; + if (unlikely(desc_index >= rx_size)) + desc_index = 0; + desc = &lp->rx.desc_p[desc_index]; + + if (sop) { + /* update VQ token count after each packet received */ + tsmac_rx_token_ctrl(lp, vq_num, desc_index); + } +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE + if (lp->loopback_enable) + return 0; +#endif + /* send skb up to the network stack */ + if (likely(skb != NULL)) + netif_receive_skb(skb); + + pkt_processed++; + desc_ctl = desc->FDCtl; + } /* end while loop */ + + lp->rx.index = desc_index; + return pkt_processed; +} + +/* + * RX polling routine used in NAPI + */ +static int PMC_FAST tsmac_rx_poll(struct napi_struct *napi, int budget) +{ + struct tsmac_private *lp = container_of(napi, struct tsmac_private, + napi); + struct net_device *dev = lp->ndev; + + unsigned int work_done; + + unsigned long irq_flag, irq_status; + + /* carrier is down, quit right away */ + if (unlikely(!netif_carrier_ok(dev) || !netif_running(dev))) { + napi_complete(napi); + + printk(KERN_DEBUG "%s: Quit RX polling routine\n", dev->name); + + /* enable RX interrupts */ + local_irq_save(irq_flag); + + /* enable RX receive interrupt */ + tsmac_write(RX_CTL_ENA, &lp->reg_map->mac.rx_ctl); + + /* ack possible RX exhausted interrupts to prevent deadlock */ + tsmac_write(IntSrc_RxDescEx, &lp->reg_map->dma.int_src); + + /* enable RX exhausted interrupt */ + tsmac_write(INT_EN_CMD, &lp->reg_map->dma.int_ena); + + /* flush to make sure TSMAC gets it */ + tsmac_cpu_to_tsmac_flush(lp->unit); + + local_irq_restore(irq_flag); + + return 0; + } + + /* flush descriptors into the DSPRAM */ + tsmac_dspram_flush(); + + /* loop until descriptor ring is empty or running out of quota */ + work_done = tsmac_rx_get_pkts(dev, lp, budget); + + /* update device based quota and CPU budget */ + budget -= work_done; + + /* running out of quota */ + if (work_done >= budget) { + /* got some room, clear possible RX exhausted interrupt */ + tsmac_write(IntSrc_RxDescEx, &lp->reg_map->dma.int_src); + + /* update RX exhausted counter */ + irq_status = tsmac_read(&lp->reg_map->dma.int_src); + if (irq_status & IntSrc_RxDescEx) + lp->lx_stats.rx_fifo_errors++; + + return 1; + } + + /* + * If we got here, we've finished processing all outstanding packets + * and the RX descriptor ring is empty + */ + + /* tell the kernel that we are done */ + napi_complete(napi); + + /* + * enabling RX receive and RX exhausted interrupt cannot be interrupted + */ + local_irq_save(irq_flag); + + /* enable RX receive interrupt */ + tsmac_write(RX_CTL_ENA, &lp->reg_map->mac.rx_ctl); + + /* ack possible RX exhausted interrupts to prevent deadlock */ + tsmac_write(IntSrc_RxDescEx, &lp->reg_map->dma.int_src); + + /* enable RX exhausted interrupt */ + tsmac_write(INT_EN_CMD, &lp->reg_map->dma.int_ena); + + /* flush to make sure TSMAC gets it */ + tsmac_cpu_to_tsmac_flush(lp->unit); + + /* update RX exhausted counter */ + irq_status = tsmac_read(&lp->reg_map->dma.int_src); + if (irq_status & IntSrc_RxDescEx) + lp->lx_stats.rx_fifo_errors++; + + local_irq_restore(irq_flag); + + /* + * There is a window above where new packet can come in before RX + * interrupt is enabled. Therefore, we do the check below to make + * sure no packet will be missed + */ + + /* make sure descriptors going to the DSPRAM */ + tsmac_dspram_flush(); + + /* advance to the next RX descriptor */ + if ((lp->rx.desc_p[lp->rx.index].FDCtl & FD_DMA_Own) != + FD_DMA_Own && napi_reschedule(napi)) { + /* disable interrupts */ + tsmac_write((INT_EN_CMD & ~IntEn_RxDescEx), + &lp->reg_map->dma.int_ena); + tsmac_write(RX_CTL_DIS, &lp->reg_map->mac.rx_ctl); + /* flush to make sure TSMAC gets it */ + tsmac_cpu_to_tsmac_flush(lp->unit); + + /* get Linux to schedule RX polling */ + return 1; + } + + return 0; +} + +#define TX_STA_ERR (Tx_ExColl | Tx_SQErr | Tx_LCarr | Tx_ExDefer | Tx_LateColl) +/* + * Update TX statistics counters + */ +static TSMAC_ATTR_INLINE void tsmac_update_tx_stat(struct tsmac_private *lp, + struct Q_Desc *desc) +{ + u32 status = desc->FDStat; + + if (unlikely(status & Tx_TxColl_Mask)) + lp->lx_stats.collisions += status & Tx_TxColl_Mask; + + if (likely(!(status & TX_STA_ERR))) { + lp->lx_stats.tx_packets++; + /* remember to add 4 bytes CRC */ + lp->sw_stats.tx_bytes += (desc->FDCtl & FD_TxBuffLn_Mask) + 4; + } else { + lp->lx_stats.tx_errors++; + } +} + +/* +* Place holder for integrating the TX QoS mechanism with Linux TC. Assume all +* outgoing packets are high priority for now. +*/ +static inline u32 tsmac_get_tx_qnum(struct sk_buff *skb, + struct tsmac_private *lp) +{ + /* + * If the skb->priority value is outside of supported range, then treat + * as low priority packet. Otherwise, use the skb->priority to egress + * queue mapping. + */ + if (skb->priority >= TSMAC_NUM_SKB_PRIORITY) + return TSMAC_DESC_PRI_LO; + else + return lp->egress_prio[skb->priority]; +} + +/* +* Clear transmitted packets. This routine also returns number of packets been +* cleared +*/ +static TSMAC_ATTR_INLINE u32 tsmac_tx_clear(struct tsmac_private *lp, + const u32 ch) +{ + struct tsmac_tx *tx = &lp->tx[ch]; + u32 tail = tx->tail; + struct Q_Desc *desc = &tx->desc_p[tail]; + u32 pkt_cleared = 0; + + while (1) { + /* DMA owns the descriptor */ + if (desc->FDCtl & FD_DMA_Own) + break; + + /* TX descriptor ring is empty */ + if (tx->qcnt == 0) + break; + + /* update statistics */ + tsmac_update_tx_stat(lp, desc); + + /* clear descriptor and free skb */ + tsmac_buffer_clear(tx->skb_pp, tail); + + /* advance to the next descriptor */ + tx->qcnt--; + tail++; + if (unlikely(tail >= tx->size)) + tail = 0; + desc = &tx->desc_p[tail]; + + pkt_cleared++; + } + + tx->tail = tail; + + return pkt_cleared; +} + +/* +* TSMAC TX, called from the Linux network stack +*/ +static int PMC_FAST tsmac_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_tx *tx; + struct Q_Desc *desc; + u32 qnum = 0; + u32 qhead; + dma_addr_t dma_skb; + tsmac_hook_function tsmac_tx_hook = NULL; + + spin_lock(&lp->tx_lock); + + /* carrier is turned down by somebody else */ + if (unlikely(!netif_carrier_ok(dev))) { + /* drop packet, update counter, and free skb */ + lp->lx_stats.tx_dropped++; + dev_kfree_skb_any(skb); + spin_unlock(&lp->tx_lock); + return NETDEV_TX_OK; + } + + rcu_read_lock(); + tsmac_tx_hook = rcu_dereference(lp->tsmac_tx_hook); + if (unlikely(tsmac_tx_hook)) { + void *priv = rcu_dereference(lp->tx_priv); + + if (unlikely(tsmac_tx_hook(&skb, dev, priv) < 0)) { + rcu_read_unlock(); + lp->lx_stats.tx_dropped++; + dev_kfree_skb_any(skb); + spin_unlock(&lp->tx_lock); + return NETDEV_TX_OK; + } + } + rcu_read_unlock(); + + /* determine priority of packet to transmit */ + qnum = tsmac_get_tx_qnum(skb, lp); + + /* clean up previously transmitted packets */ + tsmac_tx_clear(lp, qnum); + + tx = &lp->tx[qnum]; + + /* + * If the high priority (Channel 0) TX descriptor ring is full, stop + * the Linux egress queue. Also, enable the TX complete interrupt so + * we can re-enable the egress queue when there's room. + * + * If the low priority (Channel 1) TX descriptor ring is full, simply + * drop the packet. + * + * Must have at least 2 descriptors owned by the driver so the + * controller does not loop around and re-transmit an old packet. + */ + if (unlikely(tx->qcnt >= tx->size - 2)) { + lp->sw_stats.tx_full[qnum]++; + if (qnum == TSMAC_DESC_PRI_HI) { + /* stop queue and enable TX complete interrupt */ + netif_stop_queue(dev); + tsmac_write(TX_CFG(TX_CTL_ENA, lp->flow_ctl.enable), + &lp->reg_map->mac.tx_ctl); + + /* flush to make sure TSMAC gets it */ + tsmac_cpu_to_tsmac_flush(lp->unit); + spin_unlock(&lp->tx_lock); + return NETDEV_TX_BUSY; + } else { /* low priority packet */ + /* drop packet and free skb */ + lp->lx_stats.tx_dropped++; + dev_kfree_skb_any(skb); + spin_unlock(&lp->tx_lock); + return NETDEV_TX_OK; + } + } + + qhead = tx->head; + desc = &tx->desc_p[qhead]; + tx->head = qhead + 1; + if (unlikely(tx->head >= tx->size)) + tx->head = 0; + tx->skb_pp[qhead] = skb; + + /* map buffer to device and writeback cache to packet buffer */ +#ifdef CONFIG_CACHE_OPTIMIZATION + dma_skb = pmc_skb_wback_inv(skb); +#else + dma_skb = dma_map_single(lp->dev, skb->data, skb->len, DMA_TO_DEVICE); +#endif + + /* + * If skb using fast path, flush the fastpath DXU here to make sure + * it goes in the DRAM before the device uses it + */ +#ifdef CONFIG_USE_FASTPATH + tsmac_fastpath_flush(); +#else +#ifdef CONFIG_DESC_ALL_DSPRAM + tsmac_coherent_flush(); +#endif +#endif + + tx->qcnt++; + + /* set up TX descriptor */ + desc->FDBuffPtr = ((u32) dma_skb) & FD_TxBuff_Mask; + + /* + * FD_DMA_Own should be the last field to modify in the descriptor; + * therefore, we use barrier() here to prevent compiler optimization + * from moving it around + */ + barrier(); + desc->FDCtl = (FD_DMA_Own | FD_SOP | FD_EOP | + (skb->len & FD_TxBuffLn_Mask)); + + /* wake up the transmitter */ + switch (qnum) { + case TSMAC_DESC_PRI_HI: + tsmac_write(DMA_TxWakeUp_CH0, &lp->reg_map->dma.dma_init); + break; + case TSMAC_DESC_PRI_LO: + tsmac_write(DMA_TxWakeUp_CH1, &lp->reg_map->dma.dma_init); + break; + default: + break; + } + + dev->trans_start = jiffies; + + spin_unlock(&lp->tx_lock); + return NETDEV_TX_OK; +} /* tsmac_tx() */ + +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE +/* +* Do a line loopback by transmitting the received packets on the same +* interface +*/ +static TSMAC_ATTR_INLINE void tsmac_rx_loopback(struct net_device *dev, + struct sk_buff *skb) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_tx *tx; + struct Q_Desc *desc; + u32 qnum = TSMAC_DESC_PRI_HI; + u32 qhead; + dma_addr_t dma_skb; + + skb->dev = dev; + dev_hard_header(skb, dev, ntohs(skb->protocol), NULL, NULL, skb->len); + + tsmac_tx_clear(lp, qnum); + + tx = &lp->tx[qnum]; + + /* there's no room in the TX descriptor ring */ + if (tx->qcnt >= tx->size - 2) { + dev_kfree_skb_any(skb); + return; + } + + /* we have room, so get the next available Tx desc */ + qhead = tx->head; + desc = &tx->desc_p[qhead]; + tx->head = qhead + 1; + if (tx->head >= tx->size) + tx->head = 0; + + /* map buffer to device AND writeback cache to packet buffer */ + dma_skb = dma_map_single(lp->dev, skb->data, skb->len, DMA_TO_DEVICE); + + /* update TX queue counter */ + tx->qcnt++; + + /* set up TX descriptor */ + desc->FDBuffPtr = ((u32) dma_skb) & FD_TxBuff_Mask; + + barrier(); + desc->FDCtl = (FD_DMA_Own | FD_SOP | FD_EOP | + (skb->len & FD_TxBuffLn_Mask)); + + /* wake up transmitter */ + tsmac_write(DMA_TxWakeUp_CH0, &lp->reg_map->dma.dma_init); +} +#endif + +/* + * Timer function when expired checking the HW counter statistics to prevent + * overflow + */ +static void tsmac_stats_check(unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + + /* Acquire lock to make sure TSMAC is not restarting */ + tsmac_update_hw_stats(dev); + + /* reschedule itself */ + lp->stats_timer.expires = jiffies + HZ * STATS_CHK_TIME; + add_timer_on(&lp->stats_timer, atomic_read(&lp->timer_task_cpu)); +} + +/* + * Initialize the TSMAC interface, invoked some time after booting when + * 'ifconfig' is called + */ +static int tsmac_open(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + int err = -EBUSY; + + /*TODO: spin_lock required ? */ + + /* allocate memory for TX/RX descriptors */ + if (tsmac_alloc_desc(dev) == TSMAC_Q_INIT_ERROR) { + printk(KERN_WARNING "TSMAC %s: Unable to allocate queues\n", + dev->name); + /* free descriptor memory */ + tsmac_free_desc(dev); + goto out_err; + } + + /* set up the descriptor ring and allocate skb */ + if (tsmac_init_queues(dev) == TSMAC_Q_INIT_ERROR) { + printk(KERN_WARNING "TSMAC %s: Unable to set up queues\n", + dev->name); + /* free skb and descriptor memory */ + tsmac_free_queues(dev); + tsmac_free_desc(dev); + goto out_err; + } + + /* initialize the MAC */ + spin_lock_bh(&lp->control_lock); + tsmac_mac_reset(dev); + spin_unlock_bh(&lp->control_lock); + + err = tsmac_mac_init(dev); + if (err) + goto out_reset_mac; + + err = tsmac_check_phy_driver(dev); + if (err) + goto out_reset_mac; + + if (lp->conn_type == MSP_CT_ETHPHY) + /* PAL takes care of netif_carrier_on */ + phy_start(lp->phyptr); + else + /* assume carrier is up */ + netif_carrier_on(dev); + + /* initialize the statistics timer */ + init_timer(&lp->stats_timer); + lp->stats_timer.expires = jiffies + HZ * STATS_CHK_TIME; + lp->stats_timer.data = (u32) dev; + lp->stats_timer.function = tsmac_stats_check; + add_timer_on(&lp->stats_timer, atomic_read(&lp->timer_task_cpu)); + /* start up the Linux egress queue */ + netif_start_queue(dev); + + /* enable NAPI */ + napi_enable(&lp->napi); + + atomic_set(&lp->close_flag, 0); + + /* Allocate the IRQ */ + if (request_irq(dev->irq, &tsmac_interrupt, + IRQF_SHARED, cardname, dev)) { + printk(KERN_WARNING "TSMAC %s: unable to reserve IRQ %d\n", + dev->name, dev->irq); + goto out_reset_mac; + } + /* enable TX/RX path in the correct order */ + tsmac_enable_txrx(dev); + + return 0; + + out_reset_mac: + spin_lock_bh(&lp->control_lock); + tsmac_mac_reset(dev); + spin_unlock_bh(&lp->control_lock); + out_err: + return err; +} + +/* + * Close the TSMAC interface and clean up, usually invoked when 'ifconfig down' + * is called + */ +static int tsmac_close(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + unsigned long flags; + + /* This guarantees no IRQ (of the dev) will be called */ + free_irq(dev->irq, dev); + + if (lp->conn_type == MSP_CT_ETHPHY) + phy_stop(lp->phyptr); + else + /* bring down the carrier */ + netif_carrier_off(dev); + + /* stop the egress queue */ + netif_stop_queue(dev); + + /* Disable NAPI */ + napi_disable(&lp->napi); + + /* set close flag so tsmac_restart cannot run */ + atomic_set(&lp->close_flag, 1); + + /* mark MAC link is down */ + lp->link = 0; + + /* + * If tsmac_restart has already been scheduled, wait until all of them + * quit + */ + if (atomic_read(&lp->restart_pending_cnt)) { + cancel_delayed_work_sync(&lp->restart_task); + atomic_set(&lp->restart_pending_cnt, 0); + } + + /* delete the stats timer */ + del_timer_sync(&lp->stats_timer); + + /* + * Since we are resetting the MAC subsystem, we should disable local + * IRQ so we don't get interrupted + */ + local_irq_save(flags); + + /* disable TSMAC interrupts */ + tsmac_write(0, &lp->reg_map->dma.int_ena); + tsmac_write(Rx_FloodEn, &lp->reg_map->mac.rx_ctl); + tsmac_write(TX_CFG(TX_CTL_DIS, lp->flow_ctl.enable), + &lp->reg_map->mac.tx_ctl); + + /* Clean up the adaptor. */ + spin_lock(&lp->control_lock); + tsmac_mac_reset(dev); + spin_unlock(&lp->control_lock); + + /* free the the skb memeory and reset the descriptor ring */ + tsmac_free_queues(dev); + + /* free the descriptor ring memory */ + tsmac_free_desc(dev); + + local_irq_restore(flags); + + return 0; +} + +/* + * Change MTU size (called via ifconfig ethX mtu 1000) + */ +static int tsmac_change_mtu(struct net_device *dev, int new_mtu) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 length; + + if ((new_mtu < MIN_MTU_SIZE) || (new_mtu > MAX_MTU_SIZE)) + return -EINVAL; + + dev->mtu = new_mtu; + length = new_mtu + 18; + + /* set MTU for both VLAN and non-VLAN frames */ + tsmac_write(((length + 4) << 16) | length, + &lp->reg_map->mac.max_length); + + return 0; +} + +/* + * If there is a transmit timeout we schedule to restart the device + */ +static void tsmac_tx_timeout(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + + tsmac_shutdown(dev); + if (schedule_delayed_work_on(atomic_read(&lp->timer_task_cpu), + &lp->restart_task, 0)) + atomic_inc(&lp->restart_pending_cnt); +} + +/* + * Get the current counter statistics + */ +static struct net_device_stats *tsmac_get_stats(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* get TX/RX bytes from software counters */ + lp->lx_stats.rx_bytes = (u32) lp->sw_stats.rx_bytes; + lp->lx_stats.tx_bytes = (u32) lp->sw_stats.tx_bytes; + + if (netif_running(dev)) + tsmac_update_hw_stats(dev); + + return &lp->lx_stats; +} + +static const struct net_device_ops tsmac_netdev_ops = { + .ndo_open = tsmac_open, + .ndo_start_xmit = tsmac_tx, + .ndo_stop = tsmac_close, + .ndo_change_mtu = tsmac_change_mtu, + .ndo_set_multicast_list = tsmac_set_multicast_list, + .ndo_tx_timeout = tsmac_tx_timeout, + .ndo_do_ioctl = tsmac_ioctl, + .ndo_get_stats = tsmac_get_stats, + .ndo_set_mac_address = eth_mac_addr, +}; + +/* + * Probe for a TSMAC interface + */ +static int tsmac_probe(struct platform_device *pldev) +{ + int unit = pldev->id; + int i; + int err = -ENODEV; + u8 macaddr[8]; + struct tsmac_private *lp; + char tmp_str[12]; + struct net_device *dev = NULL; + struct resource *res; + void *mapaddr; + int ret; + struct eth_platform_data *eth_data = pldev->dev.platform_data; + + /* check the hardware parameters */ + if (unit < 0) + goto out_err; + + /* Sanity checks on parameters */ + if (unit >= TSMAC_MAX_UNITS) + goto out_err; + + dev = alloc_etherdev(sizeof(struct tsmac_private)); + if (dev == NULL) { + err = -ENOMEM; + goto out_err; + } + SET_NETDEV_DEV(dev, &pldev->dev); + dev_set_drvdata(&pldev->dev, dev); + + lp = netdev_priv(dev); + memset(lp, 0, sizeof(struct tsmac_private)); + + res = platform_get_resource(pldev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_ERR "tsmac_probe: IOMEM resource not found for " + "tsmac%d\n", unit); + goto out_netdev; + } + dev->base_addr = res->start; + + /* reserve the memory region */ + if (!request_mem_region(res->start + MSP_DMA_START, + sizeof(struct msp_dma_regs), "tsmac dma")) { + err = -EBUSY; + goto out_netdev; + } + if (!request_mem_region(res->start + MSP_MAC_START, + sizeof(struct msp_mac_regs), "tsmac mac")) { + err = -EBUSY; + goto out_memdma; + } + if (!request_mem_region(res->start + MSP_GPMII_START, + sizeof(struct msp_gpmii_regs), "tsmac gpmii")) { + err = -EBUSY; + goto out_memmac; + } + + /* remap the memory */ + mapaddr = ioremap_nocache(res->start, res->end - res->start + 1); + if (!mapaddr) { + printk(KERN_WARNING + "TSMAC %s: unable to ioremap address 0x%08x\n", + dev->name, res->start); + goto out_memgpmii; + } + lp->reg_map = mapaddr; + dev->irq = platform_get_irq(pldev, 0); + + /* set the logical and hardware units */ + lp->unit = unit; + + /* set the loopback status */ + lp->loopback_enable = 0; + + /* set default TX/RX descriptor ring size and mask */ + lp->rx.size = RX_RING_SIZE_DEF; + lp->tx[0].size = TX_RING_SIZE_DEF; + lp->tx[1].size = TX_RING_SIZE_DEF; + + /* Store the struct device pointer, for DMA ops */ + lp->dev = &pldev->dev; + lp->ndev = dev; + + /* set connection type using platform data. The connection type can + be changed by the user via /proc/net/ethX/connType */ + if (eth_data == NULL) { + printk(KERN_WARNING + "TSMAC%d: No default connection type provided" + " assuming phy\n", lp->unit); + ret = tsmac_set_conntype(dev, MSP_CT_ETHPHY); + } else + ret = tsmac_set_conntype(dev, eth_data->conn_type); + + if (ret) + goto out_unmap; + + /* parse MAC address */ + /* Retrieve the mac address from the PROM */ + snprintf(tmp_str, 12, TSMAC_VAR_MACADDR "%d", lp->unit); + if (get_ethernet_addr(tmp_str, macaddr)) { + printk(KERN_INFO "TSMAC%d: no MAC addr specified\n", lp->unit); + } else if (macaddr[0] & 0x01) { + printk(KERN_WARNING + "TSMAC%d: bad Multicast MAC addr specified " + "%02x:%02x:%02x:%02x:%02x:%02x\n", + lp->unit, + macaddr[0], macaddr[1], macaddr[2], + macaddr[3], macaddr[4], macaddr[5]); + } else { + /* MAC address */ + dev->addr_len = ETH_ALEN; + for (i = 0; i < dev->addr_len; i++) + dev->dev_addr[i] = macaddr[i]; + printk(KERN_INFO "TSMAC%d: assigned MAC address: " + "%02x:%02x:%02x:%02x:%02x:%02x\n", + lp->unit, macaddr[0], macaddr[1], macaddr[2], + macaddr[3], macaddr[4], macaddr[5]); + } + + /* if no default PHY setup, TSMAC will scan for an available PHY */ + if (eth_data != NULL) { + lp->bus_unit = eth_data->bus_unit; + lp->phy_addr = eth_data->phy_addr; + } + + lp->bus.priv = dev; + tsmac_register_bus(&lp->bus, lp->unit); + + if (lp->conn_type == MSP_CT_ETHPHY) { + lp->phyptr = tsmac_mii_probe(dev, &tsmac_adjust_link); + lp->link = 0; + lp->speed = 0; + lp->duplex = -1; + + if (lp->phyptr == NULL) { + err = -1; + goto out_unmap; + } + } else if (lp->conn_type == MSP_CT_ETHSWITCH) { + char bus_id[MII_BUS_ID_SIZE]; + char bus_unit[4]; + sprintf(bus_unit, "%x", lp->bus_unit); + + snprintf(bus_id, MII_BUS_ID_SIZE, PHY_ID_FMT, bus_unit, + lp->phy_addr); + lp->phyptr = phy_attach(dev, bus_id, 0, + PHY_INTERFACE_MODE_GMII); + } + + /* set the various call back functions */ + SET_ETHTOOL_OPS(dev, &tsmac_ethtool_ops); + dev->watchdog_timeo = TX_TIMEOUT * HZ; + dev->netdev_ops = &tsmac_netdev_ops; + + /* initialize NAPI */ + netif_napi_add(dev, &lp->napi, tsmac_rx_poll, TSMAC_NAPI_WEIGHT); + + /* set default CPU for timer tasks */ + atomic_set(&lp->timer_task_cpu, TSMAC_TASK_CPU_DEFAULT); + + /* initialize a work queue for the restart task */ + INIT_DELAYED_WORK(&lp->restart_task, tsmac_restart); + + /* load default values of VQs and Flood Control details */ + tsmac_config_def_vqnpause(dev); + + /* set default mapping for skb->priority values to egress queues */ + for (i = 0; i < TSMAC_NUM_SKB_PRIORITY; i++) + lp->egress_prio[i] = TSMAC_DESC_PRI_HI; + +#ifndef MODULE + { + static u8 printed_version; + + if (!printed_version++) { + printk(KERN_INFO "%s: %s, %s\n", + drv_file, drv_version, drv_reldate); + printk(KERN_INFO "%s: PMC-Sierra," + " www.pmc-sierra.com\n", drv_file); + } + } +#endif /* !MODULE */ + + printk(KERN_INFO "TSMAC%d: found at physical address %lx, " + "irq %d\n", unit, dev->base_addr, dev->irq); + err = register_netdev(dev); + if (err) { + printk(KERN_WARNING "TSMAC%d: unable to register network " + "device\n", unit); + goto out_unmap; + } + + tsmac_create_proc_entries(dev); + + /* initialize spinlocks */ + spin_lock_init(&lp->control_lock); + spin_lock_init(&lp->restart_lock); + spin_lock_init(&lp->tx_lock); + spin_lock_init(&lp->stats_lock); + + atomic_set(&lp->restart_pending_cnt, 0); + + return 0; + + out_unmap: + iounmap(mapaddr); + out_memgpmii: + release_mem_region(dev->base_addr + MSP_GPMII_START, + sizeof(struct msp_gpmii_regs)); + out_memmac: + release_mem_region(dev->base_addr + MSP_MAC_START, + sizeof(struct msp_mac_regs)); + out_memdma: + release_mem_region(dev->base_addr + MSP_DMA_START, + sizeof(struct msp_dma_regs)); + out_netdev: + free_netdev(dev); + out_err: + return err; +} + +/* + * This function removes allocated resources for the driver + */ +static int tsmac_remove(struct platform_device *pldev) +{ + struct net_device *dev = dev_get_drvdata(&pldev->dev); + struct tsmac_private *lp = netdev_priv(dev); + + tsmac_remove_proc_entries(); + mdiobus_unregister(&lp->bus); + unregister_netdev(dev); + + iounmap(lp->reg_map); + lp->reg_map = NULL; + release_mem_region(dev->base_addr + MSP_DMA_START, + sizeof(struct msp_dma_regs)); + release_mem_region(dev->base_addr + MSP_MAC_START, + sizeof(struct msp_mac_regs)); + release_mem_region(dev->base_addr + MSP_GPMII_START, + sizeof(struct msp_gpmii_regs)); + + free_netdev(dev); + return 0; +} + +/* platform device stuff for linux 2.6 */ +static char tsmac_string[] = MSP_TSMAC_ID; + +static struct platform_driver tsmac_driver = { + .probe = tsmac_probe, + .remove = __devexit_p(tsmac_remove), + .driver = { + .name = tsmac_string, + }, +}; + +/* + * Initializes the driver module. Register driver for the device + */ +static int __init tsmac_init_module(void) +{ + printk(KERN_NOTICE "PMC-Sierra TSMAC Gigabit Ethernet Driver\n"); + + if (platform_driver_register(&tsmac_driver)) { + printk(KERN_ERR "Driver registration failed\n"); + return -ENOMEM; + } + + return 0; +} + +/* + * Clean up the module. Unregisters the driver and platform devices from the\ + * kernel + */ +static void __exit tsmac_cleanup_module(void) +{ + platform_driver_unregister(&tsmac_driver); + +} + +MODULE_DESCRIPTION("PMC TSMAC 10/100/1000 Ethernet driver"); +MODULE_LICENSE("GPL"); + +module_init(tsmac_init_module); +module_exit(tsmac_cleanup_module); + +/* + * Read a TSMAC register + */ +u32 tsmac_read(void *addr) +{ + return __raw_readl(addr); +} + +/* + * Write to a TSMAC register + */ +void PMC_FAST tsmac_write(u32 val, void *addr) +{ + __raw_writel(val, addr); +} + +/* TODO : remove or modify for the proc file entry */ +#if 0 +int tsmac_get_phy(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct mii_ioctl_data *data = if_mii(req); + struct tsmac_phy tmp_phy; + uint16_t mii_reg = 0; + u32 mii_data = 0; + + /* need to lock just in case interface is about to be closed */ + spin_lock_bh(&lp->control_lock); + if (lp->phyptr == NULL) { + spin_unlock_bh(&lp->control_lock); + return -1; + } + + tmp_phy.dev_control_lock = lp->phyptr->dev_control_lock; + spin_unlock_bh(&lp->control_lock); + + tmp_phy.phyaddr = (data->phy_id & 0x1F); + tmp_phy.memaddr = &lp->reg_map->mac.md_data; + + mii_reg = (data->reg_num & 0x1F); + mii_data = tsmac_phy_read(&tmp_phy, mii_reg); + + data->val_out = (mii_data & 0xFFFF); + return 0; +} + +int tsmac_get_phy_id(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct mii_ioctl_data *data = if_mii(req); + + data->phy_id = lp->phy_addr; + return 0; +} + +int tsmac_set_phy(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct mii_ioctl_data *data = if_mii(req); + struct tsmac_phy tmp_phy; + uint16_t mii_reg = 0; + u32 mii_data = 0; + + /* need to lock just in case interface is about to be closed */ + spin_lock_bh(&lp->control_lock); + if (lp->phyptr == NULL) { + spin_unlock_bh(&lp->control_lock); + return -1; + } + + tmp_phy.dev_control_lock = lp->phyptr->dev_control_lock; + spin_unlock_bh(&lp->control_lock); + + tmp_phy.phyaddr = (data->phy_id & 0x1F); + tmp_phy.memaddr = &lp->reg_map->mac.md_data; + + mii_reg = (data->reg_num & 0x1F); + mii_data = (data->val_in & 0xFFFF); + tsmac_phy_write(&tmp_phy, mii_reg, mii_data); + + return 0; +} +#endif /* TODO : remove or modify */ + +int tsmac_copy_to_mem(void *dst, void *src, u32 n, u8 context) +{ + if (context == TSMAC_USER_DATA) + return copy_to_user(dst, src, n); + else { + memcpy(dst, src, n); + return 0; + } +} + +int tsmac_copy_from_mem(void *dst, void *src, u32 n, u8 context) +{ + if (context == TSMAC_USER_DATA) + return copy_from_user(dst, src, n); + else { + memcpy(dst, src, n); + return 0; + } +} + +/* + * Write data to the ARC entry in big endian order + */ +void tsmac_set_arc_entry(struct net_device *dev, int index, unsigned char *addr) +{ + int arc_index = index * 6; + unsigned long arc_data; + struct tsmac_private *lp = netdev_priv(dev); + +#ifdef TSMAC_DEBUG + { + int i; + printk(KERN_INFO "%s: arc %d:", cardname, index); + for (i = 0; i < 6; i++) + printk(KERN_INFO " %02x", addr[i]); + printk(KERN_INFO "\n"); + } +#endif + + /* starting location is an ODD address */ + if (index & 1) { + /* read-modify-write first 2-bytes of data */ + tsmac_write(arc_index - 2, &lp->reg_map->mac.arc_addr); + arc_data = tsmac_read(&lp->reg_map->mac.arc_data) & 0xffff0000; + arc_data |= (addr[0] << 8) | addr[1]; + tsmac_write(arc_data, &lp->reg_map->mac.arc_data); + + /* write last 4-bytes of data */ + tsmac_write(arc_index + 2, &lp->reg_map->mac.arc_addr); + arc_data = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | + addr[5]; + tsmac_write(arc_data, &lp->reg_map->mac.arc_data); + + /* starting location is an EVEN address */ + } else { + /* write first 4-bytes of data */ + tsmac_write(arc_index, &lp->reg_map->mac.arc_addr); + arc_data = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | + addr[3]; + tsmac_write(arc_data, &lp->reg_map->mac.arc_data); + + /* read-modify-write last 2-bytes of data */ + tsmac_write(arc_index + 4, &lp->reg_map->mac.arc_addr); + arc_data = tsmac_read(&lp->reg_map->mac.arc_data) & 0x0000ffff; + arc_data |= (addr[4] << 24) | (addr[5] << 16); + tsmac_write(arc_data, &lp->reg_map->mac.arc_data); + } + +#if defined(TSMAC_DEBUG) + { + int i; + for (i = arc_index / 4; i < arc_index / 4 + 2; i++) { + tsmac_write(i * 4, &lp->reg_map->mac.arc_addr); + printk(KERN_INFO "arc 0x%x: %08x\n", + i * 4, tsmac_read(&lp->reg_map->mac.arc_data)); + } + } +#endif +} + +int tsmac_set_mac_addr(struct net_device *dev, void *addr) +{ + struct sockaddr *saddr = addr; + struct tsmac_private *lp = netdev_priv(dev); + + /* conntype can ONLY be changed if the interface is NOT running */ + if (netif_running(dev)) { + printk(KERN_WARNING "TSMAC %s: cannot change MAC address " + "while the interface is running\n", dev->name); + return -1; + } + + memcpy(dev->dev_addr, saddr->sa_data, dev->addr_len); + tsmac_set_arc_entry(dev, ARC_ENTRY_MAC, dev->dev_addr); + + /* also copy MAC address to source address of PAUSE generation */ + memcpy(lp->flow_ctl.src_addr, dev->dev_addr, dev->addr_len); + tsmac_set_arc_entry(dev, ARC_ENTRY_PAUSE_SRC, lp->flow_ctl.src_addr); + + return 0; +} + +int tsmac_set_conntype(struct net_device *dev, enum msp_conntype_enum conn_type) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* conntype can ONLY be changed if the interface is NOT running */ + if (netif_running(dev)) { + printk(KERN_WARNING "TSMAC %s: cannot change connection type " + "while the interface is running\n", dev->name); + return -1; + } + + lp->conn_type = conn_type; + + switch (conn_type) { + case (MSP_CT_MOCA): + case (MSP_CT_ETHPHY): + if (lp->unit == TSMAC_C) + tsmac_enable_mac_c(dev); + + tsmac_set_mii_type(dev, TSMAC_MT_GMII); + break; + case (MSP_CT_GPON): + case (MSP_CT_ETHSWITCH): + if (lp->unit == TSMAC_C) + tsmac_enable_mac_c(dev); + + lp->speed = SPEED_1000; + lp->duplex = DUPLEX_FULL; + lp->link = 1; + tsmac_set_mii_type(dev, TSMAC_MT_GMII); + break; + default: + tsmac_set_mii_type(dev, TSMAC_MT_GMII); + break; + } + + return 0; +} + +int tsmac_set_phyaddr(struct net_device *dev, int bus_unit, int phy_addr) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* PHY address can ONLY be changed if the interface is NOT running */ + if (netif_running(dev)) { + printk(KERN_WARNING "TSMAC %s: cannot change PHY address " + "while the interface is running\n", dev->name); + return -1; + } + + lp->bus_unit = bus_unit; + lp->phy_addr = phy_addr; + + return 0; +} + +int tsmac_set_mii_type(struct net_device *dev, + enum tsmac_mii_type_enum mii_type) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* conntype can ONLY be changed if the interface is NOT running */ + if (netif_running(dev)) { + printk(KERN_WARNING + "TSMAC %s: cannot change connection type " + "while the interface is running\n", dev->name); + return -1; + } + + lp->mii_type = mii_type; + + return 0; +} + +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE +/* + * Get the line loopback status + */ +int tsmac_get_loopback(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + u8 loopback; + + /* get the loopback status from the device object */ + loopback = lp->loopback_enable; + + if (tsmac_copy_to_mem(iodata->data, &loopback, + sizeof(loopback), context)) { + printk(KERN_WARNING "TSMAC %s: copy to user failed\n", + dev->name); + return -EFAULT; + } + return 0; +} + +int tsmac_set_loopback(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + u8 loopback; + + if (tsmac_copy_from_mem(&loopback, iodata->data, sizeof(u8), context)) { + printk(KERN_WARNING "TSMAC %s: copy from user failed\n", + dev->name); + return -EFAULT; + } + + /* write the loopback enable/disable status in device object */ + lp->loopback_enable = loopback; + return 0; +} +#endif + +/* + * Set full duplex flow control parameters of PAUSE operations + */ +void tsmac_set_pause_param(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_flow_ctl *flow_ctl = &lp->flow_ctl; + u8 tmp[6]; + u32 saved_addr, reg; + + /* program ARC entry table with the destination address */ + tsmac_set_arc_entry(dev, ARC_ENTRY_PAUSE_DST, flow_ctl->dest_addr); + + /* + * Program ARC entry table with the source address + * + * Note: Always link the source adress with the deivce MAC address + */ + memcpy(flow_ctl->src_addr, dev->dev_addr, sizeof(u8) * 6); + tsmac_set_arc_entry(dev, ARC_ENTRY_PAUSE_SRC, flow_ctl->src_addr); + + /* + * Program ARC entry table with MAC control type, PAUSE operation + * opcode, and PAUSE duration + */ + /* set MAC control type */ + tmp[0] = 0x88; + tmp[1] = 0x08; + + /* set PAUSE opearation opcode */ + tmp[2] = 0x00; + tmp[3] = 0x01; + + /* set PAUSE duration */ + tmp[4] = (flow_ctl->duration & 0xFF00) >> 8; + tmp[5] = flow_ctl->duration & 0x00FF; + + tsmac_set_arc_entry(dev, ARC_ENTRY_PAUSE_CTL, tmp); + + /* enable above ARC entries */ + reg = tsmac_read(&lp->reg_map->mac.arc_ena) | + ARC_Ena_Bit(ARC_ENTRY_PAUSE_DST) | + ARC_Ena_Bit(ARC_ENTRY_PAUSE_SRC) | ARC_Ena_Bit(ARC_ENTRY_PAUSE_CTL); + tsmac_write(reg, &lp->reg_map->mac.arc_ena); + + /* program the two bytes after ARC location #20 with 0x0000 */ + saved_addr = tsmac_read(&lp->reg_map->mac.arc_addr); + tsmac_write(0x7C, &lp->reg_map->mac.arc_addr); + reg = tsmac_read(&lp->reg_map->mac.arc_data) & 0xFFFF0000; + tsmac_write(reg, &lp->reg_map->mac.arc_data); + + /* program the locations MC#1 and MC#2 with 0x0000_0000 */ + tsmac_write(0x80, &lp->reg_map->mac.arc_addr); + tsmac_write(0x00, &lp->reg_map->mac.arc_data); + tsmac_write(0x84, &lp->reg_map->mac.arc_addr); + tsmac_write(0x00, &lp->reg_map->mac.arc_data); + tsmac_write(saved_addr, &lp->reg_map->mac.arc_addr); + + /* set XON/XOFF */ + tsmac_write(flow_ctl->xoff, &lp->reg_map->mac.xoff_thresh); + tsmac_write(flow_ctl->xon, &lp->reg_map->mac.xon_thresh); + + /* set compare value */ + tsmac_write(flow_ctl->compare, &lp->reg_map->mac.rmt_pause_cmp); + + /* enable/disable PAUSE generation */ + reg = tsmac_read(&lp->reg_map->mac.tx_ctl) | flow_ctl->enable; + tsmac_write(TX_CFG(TX_CTL_DIS, flow_ctl->enable), + &lp->reg_map->mac.tx_ctl); +} + +/* + * Print Pause/ARC memory map + */ +int tsmac_print_map_pause_arc(struct tsmac_private *lp, char *buffer) +{ + u32 arc_off, arc_data; + int len = 0; + + for (arc_off = 0x00; arc_off <= 0x84; arc_off += 0x4) { + tsmac_write(arc_off, &lp->reg_map->mac.arc_addr); + arc_data = tsmac_read(&lp->reg_map->mac.arc_data); + len += sprintf(buffer + len, "0x%x\t\t\t\t0x%08x\n", + arc_off, arc_data); + } + + return len; +} + +/* + * Print classifier memory map + */ +int tsmac_print_map_classifier(struct tsmac_private *lp, char *buffer) +{ + u32 arc_off, arc_data; + u32 classifier_pos_off = 0x88; + int len = 0; + int msg_index = 0; + /* to select the messages from the messages array */ + char *messages[] = { "L2 DA Rule 0", + "L2 DA Rule 1", + "L2 DA Rule 2", + "L2 DA Rule 3", + "L2 SA Rule 0", + "L2 SA Rule 1", + "L2 SA Rule 2", + "L2 SA Rule 3", + "L2 VQ Mapping", + "ETYPE Classification", + "VLAN VQ Mapping", + "IPv4 VQ Mapping Table", + "IPv6 VQ Mapping Table" + }; + + /* + * Read all the four L2 rule configuration from classifier RAM; starts + * at 0x88. add 0x04 to get the next entry + */ + for (arc_off = 0x88; arc_off <= 0xE4; arc_off += 0x4) { + tsmac_write(arc_off, &lp->reg_map->mac.arc_addr); + arc_data = tsmac_read(&lp->reg_map->mac.arc_data); + printk(KERN_INFO "arc_data = %x\n", arc_data); + + if (arc_off == classifier_pos_off) { + /* add the message string in the output */ + len += sprintf(buffer + len, "0x%x\t%s\t\t0x%08x\n", + arc_off, messages[msg_index++], + arc_data); + /* rules started at 0x88, 0x94, 0xA0 ... */ + classifier_pos_off += 0xC; + } else + len += sprintf(buffer + len, "0x%x\t\t\t\t0x%08x\n", + arc_off, arc_data); + } + + /* + * Read L2 VQ, ETYPE Classification, and VLAN VQ mapping from the + * classifier RAM + */ + for (; arc_off <= 0xF0; arc_off += 0x4) { + tsmac_write(arc_off, &lp->reg_map->mac.arc_addr); + arc_data = tsmac_read(&lp->reg_map->mac.arc_data); + len += sprintf(buffer + len, "0x%x\t%-24s0x%08x\n", arc_off, + messages[msg_index++], arc_data); + } + + /* read ipv4, ipv6 VQ mapping table from the classifier RAM */ + classifier_pos_off = arc_off; + for (; arc_off <= 0x130; arc_off += 0x4) { + tsmac_write(arc_off, &lp->reg_map->mac.arc_addr); + arc_data = tsmac_read(&lp->reg_map->mac.arc_data); + + if (arc_off == classifier_pos_off) { + /* add the message string in the output */ + len += sprintf(buffer + len, "0x%x\t%s\t0x%08x\n", + arc_off, messages[msg_index++], + arc_data); + classifier_pos_off += 0x20; + } else { + len += sprintf(buffer + len, "0x%x\t\t\t\t0x%08x\n", + arc_off, arc_data); + } + } + return len; +} + +int tsmac_get_default_vq_map(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + u32 reg; + u8 default_vq; + + /* by default, read from device */ + reg = tsmac_read(&lp->reg_map->mac.vq_conf); + reg &= VQ_Def_Map_Mask; + reg >>= VQ_Def_Map_Shift; + default_vq = reg; + +#if defined(TSMAC_FLOOD_WORKAROUND) + /* flood control is disabled */ + if (lp->vqnflood.flood_enable == 0) { + /* read from software copy */ + default_vq = lp->vqnflood.default_vq; + } +#endif /* TSMAC_FLOOD_WORKAROUND */ + + if (tsmac_copy_to_mem(iodata->data, &default_vq, sizeof(default_vq), + context)) { + printk(KERN_WARNING "(tsmac_get_default_vq_map) copy touser " + "failed\n"); + return -EFAULT; + } + return 0; +} + +int tsmac_set_default_vq_map(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + u8 default_vq; + u32 reg; + + if (!capable(CAP_NET_ADMIN)) { + printk(KERN_ERR "(%s) Operation not permitted\n", __func__); + return -EPERM; + } + + if (tsmac_copy_from_mem(&default_vq, iodata->data, sizeof(default_vq), + context)) { + printk(KERN_ERR "(%s) copy from user failed\n", __func__); + return -EFAULT; + } + + /* update software copy */ + lp->vqnflood.default_vq = default_vq; + +#if defined(TSMAC_FLOOD_WORKAROUND) + /* if flood control is disabled */ + if (lp->vqnflood.flood_enable == 0) + return 0; +#endif /* TSMAC_FLOOD_WORKAROUND */ + + /* update device */ + reg = (default_vq << VQ_Def_Map_Shift); + tsmac_write(reg, &lp->reg_map->mac.vq_conf); + + return 0; +} + +/* + * Read L2 class rule from the memory map + */ +void tsmac_get_l2_class_entry(struct net_device *dev, u32 entry_addr, + unsigned char *data) +{ + struct tsmac_private *lp = netdev_priv(dev); + unsigned long reg; + int i; + + if (entry_addr & 2) { + /* read modify write */ + tsmac_write(entry_addr, &lp->reg_map->mac.arc_addr); + reg = tsmac_read(&lp->reg_map->mac.arc_data); + data[0] = (reg >> 8) & 0xFF; + data[1] = reg & 0xFF; + + /* read whole word */ + tsmac_write(entry_addr + 2, &lp->reg_map->mac.arc_addr); + reg = tsmac_read(&lp->reg_map->mac.arc_data); + for (i = 0; i < 4; i++) + data[i + 2] = (reg >> (24 - i * 8)) & 0xFF; + } else { + /* read whole word */ + tsmac_write(entry_addr, &lp->reg_map->mac.arc_addr); + reg = tsmac_read(&lp->reg_map->mac.arc_data); + for (i = 0; i < 4; i++) + data[i] = (reg >> (24 - i * 8)) & 0xFF; + + /* read modify write */ + tsmac_write(entry_addr + 4, &lp->reg_map->mac.arc_addr); + reg = tsmac_read(&lp->reg_map->mac.arc_data); + data[4] = (reg >> 24) & 0xFF; + data[5] = (reg >> 16) & 0xFF; + } +} + +/* + * Write L2 class rule to the memory map in big endian order + */ +void tsmac_set_l2_class_entry(struct net_device *dev, u32 entry_addr, + unsigned char *data) +{ + unsigned long reg; + struct tsmac_private *lp = netdev_priv(dev); + + if (entry_addr & 2) { + /* read modify write */ + tsmac_write(entry_addr, &lp->reg_map->mac.arc_addr); + reg = tsmac_read(&lp->reg_map->mac.arc_data) & 0xffff0000; + reg |= data[0] << 8 | data[1]; + tsmac_write(reg, &lp->reg_map->mac.arc_data); + + /* write whole word */ + tsmac_write(entry_addr + 2, &lp->reg_map->mac.arc_addr); + reg = (data[2] << 24) | (data[3] << 16) | (data[4] << 8) | + data[5]; + tsmac_write(reg, &lp->reg_map->mac.arc_data); + } else { + /* write whole word */ + tsmac_write(entry_addr, &lp->reg_map->mac.arc_addr); + reg = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | + data[3]; + tsmac_write(reg, &lp->reg_map->mac.arc_data); + + /* read modify write */ + tsmac_write(entry_addr + 4, &lp->reg_map->mac.arc_addr); + reg = tsmac_read(&lp->reg_map->mac.arc_data) & 0x0000ffff; + reg |= data[4] << 24 | (data[5] << 16); + tsmac_write(reg, &lp->reg_map->mac.arc_data); + } +} + +/* + * Get L2 classification rules + */ +int tsmac_get_addr_class_rule(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_l2_class_rule l2_rule_array[4]; + u32 reg; + int i; + + /* by default, read from device */ + for (i = 0; i < 4; i++) { + l2_rule_array[i].rule_num = i; + + /* read Dest Addr/Mask and Src Addr/Mask */ + tsmac_get_l2_class_entry(dev, (L2_DA_Rule0_offset + + L2_Rule_Index * i), + l2_rule_array[i].DA); + tsmac_get_l2_class_entry(dev, + (L2_DA_Rule0_offset + + L2_Rule_Index * i + 6), + l2_rule_array[i].DM); + tsmac_get_l2_class_entry(dev, + (L2_SA_Rule0_offset + + L2_Rule_Index * i), + l2_rule_array[i].SA); + tsmac_get_l2_class_entry(dev, + (L2_SA_Rule0_offset + + L2_Rule_Index * i + 6), + l2_rule_array[i].SM); + + /* read L2 VQ mapping */ + tsmac_write(L2_VQ_Map_Offset, &lp->reg_map->mac.arc_addr); + + reg = tsmac_read(&lp->reg_map->mac.arc_data); + l2_rule_array[i].vqnum = (reg >> (28 - 4 * i)) & 0x0F; + + /* read enable bit */ + reg = tsmac_read(&lp->reg_map->mac.l2_rule_ena); + l2_rule_array[i].enable = (reg >> i) & 0x1; + } + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + /* if flood control is disabled */ + /* read from software copy */ + memcpy(l2_rule_array, lp->vqnflood.l2_rule, + 4 * sizeof(struct tsmac_l2_class_rule)); +#endif /* TSMAC_FLOOD_WORKAROUND */ + + if (tsmac_copy_to_mem(iodata->data, l2_rule_array, + 4 * sizeof(struct tsmac_l2_class_rule), + context)) { + printk(KERN_WARNING "(tsmac_get_addr_class_rule) copy to user " + "failed\n"); + return -EFAULT; + } + return 0; +} + +/* + * Set L2 classification rules + */ +int tsmac_set_addr_class_rule(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_l2_class_rule l2_rule; + u8 rnum; + u32 reg; + + if (!capable(CAP_NET_ADMIN)) { + printk(KERN_ERR "(%s) Operation not permitted\n", __func__); + return -EPERM; + } + + if (tsmac_copy_from_mem(&l2_rule, iodata->data, + sizeof(struct tsmac_l2_class_rule), context)) { + printk(KERN_ERR "(%s) copy from user failed\n", __func__); + return -EFAULT; + } + + rnum = l2_rule.rule_num; + + if (!l2_rule.change_state_only) { + memcpy((void *)&lp->vqnflood.l2_rule[rnum], (void *)&l2_rule, + sizeof(struct tsmac_l2_class_rule)); + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + return 0; +#endif /* TSMAC_FLOOD_WORKAROUND */ + + /* update device with Dest Addr/Mask and Src Addr/Mask */ + tsmac_set_l2_class_entry(dev, (L2_DA_Rule0_offset + + L2_Rule_Index * rnum), + l2_rule.DA); + tsmac_set_l2_class_entry(dev, + (L2_DA_Rule0_offset + + L2_Rule_Index * rnum + 6), + l2_rule.DM); + tsmac_set_l2_class_entry(dev, + (L2_SA_Rule0_offset + + L2_Rule_Index * rnum), l2_rule.SA); + tsmac_set_l2_class_entry(dev, + (L2_SA_Rule0_offset + + L2_Rule_Index * rnum + 6), + l2_rule.SM); + + tsmac_write(L2_VQ_Map_Offset, &lp->reg_map->mac.arc_addr); + + /* update VQ number, in order vq0 vq1 vq2 vq3, size of nibble + */ + reg = tsmac_read(&lp->reg_map->mac.arc_data) & ~(L2_VQ_Map_Mask + >> (rnum * 4)); + reg |= l2_rule.vqnum << (28 - rnum * 4); + tsmac_write(reg, &lp->reg_map->mac.arc_data); + + /* update enable bit */ + reg = (tsmac_read(&lp->reg_map->mac.l2_rule_ena) & + ~(0x1 << rnum)); + reg |= l2_rule.enable << rnum; + tsmac_write(reg, &lp->reg_map->mac.l2_rule_ena); + } else { + lp->vqnflood.l2_rule[rnum].enable = l2_rule.enable; + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + return 0; +#endif /* TSMAC_FLOOD_WORKAROUND */ + + /* update device */ + reg = (tsmac_read(&lp->reg_map->mac.l2_rule_ena) & + ~(0x1 << rnum)); + reg |= l2_rule.enable << rnum; + tsmac_write(reg, &lp->reg_map->mac.l2_rule_ena); + } + return 0; +} + +int tsmac_get_vlan_class_rule(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_vlanvq_config vlan_vq; + u32 reg; + int i; + + /* by default, read from device */ + /* read VLAN TCI offset */ + reg = tsmac_read(&lp->reg_map->mac.vlan_tci_offset); + reg &= VLAN_TCI_Offset_Mask; + vlan_vq.tci_offset = reg; + + /* read VLAN VQ map */ + tsmac_write(VLAN_VQ_Map_Offset, &lp->reg_map->mac.arc_addr); + + reg = tsmac_read(&lp->reg_map->mac.arc_data); + for (i = 0; i < 4; i++) { + vlan_vq.vlanvq[i] = (reg >> (28 - (i * 8))) & 0xF; + vlan_vq.vlanvq[i] |= ((reg >> (28 - (i * 8) - 4)) & 0xF) << 4; + } + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + /* read from software copy */ + memcpy((void *)&vlan_vq, (void *)&lp->vqnflood.vlanvq_config, + sizeof(struct tsmac_vlanvq_config)); +#endif /* TSMAC_FLOOD_WORKAROUND */ + + if (tsmac_copy_to_mem(iodata->data, &vlan_vq, + sizeof(struct tsmac_vlanvq_config), context)) { + printk(KERN_WARNING "(tsmac_get_vlan_class_rule) copy to user" + " failed\n"); + return -EFAULT; + } + return 0; +} + +int tsmac_set_vlan_class_rule(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_vlanvq_config vlan_vq; + u32 reg; + u32 saved_addr; + + if (!capable(CAP_NET_ADMIN)) { + printk(KERN_ERR "(%s) Operation not permitted\n", __func__); + return -EPERM; + } + + if (tsmac_copy_from_mem(&vlan_vq, iodata->data, + sizeof(struct tsmac_vlanvq_config), context)) { + printk(KERN_ERR "(%s) copy from user failed\n", __func__); + return -EFAULT; + } + + /* update software copy */ + memcpy((void *)&lp->vqnflood.vlanvq_config, (void *)&vlan_vq, + sizeof(struct tsmac_vlanvq_config)); + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + return 0; +#endif /* TSMAC_FLOOD_WORKAROUND */ + + /* update device */ + tsmac_write(vlan_vq.tci_offset, &lp->reg_map->mac.vlan_tci_offset); + saved_addr = tsmac_read(&lp->reg_map->mac.arc_addr); + tsmac_write(VLAN_VQ_Map_Offset, &lp->reg_map->mac.arc_addr); + reg = ((vlan_vq.vlanvq[0] & 0x0F) << 28) | + ((vlan_vq.vlanvq[0] & 0xF0) << 20) | + ((vlan_vq.vlanvq[1] & 0x0F) << 20) | + ((vlan_vq.vlanvq[1] & 0xF0) << 12) | + ((vlan_vq.vlanvq[2] & 0x0F) << 12) | + ((vlan_vq.vlanvq[2] & 0xF0) << 4) | + ((vlan_vq.vlanvq[3] & 0x0F) << 4) | + ((vlan_vq.vlanvq[3] & 0xF0) >> 4); + tsmac_write(reg, &lp->reg_map->mac.arc_data); + tsmac_write(saved_addr, &lp->reg_map->mac.arc_addr); + return 0; +} + +/* + * Get IPv4/IPv6 VQ values. 6-bit DSCP field implies 64 different mappings to + * virtual queue. These 64 fields are divided into 8 DSCP ranges, (0-7, + * 8-15,... 56-63). All VQs are retreived together + */ +int tsmac_get_ip_class_rule(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_ipvq_config ip_rule_array[8]; + u32 addr, reg, saved_addr; + int i, j; + unsigned short ipv4_flag; + + /* + * Get the version of the rule (ipv4/ipv6) from the first strcut + * field + */ + if (tsmac_copy_from_mem(&ip_rule_array[0], iodata->data, + sizeof(struct tsmac_ipvq_config), context)) { + printk(KERN_WARNING "(tsmac_get_ip_class_rule) copy from user " + "failed\n"); + return -EFAULT; + } + + ipv4_flag = ip_rule_array[0].ipv4; + + /* by default, read from device */ + /* IPv4 or IPv6 */ + addr = (ipv4_flag) ? IPv4_VQ_Map_Offset : IPv6_VQ_Map_Offset; + saved_addr = tsmac_read(&lp->reg_map->mac.arc_addr); + + for (i = 0; i < 8; i++) { + tsmac_write(addr, &lp->reg_map->mac.arc_addr); + reg = tsmac_read(&lp->reg_map->mac.arc_data); + for (j = 0; j < 4; j++) { + ip_rule_array[i].ip_vq[j] = (reg >> (28 - (j * 8))) + & 0xF; + ip_rule_array[i].ip_vq[j] |= ((reg >> (28 - (j * 8) + - + 4)) & 0xF) << 4; + } + addr += 4; + } + + tsmac_write(saved_addr, &lp->reg_map->mac.arc_addr); + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + /* read from software copy */ + memcpy((void *)ip_rule_array, + (void *)((ipv4_flag) ? (&lp->vqnflood.ipv4_vq) : + (&lp->vqnflood.ipv6_vq)), + 8 * sizeof(struct tsmac_ipvq_config)); +#endif /* TSMAC_FLOOD_WORKAROUND */ + + if (tsmac_copy_to_mem(iodata->data, ip_rule_array, + 8 * sizeof(struct tsmac_ipvq_config), context)) { + printk(KERN_WARNING "(tsmac_get_ip_class_rule) copy to user " + "failed\n"); + return -EFAULT; + } + return 0; +} + +/* + * Set IPv4/IPv6 VQ values. 6-bit DSCP field implies 64 different mappings to + * virtual queue. Eight VQs of a purticular DSCP range, are defined at a time + */ +int tsmac_set_ip_class_rule(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_ipvq_config ip_rule; + u8 rnum; + u32 addr, val; + u32 saved_addr; + + if (!capable(CAP_NET_ADMIN)) { + printk(KERN_ERR "(%s) Operation not permitted\n", __func__); + return -EPERM; + } + + /* + * Get the version of the rule (ipv4/ipv6) from the first strcut + * field + */ + if (tsmac_copy_from_mem(&ip_rule, iodata->data, + sizeof(struct tsmac_ipvq_config), context)) { + printk(KERN_ERR "(%s) copy from user failed\n", __func__); + return -EFAULT; + } + + /* convert dsp range into rule number */ + rnum = ip_rule.dsp_range / 8; + + /* update software copy */ + memcpy((void *)((ip_rule.ipv4) ? + &(lp->vqnflood.ipv4_vq[rnum]) : + &(lp->vqnflood.ipv6_vq[rnum])), + (void *)&ip_rule, sizeof(struct tsmac_ipvq_config)); + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + return 0; +#endif /* TSMAC_FLOOD_WORKAROUND */ + + /* update device */ + addr = ((ip_rule.ipv4) ? IPv4_VQ_Map_Offset : IPv6_VQ_Map_Offset) + + IP_VQ_Map_Index * rnum; + saved_addr = tsmac_read(&lp->reg_map->mac.arc_addr); + tsmac_write(addr, &lp->reg_map->mac.arc_addr); + val = ((ip_rule.ip_vq[0] & 0x0F) << 28) | + ((ip_rule.ip_vq[0] & 0xF0) << 20) | + ((ip_rule.ip_vq[1] & 0x0F) << 20) | + ((ip_rule.ip_vq[1] & 0xF0) << 12) | + ((ip_rule.ip_vq[2] & 0x0F) << 12) | + ((ip_rule.ip_vq[2] & 0xF0) << 4) | + ((ip_rule.ip_vq[3] & 0x0F) << 4) | ((ip_rule.ip_vq[3] & 0xF0) >> 4); + tsmac_write(val, &lp->reg_map->mac.arc_data); + tsmac_write(saved_addr, &lp->reg_map->mac.arc_addr); + return 0; +} + +/* + * Get user-defined Ethernet type field, status (enabled/disabled) and VQ + * number + */ +int tsmac_get_ethtype_class_rule(struct net_device *dev, + struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_ethtype_config evq; + u32 reg; + u32 saved_addr; + + /* by default, read from device */ + saved_addr = tsmac_read(&lp->reg_map->mac.arc_addr); + tsmac_write(Ethtype_VQ_Offset, &lp->reg_map->mac.arc_addr); + + reg = tsmac_read(&lp->reg_map->mac.arc_data); + evq.ethtype_vq = (reg & 0xF0000000) >> 28; + evq.ethtype_enable = (reg & 0x08000000) >> 27; + evq.ethtype[1] = (reg & 0xFF); + evq.ethtype[0] = (reg & 0xFF00) >> 8; + + tsmac_write(saved_addr, &lp->reg_map->mac.arc_addr); + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + /* read from software copy */ + memcpy((void *)&evq, (void *)&lp->vqnflood.ethtype_config, + sizeof(struct tsmac_ethtype_config)); +#endif /* TSMAC_FLOOD_WORKAROUND */ + + if (tsmac_copy_to_mem(iodata->data, &evq, + sizeof(struct tsmac_ethtype_config), context)) { + printk(KERN_WARNING "(tsmac_get_ethtype_class_rule) copy to " + "user failed\n"); + return -EFAULT; + } + return 0; +} + +/* + * Set user-defined Ethernet type field, status (enabled/disabled) and VQ + * number + */ +int tsmac_set_ethtype_class_rule(struct net_device *dev, struct ifreq *req, + u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_ethtype_config evq; + u32 val; + u32 saved_addr; + + if (!capable(CAP_NET_ADMIN)) { + printk(KERN_ERR "(%s) Operation not permitted\n", __func__); + return -EPERM; + } + + if (tsmac_copy_from_mem(&evq, iodata->data, + sizeof(struct tsmac_ethtype_config), context)) { + printk(KERN_ERR "(%s) copy from user failed\n", __func__); + return -EFAULT; + } + + /* update device details */ + memcpy((void *)&lp->vqnflood.ethtype_config, (void *)&evq, + sizeof(struct tsmac_ethtype_config)); + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + return 0; +#endif /* TSMAC_FLOOD_WORKAROUND */ + + /* update device */ + saved_addr = tsmac_read(&lp->reg_map->mac.arc_addr); + tsmac_write(Ethtype_VQ_Offset, &lp->reg_map->mac.arc_addr); + val = (evq.ethtype_vq << 28) | (evq.ethtype_enable << 27) | + (evq.ethtype[0] << 8) | evq.ethtype[1]; + tsmac_write(val, &lp->reg_map->mac.arc_data); + tsmac_write(saved_addr, &lp->reg_map->mac.arc_addr); + return 0; +} + +int tsmac_get_vq_config(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_vq_config vq_config_array[VQ_MAX]; + u32 reg; + int i; + + /* by default, read from device */ + for (i = 0; i < VQ_MAX; i++) { + vq_config_array[i].vq_num = i; + + /* read size from software copy */ + vq_config_array[i].vq_token_count = + lp->vqnflood.vq_config[i].vq_token_count; + + /* read drop disable bit */ + reg = tsmac_read(&lp->reg_map->mac.vq_token_cnt[i]); + reg &= VQ_TC_Drop_Disable; + reg >>= VQ_TC_Drop_Disable_Shift; + vq_config_array[i].vq_drop_disable = reg; + } + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + memcpy((void *)vq_config_array, (void *)lp->vqnflood.vq_config, + VQ_MAX * sizeof(struct tsmac_vq_config)); +#endif /* TSMAC_FLOOD_WORKAROUND */ + + if (tsmac_copy_to_mem(iodata->data, vq_config_array, + VQ_MAX * sizeof(struct tsmac_vq_config), + context)) { + printk(KERN_WARNING "(tsmac_get_vq_config) copy to user " + "failed\n"); + return -EFAULT; + } + return 0; +} + +int tsmac_set_vq_config(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_vq_config vq_conf; + u8 vqnum; + u32 data; + + if (!capable(CAP_NET_ADMIN)) { + printk(KERN_ERR "(%s) Operation not permitted\n", __func__); + return -EPERM; + } + + if (tsmac_copy_from_mem(&vq_conf, iodata->data, + sizeof(struct tsmac_vq_config), context)) { + printk(KERN_ERR "(%s) copy from user failed\n", __func__); + return -EFAULT; + } + + vqnum = vq_conf.vq_num; + memcpy((void *)&lp->vqnflood.vq_config[vqnum], (void *)&vq_conf, + sizeof(struct tsmac_vq_config)); + +#if defined(TSMAC_FLOOD_WORKAROUND) + if (lp->vqnflood.flood_enable == 0) + return 0; +#endif /* TSMAC_FLOOD_WORKAROUND */ + + /* + * Load a new value to TOKEN_CNT by writing a new value to it with + * WR_OP bit set to 1 + */ + data = tsmac_read(&lp->reg_map->mac.vq_token_cnt[vqnum]) & + ~VQ_TC_Token_Cnt_Mask; + data |= vq_conf.vq_token_count & VQ_TC_Token_Cnt_Mask; + tsmac_write(data | VQ_TC_Wr_Op, &lp->reg_map->mac.vq_token_cnt[vqnum]); + + /* set drop disable state */ + data = (tsmac_read(&lp->reg_map->mac.vq_token_cnt[vqnum]) & + ~VQ_TC_Drop_Disable); + data &= ~VQ_TC_Token_Cnt_Mask; /* to avoid increment token count */ + data |= vq_conf.vq_drop_disable << VQ_TC_Drop_Disable_Shift; + tsmac_write(data, &lp->reg_map->mac.vq_token_cnt[vqnum]); + + return 0; +} + +int tsmac_get_drop_thresh(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_drop_threshold drop_level; + u32 reg; + + /* read from device */ + reg = tsmac_read(&lp->reg_map->mac.drop_on_thresh); + reg &= RX_DropOn_Mask; + drop_level.drop_on_thresh = reg; + + reg = tsmac_read(&lp->reg_map->mac.drop_off_thresh); + reg &= RX_DropOff_Mask; + drop_level.drop_off_thresh = reg; + + if (tsmac_copy_to_mem(iodata->data, &drop_level, + sizeof(struct tsmac_drop_threshold), context)) { + printk(KERN_WARNING "(tsmac_get_drop_thresh) copy to user " + "failed\n"); + return -EFAULT; + } + return 0; +} + +int tsmac_set_drop_thresh(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + struct tsmac_drop_threshold threshold; + + if (!capable(CAP_NET_ADMIN)) { + printk(KERN_ERR "(%s) Operation not permitted\n", __func__); + return -EPERM; + } + + if (tsmac_copy_from_mem(&threshold, iodata->data, + sizeof(struct tsmac_drop_threshold), context)) { + printk(KERN_ERR "(%s) copy from user failed\n", __func__); + return -EFAULT; + } + + /* update software copy */ + memcpy((void *)&lp->vqnflood.drop_threshold, (void *)&threshold, + sizeof(struct tsmac_drop_threshold)); + + /* update device */ + tsmac_write(threshold.drop_on_thresh, &lp->reg_map->mac.drop_on_thresh); + tsmac_write(threshold.drop_off_thresh, + &lp->reg_map->mac.drop_off_thresh); + return 0; +} + +/* + * Apply the stored Flood control and Full Duplex Flow Control parameters from + * the instance of the device data into the device registers + */ +int tsmac_set_vqnpause(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data iodata; + struct ifreq ifr; + u8 i; + int err = 0; + + ifr.ifr_data = &iodata; + + /* default_vq */ + iodata.data = &lp->vqnflood.default_vq; + err = tsmac_set_default_vq_map(dev, &ifr, TSMAC_KERNEL_DATA); + if (err) + goto seterr; + + /* L2 Rules */ + for (i = 0; i < 4; i++) { + lp->vqnflood.l2_rule[i].change_state_only = 0; + iodata.data = &lp->vqnflood.l2_rule[i]; + err = tsmac_set_addr_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + if (err) + goto seterr; + } + + /* VLAN priorities and tci_offset */ + iodata.data = &lp->vqnflood.vlanvq_config; + err = tsmac_set_vlan_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + if (err) + goto seterr; + + /* 8 set IPv4/IPv6 VQs values */ + for (i = 0; i < 8; i++) { + iodata.data = &lp->vqnflood.ipv4_vq[i]; + err = tsmac_set_ip_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + if (err) + goto seterr; + + iodata.data = &lp->vqnflood.ipv6_vq[i]; + err = tsmac_set_ip_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + if (err) + goto seterr; + } + + /* Ethertype */ + iodata.data = &lp->vqnflood.ethtype_config; + err = tsmac_set_ethtype_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + if (err) + goto seterr; + + /* drop threshold values */ + iodata.data = &lp->vqnflood.drop_threshold; + err = tsmac_set_drop_thresh(dev, &ifr, TSMAC_KERNEL_DATA); + if (err) + goto seterr; + + /* Virtual Queue Configuration */ + for (i = 0; i < 8; i++) { + iodata.data = &lp->vqnflood.vq_config[i]; + err = tsmac_set_vq_config(dev, &ifr, TSMAC_KERNEL_DATA); + if (err) + goto seterr; + } + + /* flood control */ + err = set_floodctl_reg(dev); + if (err) + goto seterr; + + return 0; + seterr: + return err; +} + +int tsmac_get_egress_prio(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + + if (tsmac_copy_to_mem(iodata->data, lp->egress_prio, + sizeof(lp->egress_prio), context)) { + printk(KERN_WARNING "(%s) copy to user failed\n", __func__); + return -EFAULT; + } + return 0; +} + +int tsmac_set_egress_prio(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *iodata = req->ifr_data; + enum tsmac_egress_prio egress_prio_new[2]; + + if (!capable(CAP_NET_ADMIN)) { + printk(KERN_ERR "(%s) Operation not permitted\n", __func__); + return -EPERM; + } + + if (tsmac_copy_from_mem(egress_prio_new, iodata->data, + sizeof(egress_prio_new), context)) { + printk(KERN_ERR "(%s) copy from user failed\n", __func__); + return -EFAULT; + } + + /* + * The first number in the array is the skb->priority, and the second + * number is the egress queue number. + */ + lp->egress_prio[egress_prio_new[0]] = egress_prio_new[1]; + + return 0; +} + +/* + * Configure the device with default values of flood control and flow control + */ +void tsmac_config_def_vqnpause(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_flow_ctl *flow_ctl = &lp->flow_ctl; + struct tsmac_vqnflood_configure *vqnflood = &lp->vqnflood; + u8 i, j; + + /* wipe out previous config */ + memset(flow_ctl, 0, sizeof(lp->flow_ctl)); + memset(vqnflood, 0, sizeof(lp->vqnflood)); + + /* disable pause generation by default */ + flow_ctl->enable = 0; + + /* copy device MAC addr to PAUSE generation source addr */ + memcpy(flow_ctl->src_addr, dev->dev_addr, sizeof(u8) * 6); + + /* flood control, enable by default */ + vqnflood->flood_enable = 1; + + /* default VQ */ + vqnflood->default_vq = VQ_DEFAULT; + + /* L2Rule[n].vqnum = 7, where n=0 to 3 */ + for (i = 0; i < 4; i++) { + vqnflood->l2_rule[i].vqnum = VQ_DEFAULT; + vqnflood->l2_rule[i].rule_num = i; + } + + /* VLAN priorities 0-7 map to default VQ, and tci_offset = 14 */ + for (i = 0; i < 4; i++) { + vqnflood->vlanvq_config.vlanvq[i] = VQ_DEFAULT; + vqnflood->vlanvq_config.vlanvq[i] |= VQ_DEFAULT << 4; + } + vqnflood->vlanvq_config.tci_offset = 14; + + /* + * IPv4/IPv6 VQs are 8 * 8 sets, each set will have identical values in + * the increasing order 0-7 + */ + for (i = 0; i < 8; i++) { + vqnflood->ipv4_vq[i].dsp_range = i * 8; + vqnflood->ipv4_vq[i].ipv4 = 1; + vqnflood->ipv6_vq[i].dsp_range = i * 8; + vqnflood->ipv6_vq[i].ipv4 = 0; + + for (j = 0; j < 4; j++) { + vqnflood->ipv4_vq[i].ip_vq[j] = VQ_DEFAULT; + vqnflood->ipv4_vq[i].ip_vq[j] |= VQ_DEFAULT << 4; + vqnflood->ipv6_vq[i].ip_vq[j] = VQ_DEFAULT; + vqnflood->ipv6_vq[i].ip_vq[j] |= VQ_DEFAULT << 4; + } + } + + /* Default ETYPE classification to all-zero initially */ + + /* default drop threshold */ + vqnflood->drop_threshold.drop_on_thresh = 6144; + vqnflood->drop_threshold.drop_off_thresh = 2048; + + /* VQ configuration */ + for (i = 0; i < 8; i++) + vqnflood->vq_config[i].vq_num = i; + + /* + * Due to the VQ token count HW bug, only 2 VQ can be used, with VQ + * 0 set to low-priority and VQ 1 set to high-priority and disable + * drop + */ + + /* Note: assuming default VQ is VQ 0 */ + vqnflood->vq_config[VQ_DEFAULT].vq_drop_disable = 0; + vqnflood->vq_config[VQ_DEFAULT].vq_token_count = 64; + + vqnflood->vq_config[1].vq_drop_disable = 1; +} + +/* + * Collect HW stats data from the TSMAC status registers and updates stats + * structure of the device object. + * + * Note: + * + * This function needs to be called periodically (worst case is data running + * at Gigabit line rate a 32-bit HW bytes counter can overflow in ~34 + * seconds) to prevent counter overflow + */ +void tsmac_update_hw_stats(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + int n = 0; + + /* + * Since this routine may be called by various sources, we need to + * lock it + */ + spin_lock_bh(&lp->control_lock); + spin_lock(&lp->stats_lock); + + /* + * Set SNAP bit to take the snapshot of statistics maintained by + * the MAC. The MAC clears this bit to 0 upon transferring + * contents of the hardware statistics counters to the software + * readable statistics registers. After that counters reset to 0. + */ + tsmac_write(tsmac_read(&lp->reg_map->mac.mac_ctl) | MAC_Snap, + &lp->reg_map->mac.mac_ctl); + + /* flush to make sure SNAP bit write go into the TSMAC subsystem */ + tsmac_cpu_to_tsmac_flush(lp->unit); + + /* + * The time the HW takes to clear the SNAP bit depends on the link + * speed: 31.25 ns, 140 ns, 860 ns for 1000 Mbps, 100 Mbps, 10 Mbps, + * respectively. The following while loop times out in 3 us, which is + * good for all link speeds + */ + while ((tsmac_read(&lp->reg_map->mac.mac_ctl) & MAC_Snap) && n <= 10) + ++n; + + if (n > 10) { + /* 081205: Do not print warnings when connType is GPON, since + * the link will not be established until GPON device is + * initialized. */ + if (lp->conn_type != MSP_CT_GPON) + printk(KERN_DEBUG + "TSMAC%d: Unable to update stats counters\n", + lp->unit); + goto update_hw_stats_done; + } + + lp->hw_stats.tx_packets += + tsmac_read(&lp->reg_map->mac.tx_good_frame_stat); + + lp->hw_stats.tx_bytes += + tsmac_read(&lp->reg_map->mac.tx_good_byte_stat); + + lp->hw_stats.rx_packets += + tsmac_read(&lp->reg_map->mac.rx_total_frame_stat); + + lp->hw_stats.rx_bytes += + tsmac_read(&lp->reg_map->mac.rx_total_byte_stat); + + lp->lx_stats.rx_over_errors += + tsmac_read(&lp->reg_map->mac.rx_over_frame_stat); + + lp->hw_stats.rx_dropped_bytes += + tsmac_read(&lp->reg_map->mac.rx_dropped_byte_stat); + + lp->hw_stats.rx_vq_drops[0] += + tsmac_read(&lp->reg_map->mac.vq_dropped_stat[0]); + lp->hw_stats.rx_vq_drops[1] += + tsmac_read(&lp->reg_map->mac.vq_dropped_stat[1]); + lp->hw_stats.rx_vq_drops[2] += + tsmac_read(&lp->reg_map->mac.vq_dropped_stat[2]); + lp->hw_stats.rx_vq_drops[3] += + tsmac_read(&lp->reg_map->mac.vq_dropped_stat[3]); + lp->hw_stats.rx_vq_drops[4] += + tsmac_read(&lp->reg_map->mac.vq_dropped_stat[4]); + lp->hw_stats.rx_vq_drops[5] += + tsmac_read(&lp->reg_map->mac.vq_dropped_stat[5]); + lp->hw_stats.rx_vq_drops[6] += + tsmac_read(&lp->reg_map->mac.vq_dropped_stat[6]); + lp->hw_stats.rx_vq_drops[7] += + tsmac_read(&lp->reg_map->mac.vq_dropped_stat[7]); + + update_hw_stats_done: + + spin_unlock(&lp->stats_lock); + spin_unlock_bh(&lp->control_lock); +} + +#ifdef CONFIG_PMC_MSP7150_GW_MOCA +int +tsmac_moca_reset(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + int reset = req->ifr_ifru.ifru_ivalue; + int gpio; + + switch (lp->unit) { + case 1: + gpio = TSMAC_RESET_GPIO_MACB; + break; + + case 2: + gpio = TSMAC_RESET_GPIO_MACC; + break; + + default: + printk(KERN_ERR "TSMAC%d: Invalid MoCA device\n" + , lp->unit); + return -EINVAL; + } + + if (reset) + gpio_direction_output(gpio, 0); + else + gpio_direction_output(gpio, 1); + + return 0; +} + +#else +int +tsmac_moca_reset(struct net_device *dev, struct ifreq *req, u8 context) +{ + struct tsmac_private *lp = netdev_priv(dev); + int reset = req->ifr_ifru.ifru_ivalue; + /* reset not supported on this platform */ + printk(KERN_WARNING "TSMAC%d: Faking MoCA reset to %d\n" + , lp->unit, reset); + return 0; +} +#endif + +void tsmac_enable_mac_c(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + u32 mac_c_out_reg; + + if (lp->unit != TSMAC_C) + return; + + mac_c_out_reg = tsmac_read((void *)TSMAC_MAC_C_OUTPUT_CTRL); + mac_c_out_reg |= MAC_C_EN1; + mac_c_out_reg &= ~MAC_C_EN2B; + mac_c_out_reg |= MAC_C_EN3; + mac_c_out_reg |= MAC_C_EN4; + + tsmac_write(mac_c_out_reg, (void *)TSMAC_MAC_C_OUTPUT_CTRL); +} + +/** + * tsmac_adjust_link() - callback function for PAL to adjust mac link status + * @dev: mac interface whose link status is to be adjusted + * + * Callback function for PAL layer to notify TSMAC regarding PHY's state + * change, so TSMAC can sync up its link state to PHY's. + */ +void tsmac_adjust_link(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct phy_device *phydev = lp->phyptr; + unsigned long flags; + int status_change = 0; + int mac_restart = 0; + + spin_lock_irqsave(&lp->control_lock, flags); + + if (phydev->link) { + if (lp->speed != phydev->speed) { + lp->speed = phydev->speed; + status_change = 1; + mac_restart = 1; + } + + if (lp->duplex != phydev->duplex) { + lp->duplex = phydev->duplex; + status_change = 1; + mac_restart = 1; + } + + if (!lp->link) { + netif_tx_schedule_all(dev); + lp->link = phydev->link; + status_change = 1; + } + } else if (lp->link) { + lp->link = phydev->link; + status_change = 1; + } + + if (status_change) { + printk(KERN_INFO "TSMAC%d: ", lp->unit); + phy_print_status(phydev); + + if (mac_restart) { + tsmac_shutdown(dev); + if (schedule_delayed_work_on + (atomic_read(&lp->timer_task_cpu), + &lp->restart_task, 0)) + atomic_inc(&lp->restart_pending_cnt); + } + } + + spin_unlock_irqrestore(&lp->control_lock, flags); +} diff --git a/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.h b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.h new file mode 100644 index 0000000..a889c9d --- /dev/null +++ b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac.h @@ -0,0 +1,105 @@ +/****************************************************************************** +** Copyright 2006 - 2011 PMC-Sierra, Inc +** +** PMC-SIERRA DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER +** RESULTING FROM THE USE OF THIS SOFTWARE +** +** FILE NAME: pmcmsp_tsmac.h +** +** DESCRIPTION: Linux 2.6 driver public header for TSMAC. +** +** 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; +******************************************************************************/ + +#ifndef _PMCMSP_TSMAC_H_ +#define _PMCMSP_TSMAC_H_ + +#include <linux/phy.h> + +#define MSP_TSMAC_ID "pmc_tsmac" + +/* TX/RX descriptor ring size */ +/* TODO: increase RX_RING_SIZE to inccrease RX queue depth */ +#define RX_RING_SIZE_DEF 256 +#define TX_RING_SIZE_DEF 32 + +#define MAX_RING_SIZE 512 +#define MIN_RING_SIZE 16 + +/* TX polling timer */ +#define TXPOLLCNT_CH0 0 +#define TXPOLLCNT_CH1 0 + +/* packet header offset, for checksum calculation */ +#define IPHDR_OFFSET_IPV4_VLAN 18 +#define IPHDR_OFFSET_IPV4_NVLAN 14 +#define IPHDR_OFFSET_IPV6_VLAN 20 +#define IPHDR_OFFSET_IPV6_NVLAN 16 + +/* IOCTL commands */ +#define TSMACIOCTL_COUNT 12 +#define TSMACIOCTL SIOCDEVPRIVATE +#define PMC_ETH_IOCMD_CLASSDEFVQ_READ (TSMACIOCTL + 2) +#define PMC_ETH_IOCMD_CLASSDEFVQ_WRITE (TSMACIOCTL + 2) +#define PMC_ETH_IOCMD_CLASSADDR_READ (TSMACIOCTL + 3) +#define PMC_ETH_IOCMD_CLASSADDR_WRITE (TSMACIOCTL + 3) +#define PMC_ETH_IOCMD_CLASSVLAN_READ (TSMACIOCTL + 4) +#define PMC_ETH_IOCMD_CLASSVLAN_WRITE (TSMACIOCTL + 4) +#define PMC_ETH_IOCMD_CLASS4DSCP_READ (TSMACIOCTL + 5) +#define PMC_ETH_IOCMD_CLASS4DSCP_WRITE (TSMACIOCTL + 5) +#define PMC_ETH_IOCMD_CLASS6DSCP_READ (TSMACIOCTL + 6) +#define PMC_ETH_IOCMD_CLASS6DSCP_WRITE (TSMACIOCTL + 6) +#define PMC_ETH_IOCMD_CLASSETHTYPE_READ (TSMACIOCTL + 7) +#define PMC_ETH_IOCMD_CLASSETHTYPE_WRITE (TSMACIOCTL + 7) +#define PMC_ETH_IOCMD_PROVFIFO_READ (TSMACIOCTL + 8) +#define PMC_ETH_IOCMD_PROVFIFO_WRITE (TSMACIOCTL + 8) +#define PMC_ETH_IOCMD_HWPAUSE_READ (TSMACIOCTL + 9) +#define PMC_ETH_IOCMD_HWPAUSE_WRITE (TSMACIOCTL + 9) +#define PMC_ETH_IOCMD_PROVVQ_READ (TSMACIOCTL + 10) +#define PMC_ETH_IOCMD_PROVVQ_WRITE (TSMACIOCTL + 10) +#define PMC_ETH_IOCMD_TXPRIOTHRES_READ (TSMACIOCTL + 11) +#define PMC_ETH_IOCMD_TXPRIOTHRE_WRITE (TSMACIOCTL + 11) +#define PMC_ETH_IOCMD_QOSDEFAULT_WRITE (TSMACIOCTL + 12) +#define PMC_ETH_IOCMD_LINELOOP_READ (TSMACIOCTL + 13) +#define PMC_ETH_IOCMD_LINELOOP_WRITE (TSMACIOCTL + 13) + +/** + * enum tsmac_conntype_enum - connection type of the MAC interface + * @MSP_CT_UNSED: the port is not used + * @MSP_CT_ETHYPHY: the port is connected to a sigle PHY + * @MSP_CT_ETHNOPHY: the port is connected to non-ethernet PHY. + * @MSP_CT_ETHSWITCH: the port is connected to a switch + * @MSP_CT_MOCA: the port is used for MoCA + * @MSP_CT_GPON: the port is used for GPON + * @MSP_CT_MAX: this indicates total number of members in this enum + */ +enum msp_conntype_enum { + MSP_CT_UNUSED = 0, + MSP_CT_ETHPHY, + MSP_CT_ETHSWITCH, + MSP_CT_ETHNOPHY, + MSP_CT_MOCA, + MSP_CT_GPON, + MSP_CT_MAX +}; + + +/** + * struct eth_platform_data - static MAC and PHY setup of the platform + * @phy_addr: address of the PHY on the MDIO bus (@bus_unit) + * @bus_unit: the actual MDIO bus that the PHY at @phy_addr is on + * @conn_type: default connection type of the MAC interface + * + * This structure contains the platform specific data of PHY's connection, i.e. + * that address of the PHY and the actual MDIO bus it is connected to. As + * well, this struct contains the connection type of the MAC interface. + */ +struct eth_platform_data { + int phy_addr; + int bus_unit; + enum msp_conntype_enum conn_type; +}; + +#endif /* _PMCMSP_TSMAC_H_ */ diff --git a/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_local.h b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_local.h new file mode 100644 index 0000000..5437029 --- /dev/null +++ b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_local.h @@ -0,0 +1,924 @@ +/****************************************************************************** +** Copyright 2006 - 2011 PMC-Sierra, Inc +** +** PMC-SIERRA DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER +** RESULTING FROM THE USE OF THIS SOFTWARE +** +** FILE NAME: pmcmsp_tsmac_local.h +** +** DESCRIPTION: Linux 2.6 driver local header for TSMAC. +** +** 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 +******************************************************************************/ + +#ifndef _PMCMSP_TSMAC_LOCAL_H_ +#define _PMCMSP_TSMAC_LOCAL_H_ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/mii.h> +#include <linux/in.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/ethtool.h> +#include <linux/phy.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/io.h> +#include <asm/system.h> +#include <asm/dma.h> +#include <asm/byteorder.h> +#include <asm/bootinfo.h> +#include <asm/cpu-features.h> +/*Platform headers*/ +#include <msp_int.h> +#include <msp_regs.h> + +#include "pmcmsp_tsmac.h" + +/* + * Workaround, Flood Control must always be enabled + * (PEP 33290, PM71_69_25_A/APOLLO_EMU) + * + * Driver simulates Flood Control disable by configuring + * it so packets are never dropped + */ +#define TSMAC_FLOOD_WORKAROUND + +#define TSMAC_MAX_UNITS 3 +#define TSMAC_NUM_TX_CH 2 + +/* default CPU for running non-datapath background/timer tasks */ +#define TSMAC_TASK_CPU_DEFAULT 0 + +/* number of skb->priority values we support (starting from 0) */ +#define TSMAC_NUM_SKB_PRIORITY 8 + +/* low priority egress queue pause enable flag */ +#define TSMAC_EGRESS_LO_PRIO_PAUSE 1 + +/* low priority egress queue full */ +#define TSMAC_EGRESS_LO_PRIO_FULL 2 + +/* For debugging */ +#define TSMAC_ATTR_INLINE __attribute__ ((__always_inline__)) + +#define TSMAC_SUCCESS (0) +#define TSMAC_ERROR (1) +#define TSMAC_ERROR_BASE (9000) +#define TSMAC_Q_INIT_ERROR (TSMAC_ERROR_BASE + 1) +#define TSMAC_Q_FREE_ERROR (TSMAC_ERROR_BASE + 2) +#define TSMAC_NO_PHY (TSMAC_ERROR_BASE + 3) +#define TSMAC_PHY_INIT_ERROR (TSMAC_ERROR_BASE + 4) + +/* Tuning parameters */ +#define TX_TIMEOUT (5) /* TX timeout (seconds) */ +#define STATS_CHK_TIME (20) /* time (sec) to check stats */ + +#define TSMAC_KERNEL_DATA 0 +#define TSMAC_USER_DATA 1 + +/* clock config for different link speeds */ +#define TSMAC_RMII_TX_CLK_IN 0 +#define TSMAC_RMII_REF_CLK_IN 1 +#define TSMAC_SYS_CLK_SLOW 0 +#define TSMAC_SYS_CLK_FAST 1 + +/* TX/RX descriptor */ +#define FD_DMA_Own (REG_BIT31) +#define FD_SOP (REG_BIT30) +#define FD_EOP (REG_BIT29) +#define FD_TxInt_En (REG_BIT28) +#define FD_TxCRC_Dis (REG_BIT27) +#define FD_TxPad_Dis (REG_BIT26) +#define FD_RxTrunc (REG_BIT12) +#define FD_RxBuff_Mask (0x1FFFFFFC) +#define FD_TxBuff_Mask (0x1FFFFFFF) +#define FD_Next_Mask (0xFFFFFFF0) +#define FD_RxChksum_Mask (0x1FFFE000) +#define FD_RxChksum_Shift (13) +#define FD_TxBuffLn_Mask (0x00000FFF) +#define FD_RxBuffLn_Mask (0x00000FFF) + +/* QoS, L2 classification */ +#define VQ_DEFAULT 0 +#define VQ_MAX 2 +#define L2_ARC_Class_Min (0x88) +#define L2_ARC_Class_Max (0x130) +#define L2_DA_Rule0_offset (0x88) +#define L2_SA_Rule0_offset (0xB8) +#define L2_Rule_Index (12) +#define L2_Mask_Index (6) +#define L2_VQ_Map_Offset (232) +#define L2_VQ_Map_Mask (0xF0000000) +#define Ethtype_VQ_Offset (236) +#define VLAN_VQ_Map_Offset (240) +#define IPv4_VQ_Map_Offset (244) +#define IPv6_VQ_Map_Offset (276) +#define IP_VQ_Map_Index (4) + +/* PAUSE frame */ +#define Pause_Control_Offset (120) +#define Pause_MAC_Control_Type (0x8808) +#define Pause_Operation_Opcode (0x0001) + +/* ARC entry */ +/* max number of ARC entries */ +#define ARC_ENTRY_MAX 21 +/* ARC entry for destination address of PAUSE frame (transmit) */ +#define ARC_ENTRY_PAUSE_DST 0 +/* ARC entry for source address of PAUSE frame (transmit) */ +#define ARC_ENTRY_PAUSE_SRC 1 +/* ARC entry for device MAC address */ +#define ARC_ENTRY_MAC 2 +/* ARC entry for special multicast address of PAUSE frame (receive) */ +#define ARC_ENTRY_PAUSE_RX 3 +/* ARC entry for MAC control type, PAUSE frame opcode, and operand value */ +#define ARC_ENTRY_PAUSE_CTL 20 + +/* bit assignments */ +#define REG_BIT0 0x00000001 +#define REG_BIT1 0x00000002 +#define REG_BIT2 0x00000004 +#define REG_BIT3 0x00000008 +#define REG_BIT4 0x00000010 +#define REG_BIT5 0x00000020 +#define REG_BIT6 0x00000040 +#define REG_BIT7 0x00000080 +#define REG_BIT8 0x00000100 +#define REG_BIT9 0x00000200 +#define REG_BIT10 0x00000400 +#define REG_BIT11 0x00000800 +#define REG_BIT12 0x00001000 +#define REG_BIT13 0x00002000 +#define REG_BIT14 0x00004000 +#define REG_BIT15 0x00008000 +#define REG_BIT16 0x00010000 +#define REG_BIT17 0x00020000 +#define REG_BIT18 0x00040000 +#define REG_BIT19 0x00080000 +#define REG_BIT20 0x00100000 +#define REG_BIT21 0x00200000 +#define REG_BIT22 0x00400000 +#define REG_BIT23 0x00800000 +#define REG_BIT24 0x01000000 +#define REG_BIT25 0x02000000 +#define REG_BIT26 0x04000000 +#define REG_BIT27 0x08000000 +#define REG_BIT28 0x10000000 +#define REG_BIT29 0x20000000 +#define REG_BIT30 0x40000000 +#define REG_BIT31 0x80000000 + +/* TSMAC reset bit */ +#define TSMAC_EA_RST 0x00000040 +#define TSMAC_EB_RST 0x00000080 +#define TSMAC_EC_RST 0x00000004 + +#ifdef CONFIG_PMC_MSP7150_GW_MOCA +/* GPIO for MAC C ethernet/moca MUX */ +#define MACC_MUX_GPIO 10 +/* GPIOs for MoCA reset */ +#define TSMAC_RESET_GPIO_MACB 21 +#define TSMAC_RESET_GPIO_MACC 23 +#endif + +/* control output register */ +#define TSMAC_CTRL_OUTPUT 0xBC0003DC +#define SYS_LinkSpeed_Shift (1) +#define SYS_Mode_Shift (3) +#define SYS_RMII_Clk_Shift (6) +#define SYS_Sclk_Sel_Shift (7) +#define SYS_MACA_Shift (16) +#define SYS_MACB_Shift (8) +#define SYS_MACC_Shift (0) + +/* MAC C output control register (defunct DSL output control register) */ +#define TSMAC_MAC_C_OUTPUT_CTRL 0xBC0003E0 +#define MAC_C_EN1 (REG_BIT4) /* set to 1 to enable MAC C */ +#define MAC_C_EN2B (REG_BIT3) /* set to 0 to enable MAC C */ +#define MAC_C_EN3 (REG_BIT2) /* set to 1 to enable MAC C */ +#define MAC_C_EN4 (REG_BIT0) /* set to 1 to enable MAC C */ + +/* DMA registers */ +struct msp_dma_regs { + u32 dma_ctl; /* 0x00 */ +#define DMA_TxDisable_CH1 (REG_BIT5) +#define DMA_TxDisable_CH0 (REG_BIT4) +#define DMA_RxAlign_Mask (0x0000000C) +#define DMA_RxAlign_Shift (2) +#define DMA_IntMask (REG_BIT1) +#define DMA_SWIntReq (REG_BIT0) + + u32 dma_init; /* 0x04 */ +#define DMA_RxInit_DescList (REG_BIT3) +#define DMA_TxInit_DescList (REG_BIT2) +#define DMA_TxWakeUp_CH1 (REG_BIT1) +#define DMA_TxWakeUp_CH0 (1) + + u32 txdesc_ch0; /* 0x08 */ + u32 txdesc_ch1; /* 0x0C */ +#define DMA_TxDesc_AddrMask (0X1FFFFFF0) +#define DMA_TxDesc_AddrShift (4) + + u32 reserved0; /* 0x10 */ + u32 txpollcnt_ch0; /* 0x14 */ + u32 txpollcnt_ch1; /* 0x18 */ +#define DMA_TxPCTR_Mask (0X00003FFF) + + u32 rxdesc; /* 0x1C */ +#define DMA_RxDesc_AddrMask (0X1FFFFFF0) +#define DMA_RxDesc_AddrShift (4) + + u32 iphdr_offset; /* 0x20 */ +#define DMA_OffsetVLAN_Mask (0X0000FF00) +#define DMA_OffsetVLAN_Shift (8) +#define DMA_OffsetNonVLAN_Mask (0X000000FF) + + u32 int_ena; /* 0x24 */ +#define IntEn_BadAddrRd (REG_BIT9) +#define IntEn_BadAddrWr (REG_BIT8) +#define IntEn_RxDescEx (REG_BIT4) +#define IntEn_SysBusErr (REG_BIT3) + + u32 int_src; /* 0x28 */ +#define IntSrc_BadAddrRd (REG_BIT9) +#define IntSrc_BadAddrWr (REG_BIT8) +#define IntSrc_MAC (REG_BIT7) +#define IntSrc_GPMII (REG_BIT6) +#define IntSrc_SwInt (REG_BIT5) +#define IntSrc_RxDescEx (REG_BIT4) +#define IntSrc_SysBusErr (REG_BIT3) +#define IntSrc_MACRx (REG_BIT2) +#define IntSrc_MACTx_CH1 (REG_BIT1) +#define IntSrc_MACTx_CH0 (REG_BIT0) + + u32 reserved1; /* 0x2C */ + u32 bad_addr_rd_err; /* 0x30 */ +#define BadAddrRd_Mask (0X1FFFFFFF) + + u32 bad_addr_wr_err; /* 0x34 */ +#define BadAddrWr_Mask (0X1FFFFFFF) +}; + +/* MAC registers */ +struct msp_mac_regs { + u32 pause_cnt; /* 0x8000 */ +#define PAUSE_COUNT_Mask (0X0000FFFF) + + u32 rmt_pause_cnt; /* 0x8004 */ +#define REMPAU_COUNT_Mask (0X0000FFFF) + + u32 tx_ctl_frame_stat; /* 0x8008 */ +#define TXSTAT_VALUE_Mask (0X003FFFFF) + + u32 mac_ctl; /* 0x800C */ +#define MAC_StatRoll (REG_BIT31) +#define MAC_TxPauRoll (REG_BIT30) +#define MAC_RxPauRoll (REG_BIT29) +#define MAC_Snap (REG_BIT17) +#define MAC_EnStatRoll (REG_BIT16) +#define MAC_HaltImm (REG_BIT1) +#define MAC_HaltReq (1) + + u32 arc_ctl; /* 0x8010 */ +#define ARC_CompEn (REG_BIT4) +#define ARC_NegARC (REG_BIT3) +#define ARC_BroadAcc (REG_BIT2) +#define ARC_GroupAcc (REG_BIT1) +#define ARC_StationAcc (1) + + u32 tx_ctl; /* 0x8014 */ +#define Tx_HwPAUSE_En (REG_BIT15) +#define Tx_EnComp (REG_BIT14) +#define Tx_EnLateColl (REG_BIT12) +#define Tx_EnExColl (REG_BIT11) +#define Tx_EnLCarr (REG_BIT10) +#define Tx_EnExDefer (REG_BIT9) +#define Tx_MII_10 (REG_BIT7) +#define Tx_SdPAUSE (REG_BIT6) +#define Tx_NoExDef (REG_BIT5) +#define Tx_FBack (REG_BIT4) +#define Tx_NoCRC (REG_BIT3) +#define Tx_Halt (REG_BIT1) +#define Tx_En (1) + + u32 tx_stat; /*0x8018 */ +#define Tx_PAUSE (REG_BIT21) +#define Tx_MACB (REG_BIT20) +#define Tx_VLAN (REG_BIT19) +#define Tx_BCast (REG_BIT18) +#define Tx_MCast (REG_BIT17) +#define Tx_SQErr (REG_BIT16) +#define Tx_Halted (REG_BIT15) +#define Tx_Comp (REG_BIT14) +#define Tx_Good (REG_BIT13) +#define Tx_LateColl (REG_BIT12) +#define Tx_LCarr (REG_BIT10) +#define Tx_ExDefer (REG_BIT9) +#define Tx_IntTx (REG_BIT7) +#define Tx_Paused (REG_BIT6) +#define Tx_TxDefer (REG_BIT5) +#define Tx_ExColl (REG_BIT4) +#define Tx_TxColl_Mask (0X0000000F) + + u32 rx_ctl; /* 0x801C */ +#define Rx_EnGood (REG_BIT20) +#define Rx_EnLenErr (REG_BIT19) +#define Rx_EnLongErr (REG_BIT18) +#define Rx_EnOver (REG_BIT17) +#define Rx_EnCRCErr (REG_BIT16) +#define Rx_EnAlign (REG_BIT15) +#define Rx_IgnorePause_Frm (REG_BIT10) +#define Rx_FloodEn (REG_BIT9) +#define Rx_FloodEn_Shift (9) +#define Rx_IgnoreCRC (REG_BIT7) +#define Rx_PassPAUSE (REG_BIT6) +#define Rx_PassCtl (REG_BIT5) +#define Rx_Halt (REG_BIT1) +#define Rx_En (1) + + u32 rx_stat; /* 0x8020 */ +#define Rx_Good (REG_BIT31) +#define Rx_ARCEnt_Mask (0x1F000000) +#define Rx_ARCEnt_Shift (25) +#define Rx_ARCStat_Mask (0x00F00000) +#define Rx_ARCStat_Shift (21) +#define Rx_RxPAUSE (REG_BIT20) +#define Rx_RxVLAN (REG_BIT19) +#define Rx_BCast (REG_BIT18) +#define Rx_MCast (REG_BIT17) +#define Rx_Halted (REG_BIT15) +#define Rx_LongErr (REG_BIT11) +#define Rx_OverFlow (REG_BIT10) +#define Rx_CRCErr (REG_BIT9) +#define Rx_AlignErr (REG_BIT8) +#define Rx_IntRx (REG_BIT6) +#define Rx_CTLRecd (REG_BIT5) +#define Rx_LenErr (REG_BIT4) +#define Rx_VQ_Mask (0x0000000F) + + u32 md_data; /* 0x8024 */ +#define MD_Data_Mask (0x0000ffff) + + u32 md_ca; /* 0x8028 */ +#define MD_CA_PreSup (REG_BIT12) +#define MD_CA_Busy (REG_BIT11) +#define MD_CA_Wr (REG_BIT10) +#define MD_CA_PHY_Mask (0x000003E0) +#define MD_CA_PHY_Shift (5) +#define MD_CA_PHYReg_Mask (0x0000001F) + + u32 arc_addr; /* 0x802C */ +#define ARC_MemLoc_Mask (0x000001FC) +#define ARC_MemLoc_Shift (2) + + u32 arc_data; /* 0x8030 */ +#define ARC_Data0_Mask (0xFF000000) +#define ARC_Data0_Shift (24) +#define ARC_Data1_Mask (0x00FF0000) +#define ARC_Data1_Shift (16) +#define ARC_Data2_Mask (0x0000FF00) +#define ARC_Data2_Shift (8) +#define ARC_Data3_Mask (0x000000FF) +#define ARC_Data3_Shift (0) + + u32 arc_ena; /* 0x8034 */ +#define ARC_Ena_Mask ((1 << ARC_ENTRY_MAX)-1) +#define ARC_Ena_Bit(index) (1<<(index)) + + u32 max_length; /* 0x8038 */ + u32 xoff_thresh; /* 0x803C */ + u32 xon_thresh; /* 0x8040 */ + u32 rmt_pause_cmp; /* 0x8044 */ + u32 drop_on_thresh; /* 0x8048 */ +#define RX_DROPON_MAX (8188) +#define RX_DropOn_Mask (0x0000FFFF) + u32 drop_off_thresh; /* 0x804C */ +#define RX_DROPOFF_MAX (8188) +#define RX_DropOff_Mask (0x0000FFFF) + u32 vq_conf; /* 0x8050 */ +#define VQ_Drop_Disable (REG_BIT30) +#define VQ_Wr_Op (REG_BIT31) +#define VQ_Def_Map_Mask (0xF0000000) +#define VQ_Def_Map_Shift (28) + + u32 l2_rule_ena; /* 0x8054 */ + u32 vlan_tci_offset; /* 0x8058 */ +#define VLAN_TCI_Offset_Mask (0x0000003F) + u32 reserved[9]; /* 0x805C */ + u32 vq_token_cnt[8]; /* 0x8080 */ +#define VQ_TC_Wr_Op (REG_BIT31) +#define VQ_TC_Drop_Disable (REG_BIT30) +#define VQ_TC_Drop_Disable_Shift (30) +#define VQ_TC_Token_Cnt_Mask (0xFFFF) +#define VQ_TC_Token_Cnt_Shift (0) + + u32 reserved1[24]; /* 0x8084 */ + u32 tx_good_frame_stat; /* 0x8100 */ + u32 tx_good_byte_stat; /* 0x8104 */ + u32 rx_good_frame_stat; /* 0x8108 */ + u32 rx_good_byte_stat; /* 0x810C */ + u32 rx_total_frame_stat; /* 0x8110 */ + u32 rx_total_byte_stat; /* 0x8114 */ + u32 rx_over_frame_stat; /* 0x8118 */ + u32 rx_over_byte_stat; /* 0x811C */ + u32 pause_frame_stat; /* 0x8120 */ + u32 rx_dropped_byte_stat; /* 0x8124 */ + u32 vq_dropped_stat[8]; /* 0x8128 */ +}; + +/* GPMII registers */ +struct msp_gpmii_regs { + u32 int_stat; + u32 int_ena; + u32 int_val; +#define GPMII_RCLKMON_MASK (0x00000003) + + u32 conf_general; +#define GPMII_Force_Crs_Col_En (REG_BIT15) +#define GPMII_Felbk (REG_BIT8) +#define GPMII_TxDataPath_En (REG_BIT1) +#define GPMII_RxDataPath_En (REG_BIT0) + + u32 conf_mode; +#define GPMII_Dplx_Sel (REG_BIT12) +#define GPMII_Dplx_Shift (12) +#define GPMII_LinkSpeed_Mask (0x00000300) +#define GPMII_LinkSpeed_Shift (8) +#define GPMII_Mode_Mask (0x00000007) + + u32 conf_rx_override; + u32 conf_tx_override; + u32 diag_stat; +}; + +/* DMA RX packet offset */ +#define DMA_CTL_CMD (IP_HDR_ALIGN << DMA_RxAlign_Shift) +/* TX control, enable TX completion interrupts */ +#define TX_CTL_ENA (Tx_EnComp | Tx_EnLateColl | Tx_EnExColl | \ + Tx_EnLCarr | Tx_EnExDefer | Tx_En) + +/* TX control, disable TX completion interrupts */ +#define TX_CTL_DIS (Tx_EnLateColl | Tx_EnExColl | Tx_EnLCarr | \ + Tx_EnExDefer | Tx_En) + +/* enable/disable pause frame generation */ +#define TX_CFG(cmd, pause_enable) (pause_enable ? (cmd | Tx_HwPAUSE_En) : \ + (cmd & ~Tx_HwPAUSE_En)) + +/* RX control, enable RX interrupts */ +#define RX_CTL_ENA (Rx_EnGood | Rx_EnLenErr | Rx_EnLongErr | Rx_EnOver | \ + Rx_EnCRCErr | Rx_EnAlign | Rx_PassPAUSE | \ + Rx_PassCtl | Rx_FloodEn | Rx_En) + +/* RX, control, disable RX interrupts */ +#define RX_CTL_DIS (Rx_PassPAUSE | Rx_PassCtl | Rx_FloodEn | Rx_En) + +/* enbale bus error and RX exhausted interrupts */ +#define INT_EN_CMD (IntEn_RxDescEx | IntEn_SysBusErr | IntEn_BadAddrRd | \ + IntEn_BadAddrWr) + +/* TSMAC register structures */ +struct msp_regs { + struct msp_dma_regs dma; + struct msp_mac_regs __attribute__ ((aligned(0x8000))) mac; + struct msp_gpmii_regs __attribute__ ((aligned(0x10000))) gpmii; +}; + +/* egress queue priorities */ +enum tsmac_egress_prio { + TSMAC_DESC_PRI_HI = 0, + TSMAC_DESC_PRI_LO +}; + +extern const char *msp_conntype_str[MSP_CT_MAX]; + +/* MII types */ +enum tsmac_mii_type_enum { + TSMAC_MT_MII, + TSMAC_MT_GMII, + TSMAC_MT_RMII, + TSMAC_MT_MAX +}; +extern const char *tsmac_mii_type_str[TSMAC_MT_MAX]; + +#define TSMAC_A 0 +#define TSMAC_B 1 +#define TSMAC_C 2 + +/* VQ configuration */ +struct tsmac_vq_config { + /* number of packets mapped to a VQ */ + unsigned short vq_token_count; + + /* to disable packet drop on a VQ */ + unsigned char vq_drop_disable; + + /* VQ number (0 to 7) */ + unsigned char vq_num; +}; + +/* RX packets threshold configuration */ +struct tsmac_drop_threshold { + /* FIFO threshold to start dropping low-priority packets */ + unsigned short drop_off_thresh; + + /* FIFO threshold to stop dropping */ + unsigned short drop_on_thresh; +}; + +/* L2 classification rules */ +struct tsmac_l2_class_rule { + /* rule number 0-3 */ + unsigned char rule_num; + + /* status of the rule */ + unsigned char enable; + + /* matching VQ */ + unsigned char vqnum; + + /* Destination Address */ + unsigned char DA[6]; + + /* Destination Address Mask */ + unsigned char DM[6]; + + /* Source Address */ + unsigned char SA[6]; + + /* Source Address Mask */ + unsigned char SM[6]; + + /* + * enable/disable can be edited without changing MAC, + * 1 = change the status of the rule only, 0 = set the entire rule + */ + unsigned char change_state_only; +}; + +/* full duplex flow control using PAUSE frames */ +struct tsmac_flow_ctl { + u32 enable; + + /* XOFF - PAUSE frame triggering level when RX FIFO >= XOFF */ + unsigned short xoff; + + /* XON - PAUSE frame triggering level when RX FIFO <= XON */ + unsigned short xon; + + /* compare against the XOFF */ + unsigned short compare; + + /* source address for PAUSE operations */ + unsigned char src_addr[6]; + + /* destination address for PAUSE operations */ + unsigned char dest_addr[6]; + + /* duration of pause */ + unsigned short duration; +}; + +/* VLAN VQ configuration*/ +struct tsmac_vlanvq_config { + /* VQ mapping for 8 VLAN user priority levels each of size a nibble */ + unsigned char vlanvq[4]; + + /* Byte offset of the VLAN TCI field from start of received packet */ + unsigned char tci_offset; +}; + +/* configurable Ethernet type values*/ +struct tsmac_ethtype_config { + /* configurable Ethernet type value */ + unsigned char ethtype[2]; + + /* status of the Ethernet type rule */ + unsigned char ethtype_enable; + + /* VQ (0-7) number for Ethernet type rule */ + unsigned char ethtype_vq; +}; + +/* IPv4/IPv6 DSCP-VQ map configuration */ +struct tsmac_ipvq_config { + /* IPv4/IPv6 virtual queue mapping table */ + unsigned char ip_vq[4]; + + /* 1 = IPv4 rule, 0 = IPv6 rule */ + unsigned short ipv4; + + /* DSCP range (0-7, 8-15,... 56-63) */ + unsigned short dsp_range; +}; + +/* flood control */ +struct tsmac_vqnflood_configure { + u8 flood_enable; + u8 default_vq; + + /* virtual queue configuration */ + struct tsmac_vq_config vq_config[8]; + + /* RX packets drop threshold */ + struct tsmac_drop_threshold drop_threshold; + + /* L2 classification rules, Rule 0 - Rule 3 */ + struct tsmac_l2_class_rule l2_rule[4]; + + /* VLAN VQ configuration */ + struct tsmac_vlanvq_config vlanvq_config; + + /* Ethernet type */ + struct tsmac_ethtype_config ethtype_config; + + /* IPv4/IPv6 DSCP classification of 64 mappings to VQ */ + struct tsmac_ipvq_config ipv4_vq[8]; + struct tsmac_ipvq_config ipv6_vq[8]; +}; + +/* IP header offset configuration */ +struct tsmac_iphdroffset_configure { + unsigned char vlan; + unsigned char nvlan; +}; + +/* + * Structure to define DMA data buffers. Must be aligned to a 16-byte boundary + * to meet alignment restrictions for the Q_Desc + */ +#define __tsmac_desc_align __attribute__((aligned(16))) + +/* TX/RX descriptor structure, shared between CPU and DMA */ +struct Q_Desc { + u32 FDNext; /* next descriptor */ + u32 FDBuffPtr; /* data buffer pointer */ + u32 FDCtl; /* descriptor control */ + u32 FDStat; /* descriptor status */ +} __tsmac_desc_align; + +/* TX descriptor ring and management field */ +struct tsmac_tx { + void *desc_base; /* TX descriptors base address */ + void *skb_base; /* TX skb pointers base address */ + unsigned int size; /* TX descriptor ring size */ + unsigned int head; /* TX queues head index */ + unsigned int tail; /* TX queues tail index */ + u32 qcnt; /* used TX descriptor count */ + struct Q_Desc *desc_p; /* pointer to the descriptor array */ + struct sk_buff **skb_pp; /* pointer to the skb pointer array */ +}; + +/* RX descriptor ring and management field */ +struct tsmac_rx { + void *desc_base; /* RX descriptors base address */ + void *skb_base; /* RX skb pointers base address */ + unsigned int size; /* RX descriptor ring size */ + unsigned int index; /* current RX descriptor index */ + struct Q_Desc *desc_p; /* pointer to the descriptor array */ + struct sk_buff **skb_pp; /* pointer to the skb pointer array */ +}; + +/* + * Counter statistics maintained in software, normally we use net_device_stats + * in Linux. This structure defines addtional stats supported in TSMAC + */ +struct tsmac_stats_sw { + u64 rx_bytes; /* received bytes */ + u32 rx_ints; /* RX interrupts */ + u32 rx_vq_frames[8]; /* received packets in each VQ */ + + u32 rx_long_errors; /* packet exceeds supported length */ + u32 rx_trunc_errors; /* packet was truncated */ + + u64 tx_bytes; /* transmitted bytes */ + u32 tx_ints; /* TX interrupts */ + u32 tx_full[TSMAC_NUM_TX_CH]; /* TX queue is full */ + +#ifdef CONFIG_TSMAC_TEST_CMDS + u32 rx_nochksum_vlan; /* VLAN packets with wrong L4 checksum */ + u32 rx_nochksum_nonvlan; /* non-VLAN packets with + * wrong L4 chksum + */ +#endif +}; + +/* + * RX VQ token counter + */ +struct tsmac_rx_vq_token { + u32 pkt_cnt; /* number of packets received in a VQ */ +#ifdef CONFIG_TSMAC_VQ_TOKEN_CNT_WORKAROUND + u32 update_cnt; /* number of updates performed on a VQ */ +#endif +}; + +/* counter statistics maintained in hardware */ +struct tsmac_stats_hw { + u32 rx_packets; /* received packets */ + u64 rx_bytes; /* received bytes */ + u64 rx_dropped_bytes; /* dropped bytes on the RX side */ + u32 rx_vq_drops[8]; /* VQ where RX packets are dropped */ + + u32 tx_packets; /* transmitted packets */ + u64 tx_bytes; /* transmitted bytes */ +}; + +/* hook function prototype declaration */ +typedef int (*tsmac_hook_function) (struct sk_buff **skb, + struct net_device *dev, void *priv); + +/* private information each interface */ +struct tsmac_private { + u8 unit; /* logical unit number */ + u8 loopback_enable; /* line loopback status */ + enum msp_conntype_enum conn_type; /* eth, switch, etc. */ + enum tsmac_mii_type_enum mii_type; /* MII/RMII/GMII */ + + /* MAC link status */ + int link; + int speed; + int duplex; + + /* phy configuration */ + u32 bus_unit; + u32 phy_addr; + + struct phy_device *phyptr; + struct mii_bus bus; + + /* ioremapped register access cookie */ + struct msp_regs __iomem *reg_map; + + /* stats counter timer */ + struct timer_list stats_timer; + + /* lock for stats access */ + spinlock_t stats_lock; + + /* lock for control access */ + spinlock_t control_lock; + + /* statistics */ + struct net_device_stats lx_stats; /* Linux standard stats */ + struct tsmac_stats_sw sw_stats; /* additional software stats */ + struct tsmac_stats_hw hw_stats; /* hardware stats */ + + /* work queue for the restart task */ + struct delayed_work restart_task; + + /* TX/RX descriptor rings */ + struct tsmac_tx tx[TSMAC_NUM_TX_CH]; + struct tsmac_rx rx; + + /* lock for TX */ + spinlock_t tx_lock; + + /* locks for the restart task */ + spinlock_t restart_lock; + atomic_t restart_pending_cnt; + atomic_t close_flag; + + /* device object pointer */ + struct device *dev; + struct net_device *ndev; + struct napi_struct napi; + + /* full duplex flow control using PAUSE frames */ + struct tsmac_flow_ctl flow_ctl; + + /* VQ and Flood control parameters */ + struct tsmac_vqnflood_configure vqnflood; + + /* IP header offset configuration parameters */ + struct tsmac_iphdroffset_configure iphdr_offset; + + /* TX packet skb->priority to dual egress queue priority mapping */ + enum tsmac_egress_prio egress_prio[TSMAC_NUM_SKB_PRIORITY]; + + struct tsmac_rx_vq_token vq_token[8]; + + /* CPU on which the timer tasks should run on */ + atomic_t timer_task_cpu; + + /* Hook points to allow 3rd party functions to hook in and manipulate + * packets + */ + tsmac_hook_function tsmac_rx_hook; + tsmac_hook_function tsmac_tx_hook; + + /* private datas to be used freely by the hook functions. */ + void *rx_priv, *tx_priv; +}; + +/* TSMAC specific data for Set/Get commands */ +struct tsmac_io_data { + /* Set/Get command (1 = get-command, 0 = set-command) */ + unsigned long subcmd; + + /* command specific data */ + void *data; +}; + +/* TSMAC driver information */ +extern const char version[]; +extern const char cardname[]; +extern const char drv_version[]; +extern const char drv_reldate[]; +extern const char drv_file[]; + +extern struct ethtool_ops tsmac_ethtool_ops; + +extern u32 tsmac_read(void *addr); +extern void tsmac_write(u32 val, void *addr); +extern void tsmac_adjust_link(struct net_device *dev); +extern void tsmac_register_bus(struct mii_bus *bus, int mac_unit); +extern struct phy_device *tsmac_mii_probe(struct net_device *dev, + void (*adjust_link) (struct net_device + *)); +extern int tsmac_copy_to_mem(void *dst, void *src, u32 n, u8 context); +extern int tsmac_copy_from_mem(void *dst, void *src, u32 n, u8 context); +extern void tsmac_set_arc_entry(struct net_device *dev, int index, + unsigned char *addr); +extern int tsmac_set_mac_addr(struct net_device *dev, void *addr); +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE +extern int tsmac_get_loopback(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_loopback(struct net_device *dev, struct ifreq *req, + u8 context); +#endif +extern void tsmac_set_pause_param(struct net_device *dev); +extern int tsmac_print_map_pause_arc(struct tsmac_private *lp, char *buffer); +extern int tsmac_print_map_classifier(struct tsmac_private *lp, char *buffer); +extern int tsmac_get_default_vq_map(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_default_vq_map(struct net_device *dev, struct ifreq *req, + u8 context); +extern void tsmac_get_l2_class_entry(struct net_device *dev, u32 entry_addr, + unsigned char *data); +extern void tsmac_set_l2_class_entry(struct net_device *dev, u32 entry_addr, + unsigned char *data); +extern int tsmac_get_addr_class_rule(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_addr_class_rule(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_get_vlan_class_rule(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_vlan_class_rule(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_get_ip_class_rule(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_ip_class_rule(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_get_ethtype_class_rule(struct net_device *dev, + struct ifreq *req, u8 context); +extern int tsmac_set_ethtype_class_rule(struct net_device *dev, + struct ifreq *req, u8 context); +extern int tsmac_get_vq_config(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_vq_config(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_get_drop_thresh(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_drop_thresh(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_vqnpause(struct net_device *dev); +extern int tsmac_get_egress_prio(struct net_device *dev, struct ifreq *req, + u8 context); +extern int tsmac_set_egress_prio(struct net_device *dev, struct ifreq *req, + u8 context); +extern void tsmac_config_def_vqnpause(struct net_device *dev); +extern void tsmac_update_hw_stats(struct net_device *dev); +extern int tsmac_moca_reset(struct net_device *dev, struct ifreq *req, + u8 context); + +extern int tsmac_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); +extern void tsmac_create_proc_entries(struct net_device *dev); +extern void tsmac_remove_proc_entries(void); +extern int tsmac_set_phyaddr(struct net_device *dev, int phyunit, int phyaddr); +extern int tsmac_set_conntype(struct net_device *dev, + enum msp_conntype_enum conn_type); +extern int tsmac_set_mii_type(struct net_device *dev, + enum tsmac_mii_type_enum mii_type); +extern void tsmac_enable_mac_c(struct net_device *dev); +#endif /* _PMCMSP_TSMAC_LOCAL_H_ */ diff --git a/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_mdiobus.c b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_mdiobus.c new file mode 100644 index 0000000..b954448 --- /dev/null +++ b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_mdiobus.c @@ -0,0 +1,205 @@ +/** + * Copyright 2006 - 2011 PMC-Sierra, Inc + * @file /drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_mdiobus.c + * + * MDIO bus interface for msp71xx/msp82xx TSMAC driver. It + * provides the mean to access TSMAC's station management registers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License. + * + */ + +#include "pmcmsp_tsmac_local.h" + +#define PHY_MII_DATA (0x00) +#define PHY_MII_CTRL (0x04) +#define TSMAC_MDIOBUS_TIMEOUT 100 + +/** + * tsmac_mdiobus_wait() - busy wait till MDIO bus is free + * @bus: pointer to the MDIO bus that is being accessed + */ +static int tsmac_mdiobus_wait(struct mii_bus *bus) +{ + struct net_device *dev = bus->priv; + struct tsmac_private *lp = netdev_priv(dev); + void __iomem *memaddr = &lp->reg_map->mac.md_data; + int timeout = TSMAC_MDIOBUS_TIMEOUT; + + while (tsmac_read(memaddr + PHY_MII_CTRL) & MD_CA_Busy) { + udelay(50); + if (--timeout == 0) + return -EBUSY; + } + + return 0; +} + +/** + * tsmac_mdiobus_read() - read data from PHY via MDIO bus + * @bus: pointer to the MDIO bus that is used to access the PHY + * @phyaddr: PHY address of the PHY that is being read + * @phy_reg: PHY register of the PHY that is being read + * + * This function read the data of the @phy_reg of the PHY at @phyaddr. + */ + +static int tsmac_mdiobus_read(struct mii_bus *bus, int phyaddr, int phy_reg) +{ + struct net_device *dev = bus->priv; + struct tsmac_private *lp = netdev_priv(dev); + void __iomem *memaddr = &lp->reg_map->mac.md_data; + u16 data; + int err; + + err = tsmac_mdiobus_wait(bus); + if (err < 0) + return err; + + tsmac_write(MD_CA_Busy | (phyaddr << MD_CA_PHY_Shift) | phy_reg, + memaddr + PHY_MII_CTRL); + + err = tsmac_mdiobus_wait(bus); + if (err < 0) { + printk(KERN_ERR "%s: mdio_read busy timeout!!\n", dev->name); + return err; + } + + data = tsmac_read(memaddr + PHY_MII_DATA); + return data; +} + +/** + * tsmac_mdiobus_write() - write data to PHY via MDIO bus + * @bus: pointer to the MDIO bus that is used to access the PHY + * @phyaddr: PHY address of the PHY that is being written + * @phy_reg: PHY register of the PHY that is being written + * @data: the value to be written to the PHY + * + * This function writes @data to the @phy_reg of the PHY at @phyaddr. + */ +static int tsmac_mdiobus_write(struct mii_bus *bus, int phyaddr, int phy_reg, + u16 data) +{ + struct net_device *dev = bus->priv; + struct tsmac_private *lp = netdev_priv(dev); + void __iomem *memaddr = &lp->reg_map->mac.md_data; + int err; + + err = tsmac_mdiobus_wait(bus); + if (err < 0) + return err; + + tsmac_write(data, memaddr + PHY_MII_DATA); + tsmac_write(MD_CA_Busy | MD_CA_Wr | (phyaddr << MD_CA_PHY_Shift) | + phy_reg, memaddr + PHY_MII_CTRL); + + err = tsmac_mdiobus_wait(bus); + if (err < 0) { + printk(KERN_ERR "%s: mdio_write busy timeout!!\n", dev->name); + return err; + } + + return 0; +} + +/** + * tsmac_mdiobus_reset() - dummy reset function for the TSMAC MDIO bus + * @bus: pointer to the MDIO bus that is to be reseted. + * + * TSMAC MDIO bus requires no action for reset; hence, return 0 immediately. + */ +static int tsmac_mdiobus_reset(struct mii_bus *bus) +{ + return 0; +} + +/** + * tsmac_register_bus() - initialize the MDIO bus struct + * @dev: pointer to the net device whose MDIO bus struct is being initialized + * @mac_unit: the TSMAC unit that the bus is connected to + */ +void tsmac_register_bus(struct mii_bus *bus, int mac_unit) +{ + int i; + char mdiobus_id[25]; + + /* set up mii_bus struct */ + bus->read = tsmac_mdiobus_read; + bus->write = tsmac_mdiobus_write; + bus->reset = tsmac_mdiobus_reset; + bus->state = MDIOBUS_ALLOCATED; + snprintf(mdiobus_id, 25, "TSMAC%d MDIO bus", mac_unit); + bus->name = mdiobus_id; + snprintf(bus->id, MII_BUS_ID_SIZE, "%x", mac_unit); + bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); + for (i = 0; i < PHY_MAX_ADDR; i++) + bus->irq[i] = PHY_POLL; + + /* bring up all PHYs connected to this MDIO bus */ + mdiobus_register(bus); +} + +/** + * tsmac_mii_probe() - find and attach an available PHY to the MAC + * @dev: pointer to the net device that is to be attached with a PHY + * @adjust_link: callback funciont provided to PAL to sync up MAC link status + * to the PHY link status. + * + * Search for an available PHY, either statically or dynamically, on the given + * MDIO bus and then attach it to the MAC interface. Both PHY's supported & + * advertising features are initialized to match MAC's supported features. + */ +struct phy_device *tsmac_mii_probe(struct net_device *dev, + void (*adjust_link) (struct net_device *)) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct phy_device *phydev = NULL; + int phy_addr; + char bus_id[MII_BUS_ID_SIZE]; + char bus_unit[4]; + char unit[4]; + + if (lp->dev->platform_data) { + /* static PHY setup is provided */ + sprintf(bus_unit, "%x", lp->bus_unit); + snprintf(bus_id, MII_BUS_ID_SIZE, PHY_ID_FMT, bus_unit, + lp->phy_addr); + } else { + /* scan for the first available PHY to attach */ + for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) { + if (lp->bus.phy_map[phy_addr]) { + sprintf(unit, "%x", lp->unit); + snprintf(bus_id, MII_BUS_ID_SIZE, PHY_ID_FMT, + unit, phy_addr); + break; + } + } + } + + phydev = phy_connect(dev, bus_id, adjust_link, 0, + PHY_INTERFACE_MODE_GMII); + + if (IS_ERR(phydev)) { + printk(KERN_ERR "%s: Could not attach to PHY\n", dev->name); + return NULL; + } + + /* 1000/Half is not supported by TSMAC */ + phydev->supported &= (SUPPORTED_10baseT_Half + | SUPPORTED_10baseT_Full + | SUPPORTED_100baseT_Half + | SUPPORTED_100baseT_Full + | SUPPORTED_1000baseT_Full + | SUPPORTED_Autoneg + | SUPPORTED_MII | SUPPORTED_TP); + + phydev->advertising = phydev->supported; + + printk(KERN_INFO "TSMAC%d: attached PHY driver [%s] " + "(mii_bus:phy_addr)\n", lp->unit, + phydev->drv->name); + + return phydev; +} diff --git a/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_user.c b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_user.c new file mode 100644 index 0000000..aa294f0 --- /dev/null +++ b/drivers/net/pmcmsp_tsmac/pmcmsp_tsmac_user.c @@ -0,0 +1,2687 @@ +/****************************************************************************** +** Copyright 2006 - 2007 PMC-Sierra, Inc +** +** PMC-SIERRA DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER +** RESULTING FROM THE USE OF THIS SOFTWARE +** +** FILE NAME: pmcmsp_tsmac_user.c +** +** DESCRIPTION: Linux 2.6 driver for TSMAC. +** +** 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; +** +******************************************************************************/ + +#include "pmcmsp_tsmac.h" +#include "pmcmsp_tsmac_local.h" + +/* + * Proc file config and name string + */ +/* config */ +#define KERN_BUF_MAX_SIZE 256 +#define TSMAC_REG_COUNT 50 +#define TSMAC_PROC_PERM 0644 +/* general */ +#define TSMAC_PROC_INFO "info" +#define TSMAC_PROC_MACADDR "macAddr" +#define TSMAC_PROC_REGCONTENTS "reg" +#define TSMAC_PROC_PAUSEARC "pauseARC" +#define TSMAC_PROC_MII "mii" +#define TSMAC_PROC_STATS "stats" +#define TSMAC_PROC_DUMPRX "dumpRX" +#define TSMAC_PROC_DUMPTX "dumpTX" +#define TSMAC_PROC_LOOPBACK "lineLoopBack" +#define TSMAC_PROC_IPHDROFFSET "ipHeaderOffset" +#define TSMAC_PROC_DESCSIZE "descSize" +#define TSMAC_PROC_CONNTYPE "connType" +#define TSMAC_PROC_PHYADDR "phyAddr" +#define TSMAC_PROC_MIITYPE "miiType" +#define TSMAC_PROC_LINKMODE "linkMode" +#define TSMAC_PROC_TASKCPU "taskCpu" + +/* pause frames */ +#define TSMAC_PROC_PAUSEENABLE "pauseEnable" +#define TSMAC_PROC_THRESHOLD "threshold" +#define TSMAC_PROC_COMPARE "compare" +#define TSMAC_PROC_DESTADDR "destAddr" +#define TSMAC_PROC_DURATION "duration" + +/* flood control */ +#define TSMAC_PROC_EGRESSPRIO "egressPriority" +#define TSMAC_PROC_DEFAULT "default" +#define TSMAC_PROC_L2RULE "macAddr" +#define TSMAC_PROC_ETHTYPEVLAN "ethTypeVlan" +#define TSMAC_PROC_ETHTYPEIPV4 "ethTypeIpv4" +#define TSMAC_PROC_ETHTYPEIPV6 "ethTypeIpv6" +#define TSMAC_PROC_ETHTYPEUSER "ethTypeUser" +#define TSMAC_PROC_DUMP "dump" +#define TSMAC_PROC_DROPTHRESHOLD "dropThres" +#define TSMAC_PROC_VQ "vq" + + +const char *tsmac_mii_type_str[TSMAC_MT_MAX] = { + "MII", + "GMII", + "RMII" +}; + + +const char *msp_conntype_str[MSP_CT_MAX] = { + "unused", + "eth", + "switch", + "eth_nophy", + "moca", + "gpon" +}; + + + +/* static funtion prototypes */ +static void tsmac_ethtool_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info); +static int tsmac_ethtool_get_settings(struct net_device *dev, + struct ethtool_cmd *cmd); +static int tsmac_ethtool_set_settings(struct net_device *dev, + struct ethtool_cmd *cmd); +static void tsmac_ethtool_get_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause); +static int tsmac_ethtool_set_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause); +static int tsmac_proc_read_txdesc_open(struct inode *inode, struct file *filp); +static int tsmac_proc_read_rxdesc_open(struct inode *inode, struct file *filp); + +/* register name string */ +static struct reg_mess { + int regnum; + char *desc_ptr; +} eth_reg_array[] = { + {0x0, "DMA Control"}, + {0x4, "Descriptor Init and Transmit Wakeup"}, + {0x8, "Transmit Desc Pointer, Chan 0"}, + {0xC, "Transmit Desc Pointer, Chan 1"}, + {0x14, "Transmit Polling Counter, Chan 0"}, + {0x18, "Transmit Polling Counter, Chan 1"}, + {0x1C, "Receive Desc Pointer"}, + {0x20, "IP Header Offset"}, + {0x24, "Interrupt Enable"}, + {0x28, "Interrupt Source"}, + {0x30, "Bad Address Read Error"}, + {0x34, "Bad Address Write Error"}, + {0x8008, "Transmit Control Frame Status"}, + {0x800C, "MAC Control"}, + {0x8010, "ARC Control"}, + {0x8014, "Transmit Control"}, + {0x8018, "Transmit Status"}, + {0x801C, "Receive Control"}, + {0x8020, "Receive Status"}, + {0x8024, "Station Management Data"}, + {0x8028, "Station Management Control and Address"}, + {0x802C, "ARC Address"}, + {0x8030, "ARC Data"}, + {0x8034, "ARC Enable"}, + {0x8038, "Maximum Length"}, + {0x8048, "Drop-On Threshold"}, + {0x804C, "Drop-Off Threshold"}, + {0x8050, "VQ Configuration"}, + {0x8054, "L2 Rule Enable"}, + {0x8058, "VLAN TCI Offset"}, + {0x8080, "Token Count VQ0"}, + {0x8084, "Token Count VQ1"}, + {0x8100, "Transmitted Good Frames"}, + {0x8104, "Transmitted Good Bytess"}, + {0x8108, "Received Good Frames"}, + {0x810C, "Received Good Bytes"}, + {0x8110, "Received Total Frames"}, + {0x8114, "Received Total Bytes"}, + {0x8118, "Received Overflowed Frames"}, + {0x811C, "Received Overflowed Bytes"}, + {0x8124, "Received Dropped Bytes"}, + {0x8128, "Received Dropped Frames VQ0"}, + {0x812C, "Received Dropped Frames VQ1"}, + {0x10000, "GPMII - Interrupt"}, + {0x10004, "GPMII - Interrupt Enable"}, + {0x10008, "GPMII - Interrupt Value"}, + {0x1000C, "GPMII - Config General"}, + {0x10010, "GPMII - Config Mode"}, + {0x10014, "GPMII - Config RX Override"}, + {0x10018, "GPMII - Config TX Override"}, + {0x1001C, "GPMII - Diagnostic Status"} +}; + +static char tsmac_usage_egressprio[] = + "Usage: echo <skb->priority> <high/low> > egressPriority\n"; + +static char tsmac_usage_default[] = + "Usage: echo <VQ> > default\n\n" "- VQ 0-1\n"; + +static char tsmac_usage_l2rule[] = + "Usage: echo <rule/enable> > macAddr\n" + " OR\n" + " echo <rule/enable> <VQ> <dest> <destMsk> <src> <srcMsk> > " + "macAddr\n\n" + "- rule number is 0-3, enable is 1 or 0\n" + "- VQ 0-1\n" + "- dest is the destination MAC address, eg 11:22:33:44:55:66\n" + "- destMsk 0 bits indicate which addresses bit to include, eg " + "00:00:00:00:00:00\n"; + +static char tsmac_usage_ethtypevlan[] = + "Usage: echo <TCI_offset> <VQ_of_prio0> <VQ_of_prio1> ... <VQ_of_prio7>" + " > ethTypeVlan\n\n" + "- TCI_offset is the byte offset of the VLAN TCI field from the start " + "of the packet\n" + "- TCI_offset 0-63\n" + "- VQ_of_prio0 is the VQ (0-1) for VLAN priority 0\n"; + +static char tsmac_usage_ethtypeip[] = + "Usage: echo <row> <VQa> <VQb> <VQc> <VQd> <VQe> <VQf> <VQg> <VQh>\n\n" + "- one row, 8 DSCPs, are edited at a time\n" + "- <row> is 0, 8, 16, 24, 32, 40, 48, or 56\n" + "- <VQa> is the VQ (0-1) for the first of 8 DSCPs\n"; + +static char tsmac_usage_ethtypeuser[] = + "Usage: echo <etherType/enable> <VQ> > ethTypeUser\n\n" + "- etherType is a 16-bit hexadecimal type, enable is 1 or 0\n" + "- VQ 0-1\n"; + +static char tsmac_usage_dropthreshold[] = + "Usage: echo <DropOff> <DropOn> > dropThres\n\n" + "- DropOff 0-8184, DropOn 4-8188\n" + "- each a multiple of 4-bytes, and DropOff < DropOn\n"; + +static char tsmac_usage_vq[] = + "Usage: echo <VQ/drop_disable> <size> > vq\n\n" + "- VQ is virtual queue number 0-1, drop_disable is 1 or 0\n" + "- size is packets 0-65535\n"; + +static char tsmac_usage_iphdroffset[] = + "Usage: echo <vlan_offset> <non_vlan_offset> > ipHeaderOffset\n\n" + "- vlan_offset is the byte offset of the VLAN header from the start " + "of the VLAN packet\n" + "- non_vlan_offset is the byte offset of the IP header from the start " + "of the non-VLAN packet\n"; + +static char tsmac_usage_reg[] = + "Usage: echo <reg_num> <reg_val> > reg\n\n" + "- reg_num is the TSMAC register number reported by 'cat reg'\n" + "- reg_val is the value to write to the register\n"; + +static char tsmac_usage_pause_arc[] = + "Usage: echo <addr> <val> > pauseARC\n\n" + "- addr is the PAUSE/ARC memory map address reported by " + "'cat pauseARC'\n" + "- addr needs to be in the range 0x00 - 0x84 and a multiple of 4\n" + "- val is the value to write to the address\n"; + +static char tsmac_usage_dump[] = + "Usage: echo <addr> <val> > dump\n\n" + "- addr is the classifier memory map address reported by " + "'cat dump'\n" + "- addr needs to be in the range 0x88 - 0x130 and a multiple of 4\n" + "- val is the value to write to the address\n"; + +static char tsmac_usage_desc_size[] = + "Usage: echo <sel> <new_size> > descSize\n\n" + "sel is the ID of the descriptor ring\n" + "0 for RX; 1 for TX (high prio); 2 for TX (low prio)\n" + "new_size is the new size of the descriptor ring\n"; + +/* procfs entries */ +struct tsmac_proc_dirs { + struct proc_dir_entry *eth_dir; + struct proc_dir_entry *qos_dir; + struct proc_dir_entry *classify_dir; + struct proc_dir_entry *provision_dir; +}; + +/* procfs entry directories */ +static struct tsmac_proc_dirs tsmac_proc[TSMAC_MAX_UNITS]; + +struct ethtool_ops tsmac_ethtool_ops = { + .get_drvinfo = tsmac_ethtool_get_drvinfo, + .get_settings = tsmac_ethtool_get_settings, + .set_settings = tsmac_ethtool_set_settings, + .get_pauseparam = tsmac_ethtool_get_pauseparam, + .set_pauseparam = tsmac_ethtool_set_pauseparam, + .get_link = ethtool_op_get_link, +}; + +const struct file_operations txdesc_fops = { + .owner = THIS_MODULE, + .open = tsmac_proc_read_txdesc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +const struct file_operations rxdesc_fops = { + .owner = THIS_MODULE, + .open = tsmac_proc_read_rxdesc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * ETHTOOLS support for GET DRVINFO + */ +static void tsmac_ethtool_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, cardname); + strcpy(info->version, version); + strcpy(info->fw_version, drv_version); + strcpy(info->bus_info, "GMII"); +} + +/* + * ETHTOOLS support for GET SETTINGS + * NB: We only support MoCA as this time. + */ +static int tsmac_ethtool_get_settings(struct net_device *dev, + struct ethtool_cmd *cmd) +{ + struct tsmac_private *lp = netdev_priv(dev); + /* printk(KERN_DEBUG "%s\n", __func__); */ + + if (lp->conn_type == MSP_CT_MOCA) { + cmd->speed = lp->speed; + cmd->duplex = lp->duplex; + cmd->supported = SUPPORTED_100baseT_Full; + cmd->port = PORT_TP; + cmd->phy_address = 1; + cmd->transceiver = XCVR_INTERNAL; + cmd->autoneg = AUTONEG_DISABLE; + } else if (lp->conn_type == MSP_CT_ETHSWITCH) { + cmd->speed = lp->speed; + cmd->duplex = lp->duplex; + cmd->supported = SUPPORTED_1000baseT_Full | SUPPORTED_MII; + cmd->port = PORT_MII; + cmd->phy_address = lp->phy_addr; + cmd->transceiver = XCVR_EXTERNAL; + cmd->autoneg = AUTONEG_DISABLE; + } else if (lp->conn_type == MSP_CT_ETHPHY) { + return phy_ethtool_gset(lp->phyptr, cmd); + } else { + return -EFAULT; + } + return 0; +} + +/* + * ETHTOOLS support for SET SETTINGS + * NB: We only support MoCA as this time. + */ +static int tsmac_ethtool_set_settings(struct net_device *dev, + struct ethtool_cmd *cmd) +{ + struct tsmac_private *lp = netdev_priv(dev); + /* printk(KERN_DEBUG "%s\n", __func__); */ + + if ((lp->conn_type == MSP_CT_MOCA) || + (lp->conn_type == MSP_CT_ETHSWITCH)) { + /* Nothing to do for MoCA, but must return success */ + return 0; + } else if (lp->conn_type == MSP_CT_ETHPHY) { + return phy_ethtool_sset(lp->phyptr, cmd); + } else { + /* We only support MoCA */ + return -EFAULT; + } +} + +/* + * ETHTOOLS support for GET PAUSE PARAM + */ +static void tsmac_ethtool_get_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct tsmac_private *lp = netdev_priv(dev); + /* printk(KERN_DEBUG "%s\n", __func__); */ + + if (lp->flow_ctl.enable) { + pause->rx_pause = 1; + pause->tx_pause = 1; + } else { + pause->rx_pause = 0; + pause->tx_pause = 0; + } +} + +/* + * ETHTOOLS support for SET PAUSE PARAM + * NB: We only support MoCA as this time. + */ +static int tsmac_ethtool_set_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct tsmac_private *lp = netdev_priv(dev); + /* printk(KERN_DEBUG "%s\n", __func__); */ + + if (lp->conn_type == MSP_CT_MOCA) { + /* Nothing to do for MoCA, but must return success */ + return 0; + } else { + /* We only support MoCA */ + return -EFAULT; + } +} + +/* + * Read the options that were defined while compiling the driver + */ +static int tsmac_proc_read_info(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + struct net_device *dev = data; + + /* if offset is not zero stop reading */ + if (off > 0) + return 0; + +#ifdef CONFIG_DESC_ALL_DSPRAM + len += sprintf(buffer + len, "All descriptors on DSPRAM\n"); +#else + len += sprintf(buffer + len, "All descriptors on DDR (offchip)\n"); +#endif + +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE + len += sprintf(buffer + len, "CONFIG_TSMAC_LINELOOPBACK_FEATURE=y\n"); +#else + len += sprintf(buffer + len, "CONFIG_TSMAC_LINELOOPBACK_FEATURE is " + "not set\n"); +#endif + +#ifdef TSMAC_DEBUG + len += sprintf(buffer + len, "TSMAC_DEBUG=y\n"); +#else + len += sprintf(buffer + len, "TSMAC_DEBUG is not set\n"); +#endif + len += sprintf(buffer + len, "MTU=%d\n", dev->mtu); + return len; +} + +/* + * Read the connection type of an interface + */ +static int tsmac_proc_read_conntype(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + + if (off > 0) + return 0; + + return sprintf(buffer, "%s\n", msp_conntype_str[lp->conn_type]); +} + +static int tsmac_proc_write_conntype(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + char kernel_buffer[KERN_BUF_MAX_SIZE]; + enum msp_conntype_enum conn_type = MSP_CT_MAX; + u32 ret = 0; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + memset(kernel_buffer, 0, KERN_BUF_MAX_SIZE); + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(%s): copy_from_user failed\n", + __func__); + goto err_usage; + } + + if (sscanf(kernel_buffer, "%s", kernel_buffer) != 1) { + goto err_usage; + }; + + for (conn_type = 0; conn_type < MSP_CT_MAX; conn_type++) { + if (!strcmp(kernel_buffer, msp_conntype_str[conn_type])) { + /* apply the new connection type setting + to the interface */ + ret = tsmac_set_conntype(dev, conn_type); + if (!ret) { + printk(KERN_INFO "The following settings " + "will be applied to %s:\n", dev->name); + printk(KERN_INFO " Connection Type : %s\n", + msp_conntype_str[conn_type]); + printk(KERN_INFO " MII Type : %s\n", + tsmac_mii_type_str[lp->mii_type]); + printk(KERN_INFO " PHY Address : %d:%d\n", + lp->bus_unit, lp->phy_addr); + } + return count; + } + } + +err_usage: + if (conn_type == MSP_CT_MAX) { + printk(KERN_ERR "Usage: echo <type> > connType\n\n"); + printk(KERN_ERR "Change the connection" + "type of an interface\n\n"); + printk(KERN_ERR "type:\n"); + for (conn_type = 0; conn_type < MSP_CT_MAX; conn_type++) + printk(KERN_ERR "\t%s\n", msp_conntype_str[conn_type]); + } + + return count; +} + +/* + * Read the PHY address + */ +static int tsmac_proc_read_phyaddr(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + int len = 0; + struct tsmac_private *attached; + + if ((lp->conn_type != MSP_CT_ETHPHY) && + (lp->conn_type != MSP_CT_ETHSWITCH)) { + len += sprintf(buffer + len, "Not supported for this " + "Connection Type\n"); + return len; + } + + if (lp->phyptr) { + attached = netdev_priv(lp->phyptr->attached_dev); + len += sprintf(buffer + len, "%d:%d\n", + attached->unit, lp->phyptr->addr); + len += sprintf(buffer + len, "Status: attached\n"); + } else { + len += sprintf(buffer + len, "%d:%d\n", lp->bus_unit, + lp->phy_addr); + len += sprintf(buffer + len, "Status: detached\n"); + } + + return len; +} + +/* + * Write the PHY address + */ +static int tsmac_proc_write_phyaddr(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + int phyunit, phyaddr; + int ret = 0; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + memset(kernel_buffer, 0, KERN_BUF_MAX_SIZE); + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(%s): copy_from_user failed\n", + __func__); + goto err_usage; + } + + if (sscanf(kernel_buffer, "%d:%d", &phyunit, &phyaddr) != 2) { + goto err_usage; + }; + + ret = tsmac_set_phyaddr(dev, phyunit, phyaddr); + return count; + +err_usage: + printk(KERN_ERR "Usage: echo <unit:addr> > phyAddr\n\n"); + printk(KERN_ERR "Change the PHY address of an interface\n\n"); + return count; +} + +/* + * Read the MII interface type + */ +static int tsmac_proc_read_miitype(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + + return sprintf(buffer, "%s\n", tsmac_mii_type_str[lp->mii_type]); +} + +static int tsmac_proc_write_miitype(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + enum tsmac_mii_type_enum mii_type = TSMAC_MT_MAX; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + memset(kernel_buffer, 0, KERN_BUF_MAX_SIZE); + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(%s): copy_from_user failed\n", + __func__); + goto err_usage; + } + + if (sscanf(kernel_buffer, "%s", kernel_buffer) != 1) { + goto err_usage; + }; + + for (mii_type = 0; mii_type < TSMAC_MT_MAX; mii_type++) { + if (!strcmp(kernel_buffer, tsmac_mii_type_str[mii_type])) { + /* apply the new MII type setting to the interface */ + tsmac_set_mii_type(dev, mii_type); + return count; + } + } + +err_usage: + if (mii_type == TSMAC_MT_MAX) { + printk(KERN_ERR "Usage: echo <type> > miiType\n\n"); + printk(KERN_ERR "Change the MII type of an interface\n\n"); + printk(KERN_ERR "type:\n"); + for (mii_type = 0; mii_type < TSMAC_MT_MAX; mii_type++) + printk(KERN_ERR "\t%s\n", + tsmac_mii_type_str[mii_type]); + } + + return count; +} + +/* + * Read the link mode + */ +static int tsmac_proc_read_linkmode(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + int len = 0; + + len += sprintf(buffer + len, "MAC link status : %d%s\n", + (lp->speed == SPEED_1000) ? 1000 : + ((lp->speed == SPEED_100) ? 100 : 10), + (lp->duplex) ? "Full" : "Half"); + return len; +} + +/* + * Read the CPU for running non-datapath timer/background tasks + */ +static int tsmac_proc_read_taskcpu(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + int len = 0; + + len += sprintf(buffer + len, + "CPU for running non-datapath timer/background tasks: " + "%d\n", atomic_read(&lp->timer_task_cpu)); + return len; +} + +static int tsmac_proc_write_taskcpu(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + char kernel_buffer[KERN_BUF_MAX_SIZE]; + unsigned int cpu = 0; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + memset(kernel_buffer, 0, KERN_BUF_MAX_SIZE); + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(%s): copy_from_user failed\n", + __func__); + goto err_usage; + } + + if (sscanf(kernel_buffer, "%d", &cpu) != 1) + goto err_usage; + + if (cpu < num_possible_cpus()) { + atomic_set(&lp->timer_task_cpu, cpu); + return count; + } + +err_usage: + printk(KERN_ERR "Usage <cpu> > taskCpu\n"); + printk(KERN_ERR "Change the CPU for running non-datapath" + "timer/background tasks.\n"); + printk(KERN_ERR "cpu: 0 - %d\n", num_possible_cpus() - 1); + + return count; +} + +static int tsmac_proc_read_mii(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + int len = 0; + struct tsmac_private *attached; + + /* if offset is not zero stop reading */ + if (off > 0) + return 0; + + if (lp->conn_type == MSP_CT_ETHPHY) { + if (lp->phyptr == NULL) { + printk(KERN_ERR "TSMAC: PHY pointer is NULL!\n"); + return 0; + } + attached = netdev_priv(lp->phyptr->attached_dev); + len += sprintf(buffer, "phyaddr%d %d:%d\n", lp->unit, + attached->unit, lp->phyptr->addr); + len += sprintf(buffer + len, "00 0x%04x Control\n", + phy_read(lp->phyptr, MII_BMCR)); + len += sprintf(buffer + len, "01 0x%04x Status\n", + phy_read(lp->phyptr, MII_BMSR)); + len += sprintf(buffer + len, "02 0x%04x PHY ID 1\n", + phy_read(lp->phyptr, MII_PHYSID1)); + len += sprintf(buffer + len, "03 0x%04x PHY ID 2\n", + phy_read(lp->phyptr, MII_PHYSID2)); + len += sprintf(buffer + len, "04 0x%04x Auto-Neg " + "Advertisement\n", + phy_read(lp->phyptr, MII_ADVERTISE)); + len += sprintf(buffer + len, "05 0x%04x Auto-Neg Link " + "Partner Ability\n", + phy_read(lp->phyptr, MII_LPA)); + } else + len += sprintf(buffer + len, "Not supported for this " + "Connection Type\n"); + return len; +} + +/* find the no. of args */ +static int str_arg_count(const char *s, unsigned long count) +{ + char tmpbuf[KERN_BUF_MAX_SIZE]; + char *tmpbufp = tmpbuf; + int arg_no = 0; + + if (count > KERN_BUF_MAX_SIZE) + return 0; + + memcpy(tmpbuf, s, count); + while (strsep(&tmpbufp, " ")) + arg_no++; + + return arg_no; +} + +static int tsmac_proc_read_macaddr(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + struct net_device *dev = data; + + /* if offset is not zero stop reading */ + if (off > 0) + return 0; + + /* copy the MAC address stored in the device object to the buffer */ + len += sprintf(buffer + len, "%02x:%02x:%02x:%02x:%02x:%02x", + dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + + /* add newline at end of the string */ + len += sprintf(buffer + len, "\n"); + + return len; +} + +static int tsmac_proc_write_macaddr(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + + /* kernel space buffer */ + char kernel_buffer[KERN_BUF_MAX_SIZE]; + struct sockaddr addr; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + /* copy the user space data to the kernel space buffer */ + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_macaddr): " + "copy_from_userfailed\n"); + return -EFAULT; + } + + /* parse the individual bytes from the buffer */ + if (sscanf(kernel_buffer, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &addr.sa_data[0], &addr.sa_data[1], + &addr.sa_data[2], &addr.sa_data[3], + &addr.sa_data[4], &addr.sa_data[5]) != 6) { + printk(KERN_WARNING "ERROR: Invalid argument count\n"); + return count; + } + + /* now change the MAC address of the interface */ + if (!tsmac_set_mac_addr(dev, &addr)) + return count; + else + return -EBUSY; +} + +static int tsmac_proc_read_txdesc(struct seq_file *f, void *v) +{ + struct net_device *dev = (struct net_device *)f->private; + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_tx *tx; + struct Q_Desc *desc; + u32 flags; + u32 status; + u32 index; + u32 qnum; + + if (&lp->tx[0].desc_p[0] == NULL) { + seq_printf(f, "%s has not been configured\n", dev->name); + return 0; + } + + seq_printf(f, "Ethernet interface: %s\nNetwork carrier state: %s\n", + (netif_running(dev)) ? "enabled" : "disabled", + (netif_carrier_ok(dev)) ? "connected" : "not connected"); + + for (qnum = 0; qnum < TSMAC_NUM_TX_CH; qnum++) { + tx = &lp->tx[qnum]; + seq_printf(f, "CHAN %d\n", qnum); + seq_printf(f, "Tx descriptor base=%p, ring size=%d, " + "free space=%d\n", tx->desc_base, tx->size, + tx->size - tx->qcnt); + seq_printf(f, "head index [%d], tail index [%d]\n", + tx->head, tx->tail); + + for (index = 0; index < tx->size; index++) { + desc = &tx->desc_p[index]; + seq_printf(f, "[%3d] %p NextDescrPtr=0x%x " + "BuffDataPtr=0x%x " + "skb=%p\n", + index, desc, desc->FDNext, + desc->FDBuffPtr, tx->skb_pp[index]); + flags = desc->FDCtl; + status = desc->FDStat; + seq_printf(f, " DMAOWN=%d len=%d Flags=0x%x, " + "Status=0x%x PAUSED=%d GOOD=%d\n", + (flags & FD_DMA_Own) ? 1 : 0, + flags & FD_RxBuffLn_Mask, + flags, status, + (status & Tx_Paused) ? 1 : 0, + (status & Tx_Good) ? 1 : 0); + } + } + return 0; +} + +static int tsmac_proc_read_rxdesc(struct seq_file *f, void *v) +{ + struct net_device *dev = f->private; + struct tsmac_private *lp = netdev_priv(dev); + struct Q_Desc *desc; + u32 index; + u32 flags; + u32 status; + int good; + + if (&lp->rx.desc_p[0] == NULL) { + seq_printf(f, "%s has not been configured\n", dev->name); + return 0; + } + + seq_printf(f, "Ethernet interface: %s\nNetwork carrier state: %s\n", + (netif_running(dev)) ? "enabled" : "disabled", + (netif_carrier_ok(dev)) ? "connected" : "not connected"); + + seq_printf(f, "Rx descriptor base=%p, ring size=%d, current ring " + "index=%d\n", lp->rx.desc_base, lp->rx.size, lp->rx.index); + + for (index = 0; index < lp->rx.size; index++) { + desc = &lp->rx.desc_p[index]; + seq_printf(f, "[%3d] %p NextDescrPtr=0x%x BuffDataPtr=0x%x " + "skb=%p skb->data=%p\n", index, desc, + desc->FDNext, desc->FDBuffPtr, + lp->rx.skb_pp[index], lp->rx.skb_pp[index]->data); + flags = desc->FDCtl; + status = desc->FDStat; + good = status & Rx_Good; + seq_printf(f, " DMAOWN=%d TRUNC=%d len=%d Flags=0x%x, " + "Status=0x%x GOOD=%d", + (flags & FD_DMA_Own) ? 1 : 0, + (flags & FD_RxTrunc) ? 1 : 0, + flags & FD_RxBuffLn_Mask, flags, status, + (good) ? 1 : 0); + if (good) + seq_printf(f, "\n"); + else { + seq_printf(f, " OVERFLOW=%d CRCERR=%d LENERR=%d\n", + (status & Rx_OverFlow) ? 1 : 0, + (status & Rx_CRCErr) ? 1 : 0, + (status & Rx_LenErr) ? 1 : 0); + } + } + return 0; +} + +static int tsmac_proc_read_stats(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + int len = 0; + + if (off > 0) + return 0; + + len += sprintf(buffer + len, "\nSoftware Counters\n\n"); + + len += sprintf(buffer + len, "RX Stats\n"); + /* RX packets */ + len += sprintf(buffer + len, "RX Packets: %lu\n", + lp->lx_stats.rx_packets); + /* RX multicast packets */ + len += sprintf(buffer + len, "RX Multicast Packets: %lu\n", + lp->lx_stats.multicast); + /* RX bytes */ + len += sprintf(buffer + len, "RX Bytes: %llu\n", + lp->sw_stats.rx_bytes); + /* RX packets in each VQ */ + len += sprintf(buffer + len, "RX VQ0: %u\n", + lp->sw_stats.rx_vq_frames[0]); + len += sprintf(buffer + len, "RX VQ1: %u\n", + lp->sw_stats.rx_vq_frames[1]); + len += sprintf(buffer + len, "RX VQ2: %u\n", + lp->sw_stats.rx_vq_frames[2]); + len += sprintf(buffer + len, "RX VQ3: %u\n", + lp->sw_stats.rx_vq_frames[3]); + len += sprintf(buffer + len, "RX VQ4: %u\n", + lp->sw_stats.rx_vq_frames[4]); + len += sprintf(buffer + len, "RX VQ5: %u\n", + lp->sw_stats.rx_vq_frames[5]); + len += sprintf(buffer + len, "RX VQ6: %u\n", + lp->sw_stats.rx_vq_frames[6]); + len += sprintf(buffer + len, "RX VQ7: %u\n", + lp->sw_stats.rx_vq_frames[7]); + len += sprintf(buffer + len, "\n"); + + len += sprintf(buffer + len, "RX Error Stats\n"); + /* RX error packets */ + len += sprintf(buffer + len, "RX Errors: %lu\n", + lp->lx_stats.rx_errors); + /* RX long error */ + len += sprintf(buffer + len, "RX Long Errors: %u\n", + lp->sw_stats.rx_long_errors); + /* RX packet length error */ + len += sprintf(buffer + len, "RX Length Errors: %lu\n", + lp->lx_stats.rx_length_errors); + /* RX packet alignment error */ + len += sprintf(buffer + len, "RX Align Errors: %lu\n", + lp->lx_stats.rx_frame_errors); + /* RX packet CRC errors */ + len += sprintf(buffer + len, "RX CRC Errors: %lu\n", + lp->lx_stats.rx_crc_errors); + /* RX packet truncated errors */ + len += sprintf(buffer + len, "RX Truncated: %u\n", + lp->sw_stats.rx_trunc_errors); +#ifdef CONFIG_TSMAC_TEST_CMDS + /* RX incorrect L4 checksum on VLAN packets */ + len += sprintf(buffer + len, "RX Chksum Errors (VLAN): %u\n", + lp->sw_stats.rx_nochksum_vlan); + /* RX incorrect L4 checksum on non-VLAN packets */ + len += sprintf(buffer + len, "RX Chksum Errors (non-VLAN): %u\n", + lp->sw_stats.rx_nochksum_nonvlan); +#endif + len += sprintf(buffer + len, "\n"); + + len += sprintf(buffer + len, "RX Util Stats\n"); + /* RX interrupts */ + len += sprintf(buffer + len, "RX Interrupts: %u\n", + lp->sw_stats.rx_ints); + /* RX descriptor ring exhausted */ + len += sprintf(buffer + len, "RX Exhausted: %lu\n", + lp->lx_stats.rx_fifo_errors); + /* RX dropped packets due to out of memory */ + len += sprintf(buffer + len, "RX Dropped Packets (NOMEM): %lu\n", + lp->lx_stats.rx_dropped); + len += sprintf(buffer + len, "\n"); + + len += sprintf(buffer + len, "TX Stats\n"); + /* TX packets */ + len += sprintf(buffer + len, "TX Packets: %lu\n", + lp->lx_stats.tx_packets); + /* TX bytes */ + len += sprintf(buffer + len, "TX Bytes: %llu\n", + lp->sw_stats.tx_bytes); + len += sprintf(buffer + len, "\n"); + + len += sprintf(buffer + len, "TX Error Stats\n"); + /* TX error packets */ + len += sprintf(buffer + len, "TX Errors: %lu\n", + lp->lx_stats.tx_errors); + len += sprintf(buffer + len, "\n"); + + len += sprintf(buffer + len, "TX Util Stats\n"); + /* TX interrupts */ + len += sprintf(buffer + len, "TX Interrupts: %u\n", + lp->sw_stats.tx_ints); + /* TX full */ + len += sprintf(buffer + len, "TX Full (high priority): %u\n", + lp->sw_stats.tx_full[TSMAC_DESC_PRI_HI]); + len += sprintf(buffer + len, "TX Full (low priority): %u\n", + lp->sw_stats.tx_full[TSMAC_DESC_PRI_LO]); + /* TX collisions */ + len += sprintf(buffer + len, "TX Collisions: %lu\n", + lp->lx_stats.collisions); + len += sprintf(buffer + len, "TX Dropped Packets: %lu\n", + lp->lx_stats.tx_dropped); + len += sprintf(buffer + len, "\n"); + + len += sprintf(buffer + len, "Hardware Counters\n\n"); + + /* accumulate counters from device */ + tsmac_update_hw_stats(dev); + + len += sprintf(buffer + len, "RX Stats\n"); + /* RX packets */ + len += sprintf(buffer + len, "RX Packets: %u\n", + lp->hw_stats.rx_packets); + /* RX bytes */ + len += sprintf(buffer + len, "RX Bytes: %llu\n", + lp->hw_stats.rx_bytes); + len += sprintf(buffer + len, "\n"); + + len += sprintf(buffer + len, "RX Error Stats\n"); + /* RX FIFO overflows */ + len += sprintf(buffer + len, "RX FIFO Overflow: %lu\n", + lp->lx_stats.rx_over_errors); + /* RX dropped bytes */ + len += sprintf(buffer + len, "RX Dropped Bytes: %llu\n", + lp->hw_stats.rx_dropped_bytes); + /* RX VQ dropped packets */ + len += sprintf(buffer + len, "RX Drop VQ0: %u\n", + lp->hw_stats.rx_vq_drops[0]); + len += sprintf(buffer + len, "RX Drop VQ1: %u\n", + lp->hw_stats.rx_vq_drops[1]); + len += sprintf(buffer + len, "RX Drop VQ2: %u\n", + lp->hw_stats.rx_vq_drops[2]); + len += sprintf(buffer + len, "RX Drop VQ3: %u\n", + lp->hw_stats.rx_vq_drops[3]); + len += sprintf(buffer + len, "RX Drop VQ4: %u\n", + lp->hw_stats.rx_vq_drops[4]); + len += sprintf(buffer + len, "RX Drop VQ5: %u\n", + lp->hw_stats.rx_vq_drops[5]); + len += sprintf(buffer + len, "RX Drop VQ6: %u\n", + lp->hw_stats.rx_vq_drops[6]); + len += sprintf(buffer + len, "RX Drop VQ7: %u\n", + lp->hw_stats.rx_vq_drops[7]); + len += sprintf(buffer + len, "\n"); + + len += sprintf(buffer + len, "TX Stats\n"); + len += sprintf(buffer + len, "TX Packets: %u\n", + lp->hw_stats.tx_packets); + len += sprintf(buffer + len, "TX Bytes: %llu\n", + lp->hw_stats.tx_bytes); + + return len; +} + +static int tsmac_proc_write_stats(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + + /* clear counters in device */ + tsmac_update_hw_stats(dev); + + /* clear software counters */ + memset(&lp->lx_stats, 0, sizeof(lp->lx_stats)); + memset(&lp->sw_stats, 0, sizeof(lp->sw_stats)); + memset(&lp->hw_stats, 0, sizeof(lp->hw_stats)); + + return count; +} + +static int tsmac_proc_read_reg(char *buf, char **bloc, off_t off, int length, + int *eof, void *data) +{ + int len = 0; + u32 i; + u32 regnum; + u32 regval; + struct reg_mess *regp; + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + + /* finished reading regardless of anything else */ + if (off > 0) + return 0; + + /* print each register in the TSMAC subsystem */ + regp = eth_reg_array; + for (i = 0; i < TSMAC_REG_COUNT; ++i, ++regp) { + regnum = regp->regnum; + regval = tsmac_read((void *)((u32) lp->reg_map + regnum)); + len += sprintf(buf + len, "0x%05x 0x%08x %s\n", + regnum, regval, regp->desc_ptr); + } + +#if !defined(CONFIG_PMC_MSP7140_FPGA) + /* and the Clock Manager register edited by the driver */ + regnum = TSMAC_CTRL_OUTPUT; + regval = tsmac_read((void *)regnum); + len += sprintf(buf + len, "0x%05x 0x%08x %s\n", + regnum, regval, "CLK_MGR_MSP - TSMAC Control Output"); + + /* MAC C output control register */ + regnum = TSMAC_MAC_C_OUTPUT_CTRL; + regval = tsmac_read((void *)regnum); + len += sprintf(buf + len, "0x%05x 0x%08x %s\n", + regnum, regval, "CLK_MGR_MSP - MAC C Output Control"); +#endif + return len; +} + +static int tsmac_proc_write_reg(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + struct reg_mess *regp; + u32 regnum, regval; + int i, args; + + /* kernel space buffer */ + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + /* copy the user space data to the kernel space buffer */ + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "copy_from_user failed\n"); + return -EFAULT; + } + + /* IF wrong # of arguments */ + args = sscanf(kernel_buffer, "%x %x", ®num, ®val); + if ((args != 1) && (args != 2)) { + printk(KERN_WARNING "%s", tsmac_usage_reg); + return count; + } + + /* validate register num */ + regp = eth_reg_array; + for (i = 0; i < TSMAC_REG_COUNT; ++i, ++regp) { + if (regnum == regp->regnum) + break; + } + if (i >= TSMAC_REG_COUNT) { + printk(KERN_WARNING "%s", tsmac_usage_reg); + return count; + } + + /* IF only register number provided */ + if (args == 1) { + /* print 1 register */ + regval = tsmac_read((void *)((u32) lp->reg_map + regnum)); + printk(KERN_INFO "0x%05x 0x%08x %s\n", + regnum, regval, regp->desc_ptr); + return count; + } + + /* edit register */ + tsmac_write(regval, (void *)((u32) lp->reg_map + regnum)); + + return count; +} + +static int tsmac_proc_read_pause_arc(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + + if (off > 0) + return 0; + + return tsmac_print_map_pause_arc(lp, buffer); +} + +static int tsmac_proc_write_pause_arc(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + char kernel_buffer[KERN_BUF_MAX_SIZE]; + u32 addr, val; + int args; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "copy_from_user failed\n"); + return -EFAULT; + } + + if (!netif_running(dev)) { + printk(KERN_WARNING "Interface is currently down!\n"); + return count; + } + + /* wrong # of arguments */ + args = sscanf(kernel_buffer, "%x %x", &addr, &val); + if (args != 2) { + printk(KERN_WARNING "%s", tsmac_usage_pause_arc); + return count; + } + + /* validate memory address */ + if (addr > 0x84 || ((addr & 0x3) != 0)) { + printk(KERN_WARNING "%s", tsmac_usage_pause_arc); + return count; + } + + /* load the word into memory */ + tsmac_write(addr, &lp->reg_map->mac.arc_addr); + tsmac_write(val, &lp->reg_map->mac.arc_data); + + return count; +} + +static int tsmac_proc_read_desc_size(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + + if (off > 0) + return 0; + + len += sprintf(buffer, "ID\tDescriptor\tSize\n"); + len += sprintf(buffer + len, "0\tRX\t\t%u\n", lp->rx.size); + len += sprintf(buffer + len, "1\tTX - Hi\t\t%u\n", lp->tx[0].size); + len += sprintf(buffer + len, "2\tTX - Lo\t\t%u\n", lp->tx[1].size); + + return len; +} + +static int tsmac_proc_write_desc_size(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + char kernel_buffer[KERN_BUF_MAX_SIZE]; + int sel; + unsigned int new_size; + int args; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "copy_from_user failed\n"); + return -EFAULT; + } + + if (netif_running(dev)) { + printk(KERN_WARNING "\nInterface is currently up! Descriptor " + "ring size can only be changed when the interface is " + "down\n"); + return count; + } + + /* wrong # of arguments */ + args = sscanf(kernel_buffer, "%d %u", &sel, &new_size); + if (args != 2) { + printk(KERN_WARNING "%s", tsmac_usage_desc_size); + return count; + } + + /* validate descriptor ring size */ + if (new_size > MAX_RING_SIZE || new_size < MIN_RING_SIZE) { + printk(KERN_WARNING "%s", tsmac_usage_desc_size); + return count; + } + + switch (sel) { + case 0: /* for RX descriptor ring size */ + lp->rx.size = new_size; + break; + + case 1: /* for TX - high priority */ + lp->tx[0].size = new_size; + break; + + case 2: /* for TX - low priority */ + lp->tx[1].size = new_size; + break; + + default: + break; + } + + printk(KERN_WARNING "\n\nWARNING: Setting the descriptor ring too " + "large on multiple interfaces might cause the system " + "running out of scratch pad ram\n"); + + return count; +} + +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE +static int tsmac_proc_read_lineloopback(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + u8 loopback; + + int len = 0; + + /* if offset is not zero stop reading */ + if (off > 0) + return 0; + + iodata.data = &loopback; + ifr.ifr_data = &iodata; + + tsmac_get_loopback(dev, &ifr, TSMAC_KERNEL_DATA); + + len += sprintf(buffer, "%d\n", loopback); + + return len; +} + +static int tsmac_proc_write_lineloopback(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + u8 loopback; + + /* kernel space buffer */ + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + /* copy the user space data to the kernel space buffer */ + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_lineloopback): " + "copy_from_user failed\n"); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%hhu", &loopback) != 1) { + printk(KERN_WARNING "ERROR: Invalid argument count\n"); + return count; + } + + iodata.data = &loopback; + ifr.ifr_data = &iodata; + + tsmac_set_loopback(dev, &ifr, TSMAC_KERNEL_DATA); + + return count; +} +#endif + +/* + * Read the IP header offset register + */ +static int tsmac_proc_read_iphdroffset(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + u32 reg; + u8 offset_vlan; + u8 offset_nvlan; + int len = 0; + + /* if offset is not zero stop reading */ + if (off > 0) + return 0; + + reg = tsmac_read(&lp->reg_map->dma.iphdr_offset); + + offset_vlan = (reg & DMA_OffsetVLAN_Mask) >> DMA_OffsetVLAN_Shift; + offset_nvlan = reg & DMA_OffsetNonVLAN_Mask; + + len += sprintf(buffer + len, "IP Header Offset for VLAN packets: %u\n", + offset_vlan); + len += sprintf(buffer + len, "IP Header Offset for non-VLAN packets:" + " %u\n", offset_nvlan); + return len; +} + +/* + * Configure the IP header offset register + */ +static int tsmac_proc_write_iphdroffset(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + u8 offset_vlan; + u8 offset_nvlan; + + /* kernel space buffer */ + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + /* copy the user space data to the kernel space buffer */ + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_iphdroffset): " + "copy_from_user failed\n"); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%hhu %hhu", &offset_vlan, + &offset_nvlan) != 2) { + printk(KERN_WARNING "%s", tsmac_usage_iphdroffset); + return count; + } + + lp->iphdr_offset.vlan = offset_vlan; + lp->iphdr_offset.nvlan = offset_nvlan; + + tsmac_write((offset_vlan << DMA_OffsetVLAN_Shift) | offset_nvlan, + &lp->reg_map->dma.iphdr_offset); + + return count; +} + +static int tsmac_proc_read_egressprio(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + enum tsmac_egress_prio egress_prio[TSMAC_NUM_SKB_PRIORITY]; + int i; + int len = 0; + + if (off > 0) + return 0; + + iodata.data = egress_prio; + ifr.ifr_data = &iodata; + + tsmac_get_egress_prio(dev, &ifr, TSMAC_KERNEL_DATA); + + len += sprintf(buffer, "skb->priority\tEgress Queue\n"); + for (i = 0; i < TSMAC_NUM_SKB_PRIORITY; i++) { + len += sprintf(buffer + len, "\t%d\t%s\n", i, egress_prio[i] == + TSMAC_DESC_PRI_HI ? "high" : "low"); + } + return len; +} /* tsmac_proc_read_egressprio() */ + +static int tsmac_proc_write_egressprio(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + enum tsmac_egress_prio egress_prio_new[2]; + char priority[12]; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(%s): copy_from_user failed\n", + __func__); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%d %s", (int *)&egress_prio_new[0], + priority) != 2) { + printk(KERN_WARNING "%s", tsmac_usage_egressprio); + return count; + } + + /* validate skb->priority number */ + if (egress_prio_new[0] >= TSMAC_NUM_SKB_PRIORITY) { + printk(KERN_WARNING "%s", tsmac_usage_egressprio); + return count; + } + + if (!strcmp(priority, "high")) + egress_prio_new[1] = TSMAC_DESC_PRI_HI; + else + egress_prio_new[1] = TSMAC_DESC_PRI_LO; + + iodata.data = egress_prio_new; + ifr.ifr_data = &iodata; + tsmac_set_egress_prio(dev, &ifr, TSMAC_KERNEL_DATA); + return count; +} /* tsmac_proc_write_egressprio() */ + +static int tsmac_proc_read_default(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + u8 default_vq; + int len = 0; + + if (off > 0) + return 0; + + iodata.data = &default_vq; + ifr.ifr_data = &iodata; + + tsmac_get_default_vq_map(dev, &ifr, TSMAC_KERNEL_DATA); + + len += sprintf(buffer, "Default VQ is %d\n", default_vq); + + return len; +} + +static int tsmac_proc_write_default(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + u8 default_vq; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_default): " + "copy_from_user failed\n"); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%hhu", &default_vq) != 1) { + printk(KERN_WARNING "%s", tsmac_usage_default); + return count; + } + + if (default_vq >= VQ_MAX) { + printk(KERN_WARNING "%s", tsmac_usage_default); + return count; + } + + iodata.data = &default_vq; + ifr.ifr_data = &iodata; + + tsmac_set_default_vq_map(dev, &ifr, TSMAC_KERNEL_DATA); + + return count; +} + +static int tsmac_proc_read_l2rule(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_l2_class_rule l2rules[4]; + int len = 0; + int i; + + if (off > 0) + return 0; + + iodata.data = l2rules; + ifr.ifr_data = &iodata; + + tsmac_get_addr_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + len += sprintf(buffer, + "R/E Q Dest Addr\t\tDest Mask\t\tSource Addr\t\t" + "Source Mask\n"); + + for (i = 0; i < 4; i++) { + len += sprintf(buffer + len, "%d/%d %d ", + l2rules[i].rule_num, l2rules[i].enable, + l2rules[i].vqnum); + len += sprintf(buffer + len, "%02x:%02x:%02x:%02x:%02x:%02x\t", + l2rules[i].DA[0], l2rules[i].DA[1], + l2rules[i].DA[2], l2rules[i].DA[3], + l2rules[i].DA[4], l2rules[i].DA[5]); + len += sprintf(buffer + len, "%02x:%02x:%02x:%02x:%02x:%02x\t", + l2rules[i].DM[0], l2rules[i].DM[1], + l2rules[i].DM[2], l2rules[i].DM[3], + l2rules[i].DM[4], l2rules[i].DM[5]); + len += sprintf(buffer + len, "%02x:%02x:%02x:%02x:%02x:%02x\t", + l2rules[i].SA[0], l2rules[i].SA[1], + l2rules[i].SA[2], l2rules[i].SA[3], + l2rules[i].SA[4], l2rules[i].SA[5]); + len += sprintf(buffer + len, "%02x:%02x:%02x:%02x:%02x:%02x\n", + l2rules[i].SM[0], l2rules[i].SM[1], + l2rules[i].SM[2], l2rules[i].SM[3], + l2rules[i].SM[4], l2rules[i].SM[5]); + } + return len; +} + +static int tsmac_proc_write_l2rule(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_l2_class_rule cfg; + int arg_no; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + /* copy the user space data to the kernel space buffer */ + if ((count > KERN_BUF_MAX_SIZE) || + copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_l2rule): copy_from_user" + " failed\n"); + return -EFAULT; + } + + /* find the no. of args */ + arg_no = str_arg_count(kernel_buffer, count); + + cfg.change_state_only = 0; + + if (arg_no == 1) { + /* get the Rule/enable value */ + sscanf(kernel_buffer, "%hhu/%hhu", &cfg.rule_num, &cfg.enable); + cfg.change_state_only = 1; + } else { + unsigned int ret; + ret = sscanf(kernel_buffer, + "%hhu/%hhu %hhu %hhx:%hhx:%hhx:%hhx:%hhx:%hhx " + "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx %hhx:%hhx:%hhx:" + "%hhx:%hhx:%hhx %hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &cfg.rule_num, &cfg.enable, &cfg.vqnum, + &cfg.DA[0], &cfg.DA[1], &cfg.DA[2], + &cfg.DA[3], &cfg.DA[4], &cfg.DA[5], + &cfg.DM[0], &cfg.DM[1], &cfg.DM[2], + &cfg.DM[3], &cfg.DM[4], &cfg.DM[5], + &cfg.SA[0], &cfg.SA[1], &cfg.SA[2], + &cfg.SA[3], &cfg.SA[4], &cfg.SA[5], + &cfg.SM[0], &cfg.SM[1], &cfg.SM[2], + &cfg.SM[3], &cfg.SM[4], &cfg.SM[5]); + if (ret != 27) { + printk(KERN_WARNING "%s", tsmac_usage_l2rule); + return count; + } + } + + /* validate rule number and VQ number */ + if ((cfg.rule_num >= 4) || ((arg_no > 1) && (cfg.vqnum >= VQ_MAX))) { + printk(KERN_WARNING "%s", tsmac_usage_l2rule); + return count; + } + + if (cfg.enable != 0) + cfg.enable = 1; + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_set_addr_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + return count; +} + +static int tsmac_proc_read_ethtypevlan(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_vlanvq_config cfg; + int len = 0; + + if (off > 0) + return 0; + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_get_vlan_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + len += sprintf(buffer, "TCI\tP0 P1 P2 P3 P4 P5 P6 P7\n"); + len += sprintf(buffer + len, "%d\t%d %d %d %d %d %d %d %d\n", + cfg.tci_offset, + (cfg.vlanvq[0] & 0x0F), + (cfg.vlanvq[0] & 0xF0) >> 4, + (cfg.vlanvq[1] & 0x0F), + (cfg.vlanvq[1] & 0xF0) >> 4, + (cfg.vlanvq[2] & 0x0F), + (cfg.vlanvq[2] & 0xF0) >> 4, + (cfg.vlanvq[3] & 0x0F), (cfg.vlanvq[3] & 0xF0) >> 4); + return len; +} + +static int tsmac_proc_write_ethtypevlan(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_vlanvq_config cfg; + u8 priority[8]; + int i, j; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_ethtypevlan): " + "copy_from_user failed\n"); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu " + "%hhu", &cfg.tci_offset, &priority[0], &priority[1], + &priority[2], &priority[3], &priority[4], + &priority[5], &priority[6], &priority[7]) != 9) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypevlan); + return count; + } + + /* validate VLAN offset */ + if (cfg.tci_offset >= 63) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypevlan); + return count; + } + + /* validate VQ mapping */ + for (i = 0; i < 8; i++) { + if (priority[i] >= VQ_MAX) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypevlan); + return count; + } + } + + /* + * Update the device object with the new values; each VQ mapping is + * represented by 4 bits + */ + for (i = 0, j = 0; i < 4; i++) { + cfg.vlanvq[i] = (priority[j++] & 0x0F); + cfg.vlanvq[i] |= (priority[j++] & 0x0F) << 4; + } + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_set_vlan_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + return count; +} + +static int tsmac_proc_read_ethtypeipv4(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_ipvq_config cfg[8]; + int row, dscp = 0; + int len = 0; + + if (off > 0) + return 0; + + /* set the variable indicating ipv4/ipv6 */ + cfg[0].ipv4 = 1; /* only set the first one is enough */ + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_get_ip_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + for (row = 0; row < 8; row++) { + len += sprintf(buffer + len, "dscp %02d-%02d: ", dscp, + dscp + 7); + len += sprintf(buffer + len, "%d %d %d %d %d %d %d %d\n", + (cfg[row].ip_vq[0] & 0x0F), + (cfg[row].ip_vq[0] & 0xF0) >> 4, + (cfg[row].ip_vq[1] & 0x0F), + (cfg[row].ip_vq[1] & 0xF0) >> 4, + (cfg[row].ip_vq[2] & 0x0F), + (cfg[row].ip_vq[2] & 0xF0) >> 4, + (cfg[row].ip_vq[3] & 0x0F), + (cfg[row].ip_vq[3] & 0xF0) >> 4); + dscp += 8; + } + + return len; +} + +static int tsmac_proc_read_ethtypeipv6(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_ipvq_config cfg[8]; + int row, dscp = 0; + int len = 0; + + if (off > 0) + return 0; + + /* set the variable indicating ipv4/ipv6 */ + cfg[0].ipv4 = 0; /* onlythe set the first one is enough */ + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_get_ip_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + for (row = 0; row < 8; row++) { + len += sprintf(buffer + len, "dscp %02d-%02d: ", dscp, + dscp + 7); + len += sprintf(buffer + len, "%d %d %d %d %d %d %d %d\n", + (cfg[row].ip_vq[0] & 0x0F), + (cfg[row].ip_vq[0] & 0xF0) >> 4, + (cfg[row].ip_vq[1] & 0x0F), + (cfg[row].ip_vq[1] & 0xF0) >> 4, + (cfg[row].ip_vq[2] & 0x0F), + (cfg[row].ip_vq[2] & 0xF0) >> 4, + (cfg[row].ip_vq[3] & 0x0F), + (cfg[row].ip_vq[3] & 0xF0) >> 4); + dscp += 8; + } + return len; +} + +static int tsmac_proc_write_ethtypeipv4(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_ipvq_config cfg; + u8 vqmap[8]; + int row, i, j; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_ethtypeipv4): " + "copy_from_user failed\n"); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%d %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu", + &row, &vqmap[0], &vqmap[1], &vqmap[2], &vqmap[3], &vqmap[4], + &vqmap[5], &vqmap[6], &vqmap[7]) != 9) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypeip); + return count; + } + + /* validate DSCP range */ + if (row < 0 || row > 63) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypeip); + return count; + } + + /* validate VQ mapping */ + for (i = 0; i < 8; i++) { + if (vqmap[i] >= VQ_MAX) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypeip); + return count; + } + } + + /* + * Update the device object with the new values; each VQ mapping is " + * represented by 4 bits + */ + for (i = 0, j = 0; i < 4; i++) { + cfg.ip_vq[i] = (vqmap[j++] & 0x0F); + cfg.ip_vq[i] |= (vqmap[j++] & 0x0F) << 4; + } + + /* set the variable indicating ipv4/ipv6 */ + cfg.ipv4 = 1; /* we are setting ipv4 rules */ + + /* set the row */ + cfg.dsp_range = row; + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_set_ip_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + return count; +} + +static int tsmac_proc_write_ethtypeipv6(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_ipvq_config cfg; + u8 vqmap[8]; + int row, i, j; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_ethtypeipv6): " + "copy_from_user failed\n"); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%d %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu", + &row, &vqmap[0], &vqmap[1], &vqmap[2], &vqmap[3], &vqmap[4], + &vqmap[5], &vqmap[6], &vqmap[7]) != 9) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypeip); + return count; + } + + /* validate DSCP range */ + if (row < 0 || row > 63) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypeip); + return count; + } + + /* validate VQ mapping */ + for (i = 0; i < 8; i++) { + if (vqmap[i] >= VQ_MAX) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypeip); + return count; + } + } + + /* + * Update the device object with the new values; each VQ mapping is + * represented by 4 bits + */ + for (i = 0, j = 0; i < 4; i++) { + cfg.ip_vq[i] = (vqmap[j++] & 0x0F); + cfg.ip_vq[i] |= (vqmap[j++] & 0x0F) << 4; + } + + /* set the variable indicating ipv4/ipv6 */ + cfg.ipv4 = 0; /* we are setting ipv6 rules */ + + /* set the row */ + cfg.dsp_range = row; + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_set_ip_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + return count; +} + +static int tsmac_proc_read_ethtypeuser(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_ethtype_config cfg; + int len = 0; + + if (off > 0) + return 0; + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_get_ethtype_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + len += sprintf(buffer, "EthType/E Q\n"); + len += sprintf(buffer + len, " 0x%02x%02x/%d %d\n", + cfg.ethtype[0], cfg.ethtype[1], + cfg.ethtype_enable, cfg.ethtype_vq); + return len; +} + +static int tsmac_proc_write_ethtypeuser(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_ethtype_config cfg; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_ethtypeuser): " + "copy_from_user failed\n"); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%hx/%hhu %hhu", + (unsigned short *)&cfg.ethtype[0], + &cfg.ethtype_enable, &cfg.ethtype_vq) != 3) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypeuser); + return count; + } + + if (cfg.ethtype_enable != 0) + cfg.ethtype_enable = 1; + + /* validate VQ number */ + if (cfg.ethtype_vq >= VQ_MAX) { + printk(KERN_WARNING "%s", tsmac_usage_ethtypeuser); + return count; + } + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_set_ethtype_class_rule(dev, &ifr, TSMAC_KERNEL_DATA); + + return count; +} + +static int tsmac_proc_read_dump(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + + if (off > 0) + return 0; + + return tsmac_print_map_classifier(lp, buffer); +} + +static int tsmac_proc_write_dump(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct tsmac_private *lp = netdev_priv(dev); + char kernel_buffer[KERN_BUF_MAX_SIZE]; + u32 addr, val; + int args; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "copy_from_user failed\n"); + return -EFAULT; + } + + if (!netif_running(dev)) { + printk(KERN_WARNING "Interface is currently down!\n"); + return count; + } + + /* wrong # of arguments */ + args = sscanf(kernel_buffer, "%x %x", &addr, &val); + if (args != 2) { + printk(KERN_WARNING "%s", tsmac_usage_dump); + return count; + } + + /* validate memory address */ + if (addr < 0x84 || addr > 0x130 || ((addr & 0x03) != 0)) { + printk(KERN_WARNING "%s", tsmac_usage_dump); + return count; + } + + /* load the word into memory */ + tsmac_write(addr, &lp->reg_map->mac.arc_addr); + tsmac_write(val, &lp->reg_map->mac.arc_data); + + return count; +} + +static int tsmac_proc_read_dropthreshold(char *buffer, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_drop_threshold cfg; + int len = 0; + + if (off > 0) + return 0; + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_get_drop_thresh(dev, &ifr, TSMAC_KERNEL_DATA); + + len += sprintf(buffer, "DropOff\tDropOn\n"); + + len += sprintf(buffer + len, "%7d%7d\n", cfg.drop_off_thresh, + cfg.drop_on_thresh); + + return len; +} + +static int tsmac_proc_write_dropthreshold(struct file *file, + const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_drop_threshold cfg; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_dropthreshold): " + "copy_from_user failed\n"); + return -EFAULT; + } + + if (sscanf(kernel_buffer, "%hu %hu", &cfg.drop_off_thresh, + &cfg.drop_on_thresh) != 2) { + printk(KERN_WARNING "%s", tsmac_usage_dropthreshold); + return count; + } + + /* validate drop off level */ + if ((cfg.drop_off_thresh > RX_DROPOFF_MAX) || + (cfg.drop_off_thresh & 0x3)) { + printk(KERN_WARNING "%s", tsmac_usage_dropthreshold); + return count; + } + + /* validate drop on level */ + if ((cfg.drop_on_thresh > RX_DROPON_MAX) || + (cfg.drop_on_thresh & 0x3)) { + printk(KERN_WARNING "%s", tsmac_usage_dropthreshold); + return count; + } + + if (cfg.drop_off_thresh >= cfg.drop_on_thresh) { + printk(KERN_WARNING "%s", tsmac_usage_dropthreshold); + return count; + } + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_set_drop_thresh(dev, &ifr, TSMAC_KERNEL_DATA); + + return count; +} + +static int tsmac_proc_read_vq(char *buffer, char **start, off_t off, int count, + int *eof, void *data) +{ + struct net_device *dev = data; + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_vq_config cfg[VQ_MAX]; + int i; + int len = 0; + + if (off > 0) + return 0; + + iodata.data = cfg; + ifr.ifr_data = &iodata; + + tsmac_get_vq_config(dev, &ifr, TSMAC_KERNEL_DATA); + + len += sprintf(buffer, "Q/D\tSize\tCurrent\n"); + for (i = 0; i < VQ_MAX; i++) { + len += sprintf(buffer + len, "%d/%d\t%d\t%d\n", + cfg[i].vq_num, + cfg[i].vq_drop_disable, + cfg[i].vq_token_count, + (tsmac_read(&lp->reg_map->mac.vq_token_cnt[i]) + & VQ_TC_Token_Cnt_Mask)); + } + return len; +} + +static int tsmac_proc_write_vq(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + struct net_device *dev = data; + struct tsmac_io_data iodata; + struct ifreq ifr; + struct tsmac_vq_config cfg; + char tmpbuf[80]; + char kernel_buffer[KERN_BUF_MAX_SIZE]; + + if (count > KERN_BUF_MAX_SIZE) + count = KERN_BUF_MAX_SIZE; + + if (copy_from_user(kernel_buffer, buffer, count)) { + printk(KERN_WARNING "(tsmac_proc_write_vq): copy_from_user " + "failed\n"); + return -EFAULT; + } + + /* copy data from kernel buffer to a tmp buff */ + memcpy(tmpbuf, kernel_buffer, count); + + if (sscanf(kernel_buffer, "%hhu/%hhu %hu", &cfg.vq_num, + &cfg.vq_drop_disable, &cfg.vq_token_count) != 3) { + printk(KERN_WARNING "%s", tsmac_usage_vq); + return count; + } + + /* validate vq number */ + if (cfg.vq_num >= VQ_MAX) { + printk(KERN_WARNING "%s", tsmac_usage_vq); + return count; + } + + if (cfg.vq_drop_disable != 0) + cfg.vq_drop_disable = 1; + + iodata.data = &cfg; + ifr.ifr_data = &iodata; + + tsmac_set_vq_config(dev, &ifr, TSMAC_KERNEL_DATA); + return count; +} + +/* + * Callback for open method of seq_file interface + */ +static int tsmac_proc_read_txdesc_open(struct inode *inode, struct file *filp) +{ + return single_open(filp, tsmac_proc_read_txdesc, PDE(inode)->data); +} + +static int tsmac_proc_read_rxdesc_open(struct inode *inode, struct file *filp) +{ + return single_open(filp, tsmac_proc_read_rxdesc, PDE(inode)->data); +} + +/* + * Create and register the proc file entries + */ +void tsmac_create_proc_entries(struct net_device *dev) +{ + struct tsmac_private *lp = netdev_priv(dev); + + /* general */ + struct proc_dir_entry *proc_macaddr; + struct proc_dir_entry *proc_reg; + struct proc_dir_entry *proc_txdesc; + struct proc_dir_entry *proc_rxdesc; + struct proc_dir_entry *proc_stats; + struct proc_dir_entry *proc_mii; + struct proc_dir_entry *proc_iphdroffset; + struct proc_dir_entry *proc_pause_arc; + struct proc_dir_entry *proc_desc_size; + struct proc_dir_entry *proc_conntype; + struct proc_dir_entry *proc_phyaddr; + struct proc_dir_entry *proc_miitype; + struct proc_dir_entry *proc_linkmode; + struct proc_dir_entry *proc_taskcpu; +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE + struct proc_dir_entry *proc_loopback; +#endif + + /* for flood control */ + struct proc_dir_entry *proc_egressprio; + struct proc_dir_entry *proc_default; + struct proc_dir_entry *proc_dump; + struct proc_dir_entry *proc_l2rule; + struct proc_dir_entry *proc_ethtypevlan; + struct proc_dir_entry *proc_ethtypeipv4; + struct proc_dir_entry *proc_ethtypeipv6; + struct proc_dir_entry *proc_ethtypeuser; + struct proc_dir_entry *proc_dropthreshold; + struct proc_dir_entry *proc_vq; + + tsmac_proc[lp->unit].eth_dir = proc_mkdir(dev->name, + init_net.proc_net); + + tsmac_proc[lp->unit].qos_dir = proc_mkdir("qos", + tsmac_proc[lp->unit].eth_dir); + + tsmac_proc[lp->unit].classify_dir = proc_mkdir("classify", + tsmac_proc[lp->unit]. + qos_dir); + + tsmac_proc[lp->unit].provision_dir = proc_mkdir("provision", + tsmac_proc[lp->unit]. + qos_dir); + + create_proc_read_entry(TSMAC_PROC_INFO, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir, + tsmac_proc_read_info, dev); + + proc_stats = create_proc_entry(TSMAC_PROC_STATS, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_stats) { + proc_stats->read_proc = tsmac_proc_read_stats; + proc_stats->write_proc = tsmac_proc_write_stats; + proc_stats->data = dev; + } + + proc_macaddr = create_proc_entry(TSMAC_PROC_MACADDR, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_macaddr) { + proc_macaddr->read_proc = tsmac_proc_read_macaddr; + proc_macaddr->write_proc = tsmac_proc_write_macaddr; + proc_macaddr->data = dev; + } + + proc_reg = create_proc_entry(TSMAC_PROC_REGCONTENTS, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_reg) { + proc_reg->read_proc = tsmac_proc_read_reg; + proc_reg->write_proc = tsmac_proc_write_reg; + proc_reg->data = dev; + } + + proc_pause_arc = create_proc_entry(TSMAC_PROC_PAUSEARC, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_pause_arc) { + proc_pause_arc->read_proc = tsmac_proc_read_pause_arc; + proc_pause_arc->write_proc = tsmac_proc_write_pause_arc; + proc_pause_arc->data = dev; + } + + proc_desc_size = create_proc_entry(TSMAC_PROC_DESCSIZE, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_desc_size) { + proc_desc_size->read_proc = tsmac_proc_read_desc_size; + proc_desc_size->write_proc = tsmac_proc_write_desc_size; + proc_desc_size->data = dev; + } + + proc_txdesc = create_proc_entry(TSMAC_PROC_DUMPTX, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_txdesc) { + proc_txdesc->proc_fops = &txdesc_fops; + proc_txdesc->data = dev; + } + + proc_rxdesc = create_proc_entry(TSMAC_PROC_DUMPRX, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_rxdesc) { + proc_rxdesc->proc_fops = &rxdesc_fops; + proc_rxdesc->data = dev; + } + + proc_mii = create_proc_entry(TSMAC_PROC_MII, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_mii) { + proc_mii->read_proc = tsmac_proc_read_mii; + proc_mii->data = dev; + } + + proc_egressprio = create_proc_entry(TSMAC_PROC_EGRESSPRIO, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].qos_dir); + if (proc_egressprio) { + proc_egressprio->read_proc = tsmac_proc_read_egressprio; + proc_egressprio->write_proc = tsmac_proc_write_egressprio; + proc_egressprio->data = dev; + } + + proc_default = create_proc_entry(TSMAC_PROC_DEFAULT, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].classify_dir); + if (proc_default) { + proc_default->read_proc = tsmac_proc_read_default; + proc_default->write_proc = tsmac_proc_write_default; + proc_default->data = dev; + } + + proc_dump = create_proc_entry(TSMAC_PROC_DUMP, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].classify_dir); + if (proc_dump) { + proc_dump->read_proc = tsmac_proc_read_dump; + proc_dump->write_proc = tsmac_proc_write_dump; + proc_dump->data = dev; + } + + proc_l2rule = create_proc_entry(TSMAC_PROC_L2RULE, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].classify_dir); + if (proc_l2rule) { + proc_l2rule->read_proc = tsmac_proc_read_l2rule; + proc_l2rule->write_proc = tsmac_proc_write_l2rule; + proc_l2rule->data = dev; + } + + proc_ethtypevlan = create_proc_entry(TSMAC_PROC_ETHTYPEVLAN, + TSMAC_PROC_PERM, tsmac_proc[lp->unit].classify_dir); + if (proc_ethtypevlan) { + proc_ethtypevlan->read_proc = tsmac_proc_read_ethtypevlan; + proc_ethtypevlan->write_proc = tsmac_proc_write_ethtypevlan; + proc_ethtypevlan->data = dev; + } + + proc_ethtypeipv4 = create_proc_entry(TSMAC_PROC_ETHTYPEIPV4, + TSMAC_PROC_PERM, tsmac_proc[lp->unit].classify_dir); + if (proc_ethtypeipv4) { + proc_ethtypeipv4->read_proc = tsmac_proc_read_ethtypeipv4; + proc_ethtypeipv4->write_proc = tsmac_proc_write_ethtypeipv4; + proc_ethtypeipv4->data = dev; + } + + proc_ethtypeipv6 = create_proc_entry(TSMAC_PROC_ETHTYPEIPV6, + TSMAC_PROC_PERM, tsmac_proc[lp->unit].classify_dir); + if (proc_ethtypeipv6) { + proc_ethtypeipv6->read_proc = tsmac_proc_read_ethtypeipv6; + proc_ethtypeipv6->write_proc = tsmac_proc_write_ethtypeipv6; + proc_ethtypeipv6->data = dev; + } + + proc_ethtypeuser = create_proc_entry(TSMAC_PROC_ETHTYPEUSER, + TSMAC_PROC_PERM, tsmac_proc[lp->unit].classify_dir); + if (proc_ethtypeuser) { + proc_ethtypeuser->read_proc = tsmac_proc_read_ethtypeuser; + proc_ethtypeuser->write_proc = tsmac_proc_write_ethtypeuser; + proc_ethtypeuser->data = dev; + } + + proc_dropthreshold = create_proc_entry(TSMAC_PROC_DROPTHRESHOLD, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit]. + provision_dir); + if (proc_dropthreshold) { + proc_dropthreshold->read_proc = tsmac_proc_read_dropthreshold; + proc_dropthreshold->write_proc = + tsmac_proc_write_dropthreshold; + proc_dropthreshold->data = dev; + } + + proc_vq = create_proc_entry(TSMAC_PROC_VQ, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].provision_dir); + if (proc_vq) { + proc_vq->read_proc = tsmac_proc_read_vq; + proc_vq->write_proc = tsmac_proc_write_vq; + proc_vq->data = dev; + } +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE + proc_loopback = create_proc_entry(TSMAC_PROC_LOOPBACK, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_loopback) { + proc_loopback->read_proc = tsmac_proc_read_lineloopback; + proc_loopback->write_proc = tsmac_proc_write_lineloopback; + proc_loopback->data = dev; + } +#endif + proc_iphdroffset = create_proc_entry(TSMAC_PROC_IPHDROFFSET, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_iphdroffset) { + proc_iphdroffset->read_proc = tsmac_proc_read_iphdroffset; + proc_iphdroffset->write_proc = tsmac_proc_write_iphdroffset; + proc_iphdroffset->data = dev; + } + + proc_conntype = create_proc_entry(TSMAC_PROC_CONNTYPE, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_conntype) { + proc_conntype->read_proc = tsmac_proc_read_conntype; + proc_conntype->write_proc = tsmac_proc_write_conntype; + proc_conntype->data = dev; + } + + proc_phyaddr = create_proc_entry(TSMAC_PROC_PHYADDR, + TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_phyaddr) { + proc_phyaddr->read_proc = tsmac_proc_read_phyaddr; + proc_phyaddr->write_proc = tsmac_proc_write_phyaddr; + proc_phyaddr->data = dev; + } + + proc_miitype = create_proc_entry(TSMAC_PROC_MIITYPE, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_miitype) { + proc_miitype->read_proc = tsmac_proc_read_miitype; + proc_miitype->write_proc = tsmac_proc_write_miitype; + proc_miitype->data = dev; + } + + proc_linkmode = create_proc_entry(TSMAC_PROC_LINKMODE, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_linkmode) { + proc_linkmode->read_proc = tsmac_proc_read_linkmode; + proc_linkmode->data = dev; + } + + proc_taskcpu = create_proc_entry(TSMAC_PROC_TASKCPU, TSMAC_PROC_PERM, + tsmac_proc[lp->unit].eth_dir); + if (proc_taskcpu) { + proc_taskcpu->read_proc = tsmac_proc_read_taskcpu; + proc_taskcpu->write_proc = tsmac_proc_write_taskcpu; + proc_taskcpu->data = dev; + } + + return; +} + +/* + * Remove the proc file entries + */ +void tsmac_remove_proc_entries(void) +{ + char tmp_str[80]; + int i; + + for (i = 0; i < TSMAC_MAX_UNITS; i++) { + sprintf(tmp_str, "tsmac%d", i); + + /* general */ + remove_proc_entry(tmp_str, init_net.proc_net); + remove_proc_entry(TSMAC_PROC_INFO, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_CONNTYPE, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_STATS, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_DUMPRX, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_DUMPTX, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_MACADDR, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_REGCONTENTS, + tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_MII, tsmac_proc[i].eth_dir); +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE + remove_proc_entry(TSMAC_PROC_LOOPBACK, tsmac_proc[i].eth_dir); +#endif + remove_proc_entry(TSMAC_PROC_IPHDROFFSET, + tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_PAUSEARC, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_DESCSIZE, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_PHYADDR, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_MIITYPE, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_LINKMODE, tsmac_proc[i].eth_dir); + remove_proc_entry(TSMAC_PROC_TASKCPU, tsmac_proc[i].eth_dir); + + remove_proc_entry(TSMAC_PROC_EGRESSPRIO, + tsmac_proc[i].qos_dir); + remove_proc_entry(TSMAC_PROC_DEFAULT, + tsmac_proc[i].classify_dir); + remove_proc_entry(TSMAC_PROC_L2RULE, + tsmac_proc[i].classify_dir); + remove_proc_entry(TSMAC_PROC_ETHTYPEVLAN, + tsmac_proc[i].classify_dir); + remove_proc_entry(TSMAC_PROC_ETHTYPEIPV4, + tsmac_proc[i].classify_dir); + remove_proc_entry(TSMAC_PROC_ETHTYPEIPV6, + tsmac_proc[i].classify_dir); + remove_proc_entry(TSMAC_PROC_ETHTYPEUSER, + tsmac_proc[i].classify_dir); + remove_proc_entry(TSMAC_PROC_DUMP, tsmac_proc[i].classify_dir); + remove_proc_entry(TSMAC_PROC_DROPTHRESHOLD, + tsmac_proc[i].provision_dir); + remove_proc_entry(TSMAC_PROC_VQ, tsmac_proc[i].provision_dir); + } + return; +} + +/* + * Performs interface-specific ioctl commands to configure and manage the + * Ethernet interfaces. READ and WRITE functions are using same command number + * but each is identifying independently with the help of sub command + */ +int tsmac_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct tsmac_private *lp = netdev_priv(dev); + struct tsmac_io_data *data = ifr->ifr_data; + int ret; + + if (lp->conn_type == MSP_CT_MOCA) { + + /* Handle MoCA ioctls */ + switch (cmd) { + case SIOCDEVPRIVATE: + /* Reset */ + ret = tsmac_moca_reset(dev, ifr, TSMAC_USER_DATA); + break; +/* TODO : decide to remove or modify, this is for MoCA */ +#if 0 + case SIOCGMIIREG: + /* PHY MII Read */ + ret = tsmac_get_phy(dev, ifr, TSMAC_USER_DATA); + break; + + case SIOCSMIIREG: + /* PHY MII Write */ + ret = tsmac_set_phy(dev, ifr, TSMAC_USER_DATA); + break; + + case SIOCGMIIPHY: + /* PHY MII ID */ + ret = tsmac_get_phy_id(dev, ifr, TSMAC_USER_DATA); + break; +#endif + default: + ret = -EOPNOTSUPP; + } + + } else { + + switch (cmd) { + case PMC_ETH_IOCMD_CLASSDEFVQ_READ: + if (data->subcmd) + ret = tsmac_get_default_vq_map(dev, ifr, + TSMAC_USER_DATA); + else + ret = tsmac_set_default_vq_map(dev, ifr, + TSMAC_USER_DATA); + break; + + case PMC_ETH_IOCMD_CLASSADDR_READ: + if (data->subcmd) + ret = tsmac_get_addr_class_rule(dev, ifr, + TSMAC_USER_DATA); + else + ret = tsmac_set_addr_class_rule(dev, ifr, + TSMAC_USER_DATA); + break; + +#ifdef CONFIG_TSMAC_LINELOOPBACK_FEATURE + case PMC_ETH_IOCMD_LINELOOP_READ: + if (data->subcmd) + ret = tsmac_get_loopback(dev, ifr, + TSMAC_USER_DATA); + else + ret = tsmac_set_loopback(dev, ifr, + TSMAC_USER_DATA); + break; +#endif + + case PMC_ETH_IOCMD_CLASSVLAN_READ: + if (data->subcmd) + ret = tsmac_get_vlan_class_rule(dev, ifr, + TSMAC_USER_DATA); + else + ret = tsmac_set_vlan_class_rule(dev, ifr, + TSMAC_USER_DATA); + break; + + case PMC_ETH_IOCMD_CLASS4DSCP_READ: + if (data->subcmd) + ret = tsmac_get_ip_class_rule(dev, ifr, + TSMAC_USER_DATA); + else + ret = tsmac_set_ip_class_rule(dev, ifr, + TSMAC_USER_DATA); + break; + + case PMC_ETH_IOCMD_CLASSETHTYPE_READ: + if (data->subcmd) + ret = tsmac_get_ethtype_class_rule(dev, ifr, + TSMAC_USER_DATA); + else + ret = tsmac_set_ethtype_class_rule(dev, ifr, + TSMAC_USER_DATA); + break; + + case PMC_ETH_IOCMD_PROVFIFO_READ: + if (data->subcmd) + ret = tsmac_get_drop_thresh(dev, ifr, + TSMAC_USER_DATA); + else + ret = tsmac_set_drop_thresh(dev, ifr, + TSMAC_USER_DATA); + break; + + case PMC_ETH_IOCMD_PROVVQ_READ: + if (data->subcmd) + ret = tsmac_get_vq_config(dev, ifr, + TSMAC_USER_DATA); + else + ret = tsmac_set_vq_config(dev, ifr, + TSMAC_USER_DATA); + break; + + case PMC_ETH_IOCMD_QOSDEFAULT_WRITE: + tsmac_config_def_vqnpause(dev); + ret = tsmac_set_vqnpause(dev); + break; + + default: + ret = -EOPNOTSUPP; + } + } + + return ret; +} -- 1.7.0.4