[PATCH 1/5] net: mvberlin_eth: add an Ethernet driver for Marvell Berlin
Ezequiel Garcia
ezequiel.garcia at free-electrons.com
Fri Aug 29 07:51:45 PDT 2014
Hi Antoine,
A quick look...
On 29 Aug 03:50 PM, Antoine Tenart wrote:
> This patch introduces the Marvell Berlin network unit driver, which uses
> the MVMDIO interface to communicate to the PHY. This is a fast Ethernet
> driver.
>
> This driver is highly based on the mv643xx_eth driver, and reuse some of
> its functions. But lots of differences are there:
> - They do not have the same registers.
> - The mvberlin_eth driver only supports fast Ethernet.
> - The rx/tx handling functions logic is different.
> - The mv643xx_eth driver supports TSO.
TSO is just a software feature which needs just some basic hardware features
to work. I guess there's no point in implementing it for a fast Ethernet
controller?
> - The mvberlin_eth driver uses a hash table to filter incoming packets.
>
> No packet is dropped during a ping flood (ping -f) and an iperf test
> shows:
> ------------------------------------------------------------
> Client connecting to 192.168.0.11, TCP port 5001
> TCP window size: 85.0 KByte (default)
> ------------------------------------------------------------
> [ 3] local 192.168.0.20 port 44183 connected with 192.168.0.11 port 5001
> [ ID] Interval Transfer Bandwidth
> [ 3] 0.0-10.0 sec 113 MBytes 94.8 Mbits/sec
>
> Signed-off-by: Antoine Tenart <antoine.tenart at free-electrons.com>
> ---
> drivers/net/ethernet/marvell/Kconfig | 9 +
> drivers/net/ethernet/marvell/Makefile | 1 +
> drivers/net/ethernet/marvell/mvberlin_eth.c | 2081 +++++++++++++++++++++++++++
> 3 files changed, 2091 insertions(+)
> create mode 100644 drivers/net/ethernet/marvell/mvberlin_eth.c
>
> diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig
> index 1b4fc7c639e6..a4f12257d099 100644
> --- a/drivers/net/ethernet/marvell/Kconfig
> +++ b/drivers/net/ethernet/marvell/Kconfig
> @@ -18,6 +18,15 @@ config NET_VENDOR_MARVELL
>
> if NET_VENDOR_MARVELL
>
> +config MVBERLIN_ETH
> + tristate "Marvell Berlin ethernet support"
> + depends on ARCH_BERLIN && INET
> + select PHYLIB
> + select MVMDIO
> + ---help---
> + This driver supports the ethernet interface of the Marvell
> + Berlin SoCs.
> +
> config MV643XX_ETH
> tristate "Marvell Discovery (643XX) and Orion ethernet support"
> depends on (MV64X60 || PPC32 || PLAT_ORION) && INET
> diff --git a/drivers/net/ethernet/marvell/Makefile b/drivers/net/ethernet/marvell/Makefile
> index f6425bd2884b..a802dba2503e 100644
> --- a/drivers/net/ethernet/marvell/Makefile
> +++ b/drivers/net/ethernet/marvell/Makefile
> @@ -2,6 +2,7 @@
> # Makefile for the Marvell device drivers.
> #
>
> +obj-$(CONFIG_MVBERLIN_ETH) += mvberlin_eth.o
> obj-$(CONFIG_MVMDIO) += mvmdio.o
> obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
> obj-$(CONFIG_MVNETA) += mvneta.o
> diff --git a/drivers/net/ethernet/marvell/mvberlin_eth.c b/drivers/net/ethernet/marvell/mvberlin_eth.c
> new file mode 100644
> index 000000000000..5f1874b58017
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/mvberlin_eth.c
> @@ -0,0 +1,2081 @@
> +/*
> + * Copyright (C) 2014 Marvell Technology Group Ltd.
> + *
> + * Antoine Tenart <antoine.tenart at free-electrons.com>
> + * Jisheng Zhang <jszhang at marvell.com>
> + *
> + * Based on the driver for Marvell Discovery (MV643XX) and Marvell Orion
> + * ethernet ports
> + * Copyright (C) 2002 Matthew Dharm <mdharm at momenco.com>
> + *
> + * Based on the 64360 driver from:
> + * Copyright (C) 2002 Rabeeh Khoury <rabeeh at galileo.co.il>
> + * Rabeeh Khoury <rabeeh at marvell.com>
> + *
> + * Copyright (C) 2003 PMC-Sierra, Inc.,
> + * written by Manish Lachwani
> + *
> + * Copyright (C) 2003 Ralf Baechle <ralf at linux-mips.org>
> + *
> + * Copyright (C) 2004-2006 MontaVista Software, Inc.
> + * Dale Farnsworth <dale at farnsworth.org>
> + *
> + * Copyright (C) 2004 Steven J. Hill <sjhill1 at rockwellcollins.com>
> + * <sjhill at realitydiluted.com>
> + *
> + * Copyright (C) 2007-2008 Marvell Semiconductor
> + * Lennert Buytenhek <buytenh at marvell.com>
> + *
> + * Copyright (C) 2013 Michael Stapelberg <michael at stapelberg.de>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version 2
> + * of the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/etherdevice.h>
> +#include <linux/ethtool.h>
> +#include <linux/in.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/ip.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mv643xx_eth.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_net.h>
> +#include <linux/of_mdio.h>
> +#include <linux/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/tcp.h>
> +#include <linux/types.h>
> +#include <linux/udp.h>
> +#include <linux/workqueue.h>
> +
> +static const char mvberlin_eth_driver_name[] = "mvberlin_eth";
> +static const char mvberlin_eth_driver_version[] = "1.0";
> +
> +/* Main per-port registers. These live at offset 0x0400 */
> +#define PORT_CONFIG 0x0000
> +#define PROMISCUOUS_MODE 0x00000001
I think that using BIT() makes the code more readable.
> +
> +/* SDMA configuration register default value */
> +#if defined(__BIG_ENDIAN)
> +#define PORT_SDMA_CONFIG_DEFAULT_VALUE \
> + (BURST_SIZE_8_64BIT | \
> + 0x3c | \
> + RX_FRAME_INTERRUPT)
> +#elif defined(__LITTLE_ENDIAN)
> +#define PORT_SDMA_CONFIG_DEFAULT_VALUE \
> + (BURST_SIZE_8_64BIT | \
> + BLM_RX_LE | \
> + BLM_TX_LE | \
> + 0x3c | \
> + RX_FRAME_INTERRUPT)
> +#else
> +#error One of __BIG_ENDIAN or __LITTLE_ENDIAN must be defined
> +#endif
> +
> +/* Misc definitions */
> +#define DEFAULT_RX_QUEUE_SIZE 128
> +#define DEFAULT_TX_QUEUE_SIZE 512
> +#define SKB_DMA_REALIGN ((PAGE_SIZE - NET_SKB_PAD) % SMP_CACHE_BYTES)
> +
> +/* RX/TX descriptors */
> +#if defined(__BIG_ENDIAN)
Maybe you can pack this inside the above ifdef and squash all the
endian-dependent stuff?
> +struct rx_desc {
> + u16 buf_size; /* Buffer size */
> + u16 byte_cnt; /* Descriptor buffer byte count */
> + u32 cmd_sts; /* Command/status field */
> + u32 next_desc_ptr; /* Next descriptor pointer */
> + u32 buf_ptr; /* Descriptor buffer pointer */
> +};
> +
> +struct tx_desc {
> + u16 byte_cnt; /* buffer byte count */
> + u16 l4i_chk; /* CPU provided TCP checksum */
> + u32 cmd_sts; /* Command/status field */
> + u32 next_desc_ptr; /* Pointer to next descriptor */
> + u32 buf_ptr; /* pointer to buffer for this descriptor*/
> +};
> +#elif defined(__LITTLE_ENDIAN)
> +struct rx_desc {
> + u32 cmd_sts; /* Descriptor command status */
> + u16 byte_cnt; /* Descriptor buffer byte count */
> + u16 buf_size; /* Buffer size */
> + u32 buf_ptr; /* Descriptor buffer pointer */
> + u32 next_desc_ptr; /* Next descriptor pointer */
> +};
> +
> +struct tx_desc {
> + u32 cmd_sts; /* Command/status field */
> + u16 l4i_chk; /* CPU provided TCP checksum */
> + u16 byte_cnt; /* buffer byte count */
> + u32 buf_ptr; /* pointer to buffer for this descriptor*/
> + u32 next_desc_ptr; /* Pointer to next descriptor */
> +};
> +#else
> +#error One of __BIG_ENDIAN or __LITTLE_ENDIAN must be defined
> +#endif
> +
> +/* RX & TX descriptor command */
> +#define BUFFER_OWNED_BY_DMA 0x80000000
> +
Ditto about BIT() usage.
> +static netdev_tx_t mvberlin_eth_xmit(struct sk_buff *skb,
> + struct net_device *dev)
> +{
> + struct mvberlin_eth_private *mp = netdev_priv(dev);
> + int length, queue;
> + struct tx_queue *txq;
> + struct netdev_queue *nq;
> +
> + queue = skb_get_queue_mapping(skb);
> + txq = mp->txq + queue;
> + nq = netdev_get_tx_queue(dev, queue);
> +
> + if (has_tiny_unaligned_frags(skb) && __skb_linearize(skb)) {
> + netdev_printk(KERN_DEBUG, dev,
> + "failed to linearize skb with tiny unaligned fragment\n");
netdev_dbg?
> + return NETDEV_TX_BUSY;
> + }
> +
> + length = skb->len;
> +
> + if (!txq_submit_skb(txq, skb, dev)) {
> + txq->tx_bytes += length;
> + txq->tx_packets++;
> +
> + if (txq->tx_desc_count >= txq->tx_stop_threshold)
> + netif_tx_stop_queue(nq);
> + } else {
> + txq->tx_dropped++;
> + dev_kfree_skb_any(skb);
> + }
> +
> + return NETDEV_TX_OK;
> +}
> +
> +
> +static void handle_link_event(struct mvberlin_eth_private *mp)
> +{
> + struct net_device *dev = mp->dev;
> + u32 port_status;
> + int speed;
> + int duplex;
> + int fc;
> +
> + port_status = rdlp(mp, PORT_STATUS);
> + if (!(port_status & LINK_UP)) {
> + if (netif_carrier_ok(dev)) {
> + int i;
> +
> + netdev_info(dev, "link down\n");
> +
> + netif_carrier_off(dev);
> +
> + for (i = 0; i < mp->txq_count; i++) {
> + struct tx_queue *txq = mp->txq + i;
> +
> + txq_reclaim(txq, txq->tx_ring_size, 1);
> + txq_reset_hw_ptr(txq);
> + }
> + }
> + return;
> + }
> +
> + switch (port_status & PORT_SPEED_MASK) {
> + case PORT_SPEED_10:
> + speed = 10;
> + break;
> + case PORT_SPEED_100:
> + speed = 100;
> + break;
> + default:
> + speed = -1;
> + break;
> + }
> +
> + duplex = (port_status & FULL_DUPLEX) ? 1 : 0;
> + fc = (port_status & FLOW_CONTROL_ENABLED) ? 1 : 0;
> +
> + netdev_info(dev, "link up, %d Mb/s, %s duplex, flow control %sabled\n",
> + speed, duplex ? "full" : "half", fc ? "en" : "dis");
Maybe you can use phy_print_status() instead of rolling your own?
> +
> + if (!netif_carrier_ok(dev))
> + netif_carrier_on(dev);
> +}
> +
> +static irqreturn_t mvberlin_eth_irq(int irq, void *dev_id)
> +{
> + struct net_device *dev = (struct net_device *)dev_id;
> + struct mvberlin_eth_private *mp = netdev_priv(dev);
> + u32 int_cause, txstatus;
> + int i;
> +
> + int_cause = rdlp(mp, INT_CAUSE) & mp->int_mask;
> +
> + if (int_cause == 0)
> + return IRQ_NONE;
> + wrlp(mp, INT_CAUSE, ~int_cause);
> +
> + if (int_cause & INT_RX) {
> + wrlp(mp, INT_MASK, mp->int_mask & ~INT_RX);
> + napi_schedule(&mp->napi);
> + }
> +
> + if (int_cause & INT_EXT)
> + handle_link_event(mp);
> +
> + txstatus = int_cause & INT_TX;
> + for (i = 0; i < mp->txq_count; ++i) {
> + if (txstatus & INT_TX_0 << i) {
> + txq_reclaim(mp->txq + i, 16, 0);
> + txq_maybe_wake(mp->txq + i);
> + }
> + }
> +
> + txstatus = ((int_cause & INT_TX_END) >> 6) &
> + ~((rdlp(mp, SDMA_COMMAND) >> 16) & 0x3);
> + for (i = 0; i < mp->txq_count; ++i) {
> + if (txstatus & 1 << i)
> + txq_kick(mp->txq + i);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +static void set_params(struct mvberlin_eth_private *mp,
> + struct mv643xx_eth_platform_data *pd)
> +{
> + struct net_device *dev = mp->dev;
> +
> + if (is_valid_ether_addr(pd->mac_addr))
> + memcpy(dev->dev_addr, pd->mac_addr, ETH_ALEN);
> + else
> + uc_addr_get(mp, dev->dev_addr);
> +
> + mp->rx_ring_size = DEFAULT_RX_QUEUE_SIZE;
> + if (pd->rx_queue_size)
> + mp->rx_ring_size = pd->rx_queue_size;
> + mp->rx_desc_sram_addr = pd->rx_sram_addr;
> + mp->rx_desc_sram_size = pd->rx_sram_size;
> +
> + mp->rxq_count = pd->rx_queue_count ? : 1;
> +
> + mp->tx_ring_size = DEFAULT_TX_QUEUE_SIZE;
> + if (pd->tx_queue_size)
> + mp->tx_ring_size = pd->tx_queue_size;
> +
> + mp->tx_desc_sram_addr = pd->tx_sram_addr;
> + mp->tx_desc_sram_size = pd->tx_sram_size;
> +
> + mp->txq_count = pd->tx_queue_count ? : 1;
> +}
> +
> +static const struct net_device_ops mvberlin_eth_netdev_ops = {
> + .ndo_open = mvberlin_eth_open,
> + .ndo_stop = mvberlin_eth_stop,
> + .ndo_start_xmit = mvberlin_eth_xmit,
> + .ndo_set_rx_mode = mvberlin_eth_set_multicast_list,
> + .ndo_set_mac_address = mvberlin_eth_set_mac_address,
> + .ndo_validate_addr = eth_validate_addr,
> + .ndo_do_ioctl = mvberlin_eth_ioctl,
> + .ndo_change_mtu = mvberlin_eth_change_mtu,
> + .ndo_tx_timeout = mvberlin_eth_tx_timeout,
> + .ndo_get_stats = mvberlin_eth_get_stats,
> +#ifdef CONFIG_NET_POLL_CONTROLLER
> + .ndo_poll_controller = mvberlin_eth_netpoll,
> +#endif
> +};
> +
> +static int mvberlin_eth_probe(struct platform_device *pdev)
> +{
> + struct mv643xx_eth_platform_data *pd;
mv643xx_eth_platform_data? You really lost me here :) I'm having a hard
time figuring out who will set this platform data. I guess I'm overlooking
something?
> + struct mvberlin_eth_private *mp;
> + struct net_device *dev;
> + struct resource *res;
> + int ret;
> +
> + dev = alloc_etherdev_mq(sizeof(struct mvberlin_eth_private), 8);
> + if (!dev)
> + return -ENOMEM;
> +
> + pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL);
> + if (!pd)
> + return -ENOMEM;
> +
> + mp = netdev_priv(dev);
> + platform_set_drvdata(pdev, mp);
> + mp->dev = dev;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res)
> + return -ENOMEM;
> +
> + mp->shared = devm_kzalloc(&pdev->dev,
> + sizeof(struct mvberlin_eth_shared_private),
> + GFP_KERNEL);
> + if (!mp->shared)
> + return -ENOMEM;
> +
> + mp->shared->base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(mp->shared->base))
> + return PTR_ERR(mp->shared->base);
> + mp->base = mp->shared->base + 0x400;
> +
> + mp->clk = devm_clk_get(&pdev->dev, NULL);
> + if (!IS_ERR(mp->clk)) {
> + clk_prepare_enable(mp->clk);
> + mp->t_clk = clk_get_rate(mp->clk);
> + }
> +
The binding doesn't declare the clock as optional, I'd say you should enforce
the requirement here.
> + set_params(mp, pd);
> + netif_set_real_num_tx_queues(dev, mp->txq_count);
> + netif_set_real_num_rx_queues(dev, mp->rxq_count);
> +
> + pd->phy_node = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0);
> + if (!pd->phy_node) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + mp->phy = of_phy_connect(dev, pd->phy_node,
> + mvberlin_eth_adjust_link, 0,
> + PHY_INTERFACE_MODE_RGMII);
> + if (!mp->phy) {
> + ret = -EPROBE_DEFER;
> + goto out;
> + }
> +
> + dev->ethtool_ops = &mvberlin_eth_ethtool_ops;
> +
> + init_pscr(mp);
> +
> + init_hash_table(mp);
> + mvberlin_eth_program_unicast_filter(mp, NULL, dev->dev_addr);
> +
> + mib_counters_clear(mp);
> +
> + INIT_WORK(&mp->tx_timeout_task, tx_timeout_task);
> +
> + netif_napi_add(dev, &mp->napi, mvberlin_eth_poll, NAPI_POLL_WEIGHT);
> +
> + init_timer(&mp->rx_oom);
> + mp->rx_oom.data = (unsigned long)mp;
> + mp->rx_oom.function = oom_timer_wrapper;
> +
> + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
> + BUG_ON(!res);
Hm... BUG_ON!?!? Are you sure you want to kill the entire system?
There's another BUG_ON above, and since this is just network driver,
I think it can be relaxed.
> + dev->irq = res->start;
> +
> + dev->netdev_ops = &mvberlin_eth_netdev_ops;
> +
> + dev->watchdog_timeo = 2 * HZ;
> + dev->base_addr = 0;
> +
> + SET_NETDEV_DEV(dev, &pdev->dev);
> +
> + wrlp(mp, SDMA_CONFIG, PORT_SDMA_CONFIG_DEFAULT_VALUE);
> +
> + ret = register_netdev(dev);
> + if (ret)
> + goto out;
> +
> + netif_carrier_off(dev);
> +
> + return 0;
> +
> +out:
> + if (!IS_ERR(mp->clk))
> + clk_disable_unprepare(mp->clk);
> + free_netdev(dev);
> +
> + return ret;
> +}
> +
> +static int mvberlin_eth_remove(struct platform_device *pdev)
> +{
> + struct mvberlin_eth_private *mp = platform_get_drvdata(pdev);
> +
> + unregister_netdev(mp->dev);
> + if (mp->phy != NULL)
> + phy_disconnect(mp->phy);
> + cancel_work_sync(&mp->tx_timeout_task);
> +
> + if (!IS_ERR(mp->clk))
> + clk_disable_unprepare(mp->clk);
> +
Ditto for the optional clock.
--
Ezequiel García, Free Electrons
Embedded Linux, Kernel and Android Engineering
http://free-electrons.com
More information about the linux-arm-kernel
mailing list