mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-22 15:02:32 +00:00
cddd459140
Add patches for linux-5.4. The patches are from NXP LSDK-20.04 release which was tagged LSDK-20.04-V5.4. https://source.codeaurora.org/external/qoriq/qoriq-components/linux/ For boards LS1021A-IOT, and Traverse-LS1043 which are not involved in LSDK, port the dts patches from 4.14. The patches are sorted into the following categories: 301-arch-xxxx 302-dts-xxxx 303-core-xxxx 701-net-xxxx 801-audio-xxxx 802-can-xxxx 803-clock-xxxx 804-crypto-xxxx 805-display-xxxx 806-dma-xxxx 807-gpio-xxxx 808-i2c-xxxx 809-jailhouse-xxxx 810-keys-xxxx 811-kvm-xxxx 812-pcie-xxxx 813-pm-xxxx 814-qe-xxxx 815-sata-xxxx 816-sdhc-xxxx 817-spi-xxxx 818-thermal-xxxx 819-uart-xxxx 820-usb-xxxx 821-vfio-xxxx Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
12970 lines
377 KiB
Diff
12970 lines
377 KiB
Diff
From f7f94b1e7e9c6044a23bab1c5e773f6259f2d3e0 Mon Sep 17 00:00:00 2001
|
|
From: Madalin Bucur <madalin.bucur@nxp.com>
|
|
Date: Wed, 10 May 2017 16:39:42 +0300
|
|
Subject: [PATCH] dpa: SDK DPAA 1.x Ethernet driver
|
|
|
|
Signed-off-by: Madalin Bucur <madalin.bucur@nxp.com>
|
|
---
|
|
drivers/net/ethernet/freescale/sdk_dpaa/Kconfig | 173 ++
|
|
drivers/net/ethernet/freescale/sdk_dpaa/Makefile | 46 +
|
|
.../net/ethernet/freescale/sdk_dpaa/dpaa_1588.c | 580 ++++++
|
|
.../net/ethernet/freescale/sdk_dpaa/dpaa_1588.h | 138 ++
|
|
.../net/ethernet/freescale/sdk_dpaa/dpaa_debugfs.c | 180 ++
|
|
.../net/ethernet/freescale/sdk_dpaa/dpaa_debugfs.h | 43 +
|
|
drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth.c | 1210 ++++++++++++
|
|
drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth.h | 697 +++++++
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_base.c | 263 +++
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_base.h | 50 +
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_ceetm.c | 1991 ++++++++++++++++++++
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_ceetm.h | 236 +++
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_common.c | 1812 ++++++++++++++++++
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_common.h | 226 +++
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_proxy.c | 381 ++++
|
|
.../net/ethernet/freescale/sdk_dpaa/dpaa_eth_sg.c | 1113 +++++++++++
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_sysfs.c | 278 +++
|
|
.../ethernet/freescale/sdk_dpaa/dpaa_eth_trace.h | 144 ++
|
|
.../net/ethernet/freescale/sdk_dpaa/dpaa_ethtool.c | 544 ++++++
|
|
drivers/net/ethernet/freescale/sdk_dpaa/dpaa_ptp.c | 290 +++
|
|
drivers/net/ethernet/freescale/sdk_dpaa/mac-api.c | 909 +++++++++
|
|
drivers/net/ethernet/freescale/sdk_dpaa/mac.c | 489 +++++
|
|
drivers/net/ethernet/freescale/sdk_dpaa/mac.h | 135 ++
|
|
.../net/ethernet/freescale/sdk_dpaa/offline_port.c | 848 +++++++++
|
|
.../net/ethernet/freescale/sdk_dpaa/offline_port.h | 59 +
|
|
25 files changed, 12835 insertions(+)
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/Kconfig
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/Makefile
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_1588.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_1588.h
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_debugfs.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_debugfs.h
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth.h
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_base.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_base.h
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_ceetm.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_ceetm.h
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_common.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_common.h
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_proxy.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_sg.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_sysfs.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_trace.h
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_ethtool.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/dpaa_ptp.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/mac-api.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/mac.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/mac.h
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/offline_port.c
|
|
create mode 100644 drivers/net/ethernet/freescale/sdk_dpaa/offline_port.h
|
|
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/Kconfig
|
|
@@ -0,0 +1,173 @@
|
|
+menuconfig FSL_SDK_DPAA_ETH
|
|
+ tristate "DPAA Ethernet"
|
|
+ depends on (FSL_SOC || ARM64 || ARM) && FSL_SDK_BMAN && FSL_SDK_QMAN && FSL_SDK_FMAN && !FSL_DPAA_ETH
|
|
+ select PHYLIB
|
|
+ help
|
|
+ Data Path Acceleration Architecture Ethernet driver,
|
|
+ supporting the Freescale QorIQ chips.
|
|
+ Depends on Freescale Buffer Manager and Queue Manager
|
|
+ driver and Frame Manager Driver.
|
|
+
|
|
+if FSL_SDK_DPAA_ETH
|
|
+
|
|
+config FSL_DPAA_HOOKS
|
|
+ bool "DPAA Ethernet driver hooks"
|
|
+
|
|
+config FSL_DPAA_CEETM
|
|
+ bool "DPAA CEETM QoS"
|
|
+ depends on NET_SCHED
|
|
+ default n
|
|
+ help
|
|
+ Enable QoS offloading support through the CEETM hardware block.
|
|
+
|
|
+config FSL_DPAA_OFFLINE_PORTS
|
|
+ bool "Offline Ports support"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ default y
|
|
+ help
|
|
+ The Offline Parsing / Host Command ports (short: OH ports, of Offline ports) provide
|
|
+ most of the functionality of the regular, online ports, except they receive their
|
|
+ frames from a core or an accelerator on the SoC, via QMan frame queues,
|
|
+ rather than directly from the network.
|
|
+ Offline ports are configured via PCD (Parse-Classify-Distribute) schemes, just like
|
|
+ any online FMan port. They deliver the processed frames to frame queues, according
|
|
+ to the applied PCD configurations.
|
|
+
|
|
+ Choosing this feature will not impact the functionality and/or performance of the system,
|
|
+ so it is safe to have it.
|
|
+
|
|
+config FSL_DPAA_ADVANCED_DRIVERS
|
|
+ bool "Advanced DPAA Ethernet drivers"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ default y
|
|
+ help
|
|
+ Besides the standard DPAA Ethernet driver the DPAA Proxy initialization driver
|
|
+ is needed to support advanced scenarios. Select this to also build the advanced
|
|
+ drivers.
|
|
+
|
|
+config FSL_DPAA_ETH_JUMBO_FRAME
|
|
+ bool "Optimize for jumbo frames"
|
|
+ default n
|
|
+ help
|
|
+ Optimize the DPAA Ethernet driver throughput for large frames
|
|
+ termination traffic (e.g. 4K and above).
|
|
+ NOTE: This option can only be used if FSL_FM_MAX_FRAME_SIZE
|
|
+ is set to 9600 bytes.
|
|
+ Using this option in combination with small frames increases
|
|
+ significantly the driver's memory footprint and may even deplete
|
|
+ the system memory. Also, the skb truesize is altered and messages
|
|
+ from the stack that warn against this are bypassed.
|
|
+ This option is not available on LS1043.
|
|
+
|
|
+config FSL_DPAA_TS
|
|
+ bool "Linux compliant timestamping"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ default n
|
|
+ help
|
|
+ Enable Linux API compliant timestamping support.
|
|
+
|
|
+config FSL_DPAA_1588
|
|
+ bool "IEEE 1588-compliant timestamping"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ select FSL_DPAA_TS
|
|
+ default n
|
|
+ help
|
|
+ Enable IEEE1588 support code.
|
|
+
|
|
+config FSL_DPAA_ETH_USE_NDO_SELECT_QUEUE
|
|
+ bool "Use driver's Tx queue selection mechanism"
|
|
+ default y
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ help
|
|
+ The DPAA-Ethernet driver defines a ndo_select_queue() callback for optimal selection
|
|
+ of the egress FQ. That will override the XPS support for this netdevice.
|
|
+ If for whatever reason you want to be in control of the egress FQ-to-CPU selection and mapping,
|
|
+ or simply don't want to use the driver's ndo_select_queue() callback, then unselect this
|
|
+ and use the standard XPS support instead.
|
|
+
|
|
+config FSL_DPAA_ETH_MAX_BUF_COUNT
|
|
+ int "Maximum nuber of buffers in private bpool"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ range 64 2048
|
|
+ default "128"
|
|
+ help
|
|
+ The maximum number of buffers to be by default allocated in the DPAA-Ethernet private port's
|
|
+ buffer pool. One needn't normally modify this, as it has probably been tuned for performance
|
|
+ already. This cannot be lower than DPAA_ETH_REFILL_THRESHOLD.
|
|
+
|
|
+config FSL_DPAA_ETH_REFILL_THRESHOLD
|
|
+ int "Private bpool refill threshold"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ range 32 FSL_DPAA_ETH_MAX_BUF_COUNT
|
|
+ default "80"
|
|
+ help
|
|
+ The DPAA-Ethernet driver will start replenishing buffer pools whose count
|
|
+ falls below this threshold. This must be related to DPAA_ETH_MAX_BUF_COUNT. One needn't normally
|
|
+ modify this value unless one has very specific performance reasons.
|
|
+
|
|
+config FSL_DPAA_CS_THRESHOLD_1G
|
|
+ hex "Egress congestion threshold on 1G ports"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ range 0x1000 0x10000000
|
|
+ default "0x06000000"
|
|
+ help
|
|
+ The size in bytes of the egress Congestion State notification threshold on 1G ports.
|
|
+ The 1G dTSECs can quite easily be flooded by cores doing Tx in a tight loop
|
|
+ (e.g. by sending UDP datagrams at "while(1) speed"),
|
|
+ and the larger the frame size, the more acute the problem.
|
|
+ So we have to find a balance between these factors:
|
|
+ - avoiding the device staying congested for a prolonged time (risking
|
|
+ the netdev watchdog to fire - see also the tx_timeout module param);
|
|
+ - affecting performance of protocols such as TCP, which otherwise
|
|
+ behave well under the congestion notification mechanism;
|
|
+ - preventing the Tx cores from tightly-looping (as if the congestion
|
|
+ threshold was too low to be effective);
|
|
+ - running out of memory if the CS threshold is set too high.
|
|
+
|
|
+config FSL_DPAA_CS_THRESHOLD_10G
|
|
+ hex "Egress congestion threshold on 10G ports"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ range 0x1000 0x20000000
|
|
+ default "0x10000000"
|
|
+ help
|
|
+ The size in bytes of the egress Congestion State notification threshold on 10G ports.
|
|
+
|
|
+config FSL_DPAA_INGRESS_CS_THRESHOLD
|
|
+ hex "Ingress congestion threshold on FMan ports"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ default "0x10000000"
|
|
+ help
|
|
+ The size in bytes of the ingress tail-drop threshold on FMan ports.
|
|
+ Traffic piling up above this value will be rejected by QMan and discarded by FMan.
|
|
+
|
|
+config FSL_DPAA_ETH_DEBUGFS
|
|
+ bool "DPAA Ethernet debugfs interface"
|
|
+ depends on DEBUG_FS && FSL_SDK_DPAA_ETH
|
|
+ default y
|
|
+ help
|
|
+ This option compiles debugfs code for the DPAA Ethernet driver.
|
|
+
|
|
+config FSL_DPAA_ETH_DEBUG
|
|
+ bool "DPAA Ethernet Debug Support"
|
|
+ depends on FSL_SDK_DPAA_ETH
|
|
+ default n
|
|
+ help
|
|
+ This option compiles debug code for the DPAA Ethernet driver.
|
|
+
|
|
+config FSL_DPAA_DBG_LOOP
|
|
+ bool "DPAA Ethernet Debug loopback"
|
|
+ depends on FSL_DPAA_ETH_DEBUGFS && FSL_DPAA_ETH_USE_NDO_SELECT_QUEUE
|
|
+ default n
|
|
+ help
|
|
+ This option allows to divert all received traffic on a certain interface A towards a
|
|
+ selected interface B. This option is used to benchmark the HW + Ethernet driver in
|
|
+ isolation from the Linux networking stack. The loops are controlled by debugfs entries,
|
|
+ one for each interface. By default all loops are disabled (target value is -1). I.e. to
|
|
+ change the loop setting for interface 4 and divert all received traffic to interface 5
|
|
+ write Tx interface number in the receive interface debugfs file:
|
|
+ # cat /sys/kernel/debug/powerpc/fsl_dpa/eth4_loop
|
|
+ 4->-1
|
|
+ # echo 5 > /sys/kernel/debug/powerpc/fsl_dpa/eth4_loop
|
|
+ # cat /sys/kernel/debug/powerpc/fsl_dpa/eth4_loop
|
|
+ 4->5
|
|
+endif # FSL_SDK_DPAA_ETH
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/Makefile
|
|
@@ -0,0 +1,46 @@
|
|
+#
|
|
+# Makefile for the Freescale Ethernet controllers
|
|
+#
|
|
+ccflags-y += -DVERSION=\"\"
|
|
+#
|
|
+# Include netcomm SW specific definitions
|
|
+include $(srctree)/drivers/net/ethernet/freescale/sdk_fman/ncsw_config.mk
|
|
+
|
|
+ccflags-y += -I$(NET_DPA)
|
|
+
|
|
+obj-$(CONFIG_FSL_SDK_DPAA_ETH) += fsl_mac.o fsl_dpa.o
|
|
+obj-$(CONFIG_PTP_1588_CLOCK_DPAA) += dpaa_ptp.o
|
|
+
|
|
+fsl_dpa-objs += dpaa_ethtool.o dpaa_eth_sysfs.o dpaa_eth.o dpaa_eth_sg.o dpaa_eth_common.o
|
|
+ifeq ($(CONFIG_FSL_DPAA_DBG_LOOP),y)
|
|
+fsl_dpa-objs += dpaa_debugfs.o
|
|
+endif
|
|
+ifeq ($(CONFIG_FSL_DPAA_1588),y)
|
|
+fsl_dpa-objs += dpaa_1588.o
|
|
+endif
|
|
+ifeq ($(CONFIG_FSL_DPAA_CEETM),y)
|
|
+ccflags-y += -Idrivers/net/ethernet/freescale/sdk_fman/src/wrapper
|
|
+fsl_dpa-objs += dpaa_eth_ceetm.o
|
|
+endif
|
|
+
|
|
+fsl_mac-objs += mac.o mac-api.o
|
|
+
|
|
+# Advanced drivers
|
|
+ifeq ($(CONFIG_FSL_DPAA_ADVANCED_DRIVERS),y)
|
|
+obj-$(CONFIG_FSL_SDK_DPAA_ETH) += fsl_advanced.o
|
|
+obj-$(CONFIG_FSL_SDK_DPAA_ETH) += fsl_proxy.o
|
|
+
|
|
+fsl_advanced-objs += dpaa_eth_base.o
|
|
+# suport for multiple drivers per kernel module comes in kernel 3.14
|
|
+# so we are forced to generate several modules for the advanced drivers
|
|
+fsl_proxy-objs += dpaa_eth_proxy.o
|
|
+
|
|
+ifeq ($(CONFIG_FSL_DPAA_OFFLINE_PORTS),y)
|
|
+obj-$(CONFIG_FSL_SDK_DPAA_ETH) += fsl_oh.o
|
|
+
|
|
+fsl_oh-objs += offline_port.o
|
|
+endif
|
|
+endif
|
|
+
|
|
+# Needed by the tracing framework
|
|
+CFLAGS_dpaa_eth.o := -I$(src)
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_1588.c
|
|
@@ -0,0 +1,580 @@
|
|
+/* Copyright (C) 2011 Freescale Semiconductor, Inc.
|
|
+ * Copyright (C) 2009 IXXAT Automation, GmbH
|
|
+ *
|
|
+ * DPAA Ethernet Driver -- IEEE 1588 interface functionality
|
|
+ *
|
|
+ * 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, write to the Free Software Foundation, Inc.,
|
|
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
+ *
|
|
+ */
|
|
+#include <linux/io.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <linux/spinlock.h>
|
|
+#include <linux/ip.h>
|
|
+#include <linux/ipv6.h>
|
|
+#include <linux/udp.h>
|
|
+#include <asm/div64.h>
|
|
+#include "dpaa_eth.h"
|
|
+#include "dpaa_eth_common.h"
|
|
+#include "dpaa_1588.h"
|
|
+#include "mac.h"
|
|
+
|
|
+static int dpa_ptp_init_circ(struct dpa_ptp_circ_buf *ptp_buf, u32 size)
|
|
+{
|
|
+ struct circ_buf *circ_buf = &ptp_buf->circ_buf;
|
|
+
|
|
+ circ_buf->buf = vmalloc(sizeof(struct dpa_ptp_data) * size);
|
|
+ if (!circ_buf->buf)
|
|
+ return 1;
|
|
+
|
|
+ circ_buf->head = 0;
|
|
+ circ_buf->tail = 0;
|
|
+ ptp_buf->size = size;
|
|
+ spin_lock_init(&ptp_buf->ptp_lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dpa_ptp_reset_circ(struct dpa_ptp_circ_buf *ptp_buf, u32 size)
|
|
+{
|
|
+ struct circ_buf *circ_buf = &ptp_buf->circ_buf;
|
|
+
|
|
+ circ_buf->head = 0;
|
|
+ circ_buf->tail = 0;
|
|
+ ptp_buf->size = size;
|
|
+}
|
|
+
|
|
+static int dpa_ptp_insert(struct dpa_ptp_circ_buf *ptp_buf,
|
|
+ struct dpa_ptp_data *data)
|
|
+{
|
|
+ struct circ_buf *circ_buf = &ptp_buf->circ_buf;
|
|
+ int size = ptp_buf->size;
|
|
+ struct dpa_ptp_data *tmp;
|
|
+ unsigned long flags;
|
|
+ int head, tail;
|
|
+
|
|
+ spin_lock_irqsave(&ptp_buf->ptp_lock, flags);
|
|
+
|
|
+ head = circ_buf->head;
|
|
+ tail = circ_buf->tail;
|
|
+
|
|
+ if (CIRC_SPACE(head, tail, size) <= 0)
|
|
+ circ_buf->tail = (tail + 1) & (size - 1);
|
|
+
|
|
+ tmp = (struct dpa_ptp_data *)(circ_buf->buf) + head;
|
|
+ memcpy(tmp, data, sizeof(struct dpa_ptp_data));
|
|
+
|
|
+ circ_buf->head = (head + 1) & (size - 1);
|
|
+
|
|
+ spin_unlock_irqrestore(&ptp_buf->ptp_lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dpa_ptp_is_ident_match(struct dpa_ptp_ident *dst,
|
|
+ struct dpa_ptp_ident *src)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ if ((dst->version != src->version) || (dst->msg_type != src->msg_type))
|
|
+ return 0;
|
|
+
|
|
+ if ((dst->netw_prot == src->netw_prot)
|
|
+ || src->netw_prot == DPA_PTP_PROT_DONTCARE) {
|
|
+ if (dst->seq_id != src->seq_id)
|
|
+ return 0;
|
|
+
|
|
+ ret = memcmp(dst->snd_port_id, src->snd_port_id,
|
|
+ DPA_PTP_SOURCE_PORT_LENGTH);
|
|
+ if (ret)
|
|
+ return 0;
|
|
+ else
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dpa_ptp_find_and_remove(struct dpa_ptp_circ_buf *ptp_buf,
|
|
+ struct dpa_ptp_ident *ident,
|
|
+ struct dpa_ptp_time *ts)
|
|
+{
|
|
+ struct circ_buf *circ_buf = &ptp_buf->circ_buf;
|
|
+ int size = ptp_buf->size;
|
|
+ int head, tail, idx;
|
|
+ unsigned long flags;
|
|
+ struct dpa_ptp_data *tmp, *tmp2;
|
|
+ struct dpa_ptp_ident *tmp_ident;
|
|
+
|
|
+ spin_lock_irqsave(&ptp_buf->ptp_lock, flags);
|
|
+
|
|
+ head = circ_buf->head;
|
|
+ tail = idx = circ_buf->tail;
|
|
+
|
|
+ if (CIRC_CNT(head, tail, size) == 0) {
|
|
+ spin_unlock_irqrestore(&ptp_buf->ptp_lock, flags);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ while (idx != head) {
|
|
+ tmp = (struct dpa_ptp_data *)(circ_buf->buf) + idx;
|
|
+ tmp_ident = &tmp->ident;
|
|
+ if (dpa_ptp_is_ident_match(tmp_ident, ident))
|
|
+ break;
|
|
+ idx = (idx + 1) & (size - 1);
|
|
+ }
|
|
+
|
|
+ if (idx == head) {
|
|
+ spin_unlock_irqrestore(&ptp_buf->ptp_lock, flags);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ ts->sec = tmp->ts.sec;
|
|
+ ts->nsec = tmp->ts.nsec;
|
|
+
|
|
+ if (idx != tail) {
|
|
+ if (CIRC_CNT(idx, tail, size) > TS_ACCUMULATION_THRESHOLD) {
|
|
+ tail = circ_buf->tail =
|
|
+ (idx - TS_ACCUMULATION_THRESHOLD) & (size - 1);
|
|
+ }
|
|
+
|
|
+ while (CIRC_CNT(idx, tail, size) > 0) {
|
|
+ tmp = (struct dpa_ptp_data *)(circ_buf->buf) + idx;
|
|
+ idx = (idx - 1) & (size - 1);
|
|
+ tmp2 = (struct dpa_ptp_data *)(circ_buf->buf) + idx;
|
|
+ *tmp = *tmp2;
|
|
+ }
|
|
+ }
|
|
+ circ_buf->tail = (tail + 1) & (size - 1);
|
|
+
|
|
+ spin_unlock_irqrestore(&ptp_buf->ptp_lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Parse the PTP packets
|
|
+ *
|
|
+ * The PTP header can be found in an IPv4 packet, IPv6 patcket or in
|
|
+ * an IEEE802.3 ethernet frame. This function returns the position of
|
|
+ * the PTP packet or NULL if no PTP found
|
|
+ */
|
|
+static u8 *dpa_ptp_parse_packet(struct sk_buff *skb, u16 *eth_type)
|
|
+{
|
|
+ u8 *pos = skb->data + ETH_ALEN + ETH_ALEN;
|
|
+ u8 *ptp_loc = NULL;
|
|
+ u8 msg_type;
|
|
+ u32 access_len = ETH_ALEN + ETH_ALEN + DPA_ETYPE_LEN;
|
|
+ struct iphdr *iph;
|
|
+ struct udphdr *udph;
|
|
+ struct ipv6hdr *ipv6h;
|
|
+
|
|
+ /* when we can receive S/G frames we need to check the data we want to
|
|
+ * access is in the linear skb buffer
|
|
+ */
|
|
+ if (!pskb_may_pull(skb, access_len))
|
|
+ return NULL;
|
|
+
|
|
+ *eth_type = *((u16 *)pos);
|
|
+
|
|
+ /* Check if inner tag is here */
|
|
+ if (*eth_type == ETH_P_8021Q) {
|
|
+ access_len += DPA_VLAN_TAG_LEN;
|
|
+
|
|
+ if (!pskb_may_pull(skb, access_len))
|
|
+ return NULL;
|
|
+
|
|
+ pos += DPA_VLAN_TAG_LEN;
|
|
+ *eth_type = *((u16 *)pos);
|
|
+ }
|
|
+
|
|
+ pos += DPA_ETYPE_LEN;
|
|
+
|
|
+ switch (*eth_type) {
|
|
+ /* Transport of PTP over Ethernet */
|
|
+ case ETH_P_1588:
|
|
+ ptp_loc = pos;
|
|
+
|
|
+ if (!pskb_may_pull(skb, access_len + PTP_OFFS_MSG_TYPE + 1))
|
|
+ return NULL;
|
|
+
|
|
+ msg_type = *((u8 *)(ptp_loc + PTP_OFFS_MSG_TYPE)) & 0xf;
|
|
+ if ((msg_type == PTP_MSGTYPE_SYNC)
|
|
+ || (msg_type == PTP_MSGTYPE_DELREQ)
|
|
+ || (msg_type == PTP_MSGTYPE_PDELREQ)
|
|
+ || (msg_type == PTP_MSGTYPE_PDELRESP))
|
|
+ return ptp_loc;
|
|
+ break;
|
|
+ /* Transport of PTP over IPv4 */
|
|
+ case ETH_P_IP:
|
|
+ iph = (struct iphdr *)pos;
|
|
+ access_len += sizeof(struct iphdr);
|
|
+
|
|
+ if (!pskb_may_pull(skb, access_len))
|
|
+ return NULL;
|
|
+
|
|
+ if (ntohs(iph->protocol) != IPPROTO_UDP)
|
|
+ return NULL;
|
|
+
|
|
+ access_len += iph->ihl * 4 - sizeof(struct iphdr) +
|
|
+ sizeof(struct udphdr);
|
|
+
|
|
+ if (!pskb_may_pull(skb, access_len))
|
|
+ return NULL;
|
|
+
|
|
+ pos += iph->ihl * 4;
|
|
+ udph = (struct udphdr *)pos;
|
|
+ if (ntohs(udph->dest) != 319)
|
|
+ return NULL;
|
|
+ ptp_loc = pos + sizeof(struct udphdr);
|
|
+ break;
|
|
+ /* Transport of PTP over IPv6 */
|
|
+ case ETH_P_IPV6:
|
|
+ ipv6h = (struct ipv6hdr *)pos;
|
|
+
|
|
+ access_len += sizeof(struct ipv6hdr) + sizeof(struct udphdr);
|
|
+
|
|
+ if (ntohs(ipv6h->nexthdr) != IPPROTO_UDP)
|
|
+ return NULL;
|
|
+
|
|
+ pos += sizeof(struct ipv6hdr);
|
|
+ udph = (struct udphdr *)pos;
|
|
+ if (ntohs(udph->dest) != 319)
|
|
+ return NULL;
|
|
+ ptp_loc = pos + sizeof(struct udphdr);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return ptp_loc;
|
|
+}
|
|
+
|
|
+static int dpa_ptp_store_stamp(const struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, void *data, enum port_type rx_tx,
|
|
+ struct dpa_ptp_data *ptp_data)
|
|
+{
|
|
+ u64 nsec;
|
|
+ u32 mod;
|
|
+ u8 *ptp_loc;
|
|
+ u16 eth_type;
|
|
+
|
|
+ ptp_loc = dpa_ptp_parse_packet(skb, ð_type);
|
|
+ if (!ptp_loc)
|
|
+ return -EINVAL;
|
|
+
|
|
+ switch (eth_type) {
|
|
+ case ETH_P_IP:
|
|
+ ptp_data->ident.netw_prot = DPA_PTP_PROT_IPV4;
|
|
+ break;
|
|
+ case ETH_P_IPV6:
|
|
+ ptp_data->ident.netw_prot = DPA_PTP_PROT_IPV6;
|
|
+ break;
|
|
+ case ETH_P_1588:
|
|
+ ptp_data->ident.netw_prot = DPA_PTP_PROT_802_3;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (!pskb_may_pull(skb, ptp_loc - skb->data + PTP_OFFS_SEQ_ID + 2))
|
|
+ return -EINVAL;
|
|
+
|
|
+ ptp_data->ident.version = *(ptp_loc + PTP_OFFS_VER_PTP) & 0xf;
|
|
+ ptp_data->ident.msg_type = *(ptp_loc + PTP_OFFS_MSG_TYPE) & 0xf;
|
|
+ ptp_data->ident.seq_id = *((u16 *)(ptp_loc + PTP_OFFS_SEQ_ID));
|
|
+ memcpy(ptp_data->ident.snd_port_id, ptp_loc + PTP_OFFS_SRCPRTID,
|
|
+ DPA_PTP_SOURCE_PORT_LENGTH);
|
|
+
|
|
+ nsec = dpa_get_timestamp_ns(priv, rx_tx, data);
|
|
+ mod = do_div(nsec, NANOSEC_PER_SECOND);
|
|
+ ptp_data->ts.sec = nsec;
|
|
+ ptp_data->ts.nsec = mod;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void dpa_ptp_store_txstamp(const struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, void *data)
|
|
+{
|
|
+ struct dpa_ptp_tsu *tsu = priv->tsu;
|
|
+ struct dpa_ptp_data ptp_tx_data;
|
|
+
|
|
+ if (dpa_ptp_store_stamp(priv, skb, data, TX, &ptp_tx_data))
|
|
+ return;
|
|
+
|
|
+ dpa_ptp_insert(&tsu->tx_timestamps, &ptp_tx_data);
|
|
+}
|
|
+
|
|
+void dpa_ptp_store_rxstamp(const struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, void *data)
|
|
+{
|
|
+ struct dpa_ptp_tsu *tsu = priv->tsu;
|
|
+ struct dpa_ptp_data ptp_rx_data;
|
|
+
|
|
+ if (dpa_ptp_store_stamp(priv, skb, data, RX, &ptp_rx_data))
|
|
+ return;
|
|
+
|
|
+ dpa_ptp_insert(&tsu->rx_timestamps, &ptp_rx_data);
|
|
+}
|
|
+
|
|
+static uint8_t dpa_get_tx_timestamp(struct dpa_ptp_tsu *ptp_tsu,
|
|
+ struct dpa_ptp_ident *ident,
|
|
+ struct dpa_ptp_time *ts)
|
|
+{
|
|
+ struct dpa_ptp_tsu *tsu = ptp_tsu;
|
|
+ struct dpa_ptp_time tmp;
|
|
+ int flag;
|
|
+
|
|
+ flag = dpa_ptp_find_and_remove(&tsu->tx_timestamps, ident, &tmp);
|
|
+ if (!flag) {
|
|
+ ts->sec = tmp.sec;
|
|
+ ts->nsec = tmp.nsec;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static uint8_t dpa_get_rx_timestamp(struct dpa_ptp_tsu *ptp_tsu,
|
|
+ struct dpa_ptp_ident *ident,
|
|
+ struct dpa_ptp_time *ts)
|
|
+{
|
|
+ struct dpa_ptp_tsu *tsu = ptp_tsu;
|
|
+ struct dpa_ptp_time tmp;
|
|
+ int flag;
|
|
+
|
|
+ flag = dpa_ptp_find_and_remove(&tsu->rx_timestamps, ident, &tmp);
|
|
+ if (!flag) {
|
|
+ ts->sec = tmp.sec;
|
|
+ ts->nsec = tmp.nsec;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static void dpa_set_fiper_alarm(struct dpa_ptp_tsu *tsu,
|
|
+ struct dpa_ptp_time *cnt_time)
|
|
+{
|
|
+ struct mac_device *mac_dev = tsu->dpa_priv->mac_dev;
|
|
+ u64 tmp, fiper;
|
|
+
|
|
+ if (mac_dev->fm_rtc_disable)
|
|
+ mac_dev->fm_rtc_disable(get_fm_handle(tsu->dpa_priv->net_dev));
|
|
+
|
|
+ /* TMR_FIPER1 will pulse every second after ALARM1 expired */
|
|
+ tmp = (u64)cnt_time->sec * NANOSEC_PER_SECOND + (u64)cnt_time->nsec;
|
|
+ fiper = NANOSEC_PER_SECOND - DPA_PTP_NOMINAL_FREQ_PERIOD_NS;
|
|
+ if (mac_dev->fm_rtc_set_alarm)
|
|
+ mac_dev->fm_rtc_set_alarm(get_fm_handle(tsu->dpa_priv->net_dev),
|
|
+ 0, tmp);
|
|
+ if (mac_dev->fm_rtc_set_fiper)
|
|
+ mac_dev->fm_rtc_set_fiper(get_fm_handle(tsu->dpa_priv->net_dev),
|
|
+ 0, fiper);
|
|
+
|
|
+ if (mac_dev->fm_rtc_enable)
|
|
+ mac_dev->fm_rtc_enable(get_fm_handle(tsu->dpa_priv->net_dev));
|
|
+}
|
|
+
|
|
+static void dpa_get_curr_cnt(struct dpa_ptp_tsu *tsu,
|
|
+ struct dpa_ptp_time *curr_time)
|
|
+{
|
|
+ struct mac_device *mac_dev = tsu->dpa_priv->mac_dev;
|
|
+ u64 tmp;
|
|
+ u32 mod;
|
|
+
|
|
+ if (mac_dev->fm_rtc_get_cnt)
|
|
+ mac_dev->fm_rtc_get_cnt(get_fm_handle(tsu->dpa_priv->net_dev),
|
|
+ &tmp);
|
|
+
|
|
+ mod = do_div(tmp, NANOSEC_PER_SECOND);
|
|
+ curr_time->sec = (u32)tmp;
|
|
+ curr_time->nsec = mod;
|
|
+}
|
|
+
|
|
+static void dpa_set_1588cnt(struct dpa_ptp_tsu *tsu,
|
|
+ struct dpa_ptp_time *cnt_time)
|
|
+{
|
|
+ struct mac_device *mac_dev = tsu->dpa_priv->mac_dev;
|
|
+ u64 tmp;
|
|
+
|
|
+ tmp = (u64)cnt_time->sec * NANOSEC_PER_SECOND + (u64)cnt_time->nsec;
|
|
+
|
|
+ if (mac_dev->fm_rtc_set_cnt)
|
|
+ mac_dev->fm_rtc_set_cnt(get_fm_handle(tsu->dpa_priv->net_dev),
|
|
+ tmp);
|
|
+
|
|
+ /* Restart fiper two seconds later */
|
|
+ cnt_time->sec += 2;
|
|
+ cnt_time->nsec = 0;
|
|
+ dpa_set_fiper_alarm(tsu, cnt_time);
|
|
+}
|
|
+
|
|
+static void dpa_get_drift(struct dpa_ptp_tsu *tsu, u32 *addend)
|
|
+{
|
|
+ struct mac_device *mac_dev = tsu->dpa_priv->mac_dev;
|
|
+ u32 drift;
|
|
+
|
|
+ if (mac_dev->fm_rtc_get_drift)
|
|
+ mac_dev->fm_rtc_get_drift(get_fm_handle(tsu->dpa_priv->net_dev),
|
|
+ &drift);
|
|
+
|
|
+ *addend = drift;
|
|
+}
|
|
+
|
|
+static void dpa_set_drift(struct dpa_ptp_tsu *tsu, u32 addend)
|
|
+{
|
|
+ struct mac_device *mac_dev = tsu->dpa_priv->mac_dev;
|
|
+
|
|
+ if (mac_dev->fm_rtc_set_drift)
|
|
+ mac_dev->fm_rtc_set_drift(get_fm_handle(tsu->dpa_priv->net_dev),
|
|
+ addend);
|
|
+}
|
|
+
|
|
+static void dpa_flush_timestamp(struct dpa_ptp_tsu *tsu)
|
|
+{
|
|
+ dpa_ptp_reset_circ(&tsu->rx_timestamps, DEFAULT_PTP_RX_BUF_SZ);
|
|
+ dpa_ptp_reset_circ(&tsu->tx_timestamps, DEFAULT_PTP_TX_BUF_SZ);
|
|
+}
|
|
+
|
|
+int dpa_ioctl_1588(struct net_device *dev, struct ifreq *ifr, int cmd)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(dev);
|
|
+ struct dpa_ptp_tsu *tsu = priv->tsu;
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+ struct dpa_ptp_data ptp_data;
|
|
+ struct dpa_ptp_data *ptp_data_user;
|
|
+ struct dpa_ptp_time act_time;
|
|
+ u32 addend;
|
|
+ int retval = 0;
|
|
+
|
|
+ if (!tsu || !tsu->valid)
|
|
+ return -ENODEV;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case PTP_ENBL_TXTS_IOCTL:
|
|
+ tsu->hwts_tx_en_ioctl = 1;
|
|
+ if (mac_dev->fm_rtc_enable)
|
|
+ mac_dev->fm_rtc_enable(get_fm_handle(dev));
|
|
+ if (mac_dev->ptp_enable)
|
|
+ mac_dev->ptp_enable(mac_dev->get_mac_handle(mac_dev));
|
|
+ break;
|
|
+ case PTP_DSBL_TXTS_IOCTL:
|
|
+ tsu->hwts_tx_en_ioctl = 0;
|
|
+ if (mac_dev->fm_rtc_disable)
|
|
+ mac_dev->fm_rtc_disable(get_fm_handle(dev));
|
|
+ if (mac_dev->ptp_disable)
|
|
+ mac_dev->ptp_disable(mac_dev->get_mac_handle(mac_dev));
|
|
+ break;
|
|
+ case PTP_ENBL_RXTS_IOCTL:
|
|
+ tsu->hwts_rx_en_ioctl = 1;
|
|
+ break;
|
|
+ case PTP_DSBL_RXTS_IOCTL:
|
|
+ tsu->hwts_rx_en_ioctl = 0;
|
|
+ break;
|
|
+ case PTP_GET_RX_TIMESTAMP:
|
|
+ ptp_data_user = (struct dpa_ptp_data *)ifr->ifr_data;
|
|
+ if (copy_from_user(&ptp_data.ident,
|
|
+ &ptp_data_user->ident, sizeof(ptp_data.ident)))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dpa_get_rx_timestamp(tsu, &ptp_data.ident, &ptp_data.ts))
|
|
+ return -EAGAIN;
|
|
+
|
|
+ if (copy_to_user((void __user *)&ptp_data_user->ts,
|
|
+ &ptp_data.ts, sizeof(ptp_data.ts)))
|
|
+ return -EFAULT;
|
|
+ break;
|
|
+ case PTP_GET_TX_TIMESTAMP:
|
|
+ ptp_data_user = (struct dpa_ptp_data *)ifr->ifr_data;
|
|
+ if (copy_from_user(&ptp_data.ident,
|
|
+ &ptp_data_user->ident, sizeof(ptp_data.ident)))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dpa_get_tx_timestamp(tsu, &ptp_data.ident, &ptp_data.ts))
|
|
+ return -EAGAIN;
|
|
+
|
|
+ if (copy_to_user((void __user *)&ptp_data_user->ts,
|
|
+ &ptp_data.ts, sizeof(ptp_data.ts)))
|
|
+ return -EFAULT;
|
|
+ break;
|
|
+ case PTP_GET_TIME:
|
|
+ dpa_get_curr_cnt(tsu, &act_time);
|
|
+ if (copy_to_user(ifr->ifr_data, &act_time, sizeof(act_time)))
|
|
+ return -EFAULT;
|
|
+ break;
|
|
+ case PTP_SET_TIME:
|
|
+ if (copy_from_user(&act_time, ifr->ifr_data, sizeof(act_time)))
|
|
+ return -EINVAL;
|
|
+ dpa_set_1588cnt(tsu, &act_time);
|
|
+ break;
|
|
+ case PTP_GET_ADJ:
|
|
+ dpa_get_drift(tsu, &addend);
|
|
+ if (copy_to_user(ifr->ifr_data, &addend, sizeof(addend)))
|
|
+ return -EFAULT;
|
|
+ break;
|
|
+ case PTP_SET_ADJ:
|
|
+ if (copy_from_user(&addend, ifr->ifr_data, sizeof(addend)))
|
|
+ return -EINVAL;
|
|
+ dpa_set_drift(tsu, addend);
|
|
+ break;
|
|
+ case PTP_SET_FIPER_ALARM:
|
|
+ if (copy_from_user(&act_time, ifr->ifr_data, sizeof(act_time)))
|
|
+ return -EINVAL;
|
|
+ dpa_set_fiper_alarm(tsu, &act_time);
|
|
+ break;
|
|
+ case PTP_CLEANUP_TS:
|
|
+ dpa_flush_timestamp(tsu);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+int dpa_ptp_init(struct dpa_priv_s *priv)
|
|
+{
|
|
+ struct dpa_ptp_tsu *tsu;
|
|
+
|
|
+ /* Allocate memory for PTP structure */
|
|
+ tsu = kzalloc(sizeof(struct dpa_ptp_tsu), GFP_KERNEL);
|
|
+ if (!tsu)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ tsu->valid = TRUE;
|
|
+ tsu->dpa_priv = priv;
|
|
+
|
|
+ dpa_ptp_init_circ(&tsu->rx_timestamps, DEFAULT_PTP_RX_BUF_SZ);
|
|
+ dpa_ptp_init_circ(&tsu->tx_timestamps, DEFAULT_PTP_TX_BUF_SZ);
|
|
+
|
|
+ priv->tsu = tsu;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_ptp_init);
|
|
+
|
|
+void dpa_ptp_cleanup(struct dpa_priv_s *priv)
|
|
+{
|
|
+ struct dpa_ptp_tsu *tsu = priv->tsu;
|
|
+
|
|
+ tsu->valid = FALSE;
|
|
+ vfree(tsu->rx_timestamps.circ_buf.buf);
|
|
+ vfree(tsu->tx_timestamps.circ_buf.buf);
|
|
+
|
|
+ kfree(tsu);
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_ptp_cleanup);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_1588.h
|
|
@@ -0,0 +1,138 @@
|
|
+/* Copyright (C) 2011 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License along
|
|
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
+ *
|
|
+ */
|
|
+#ifndef __DPAA_1588_H__
|
|
+#define __DPAA_1588_H__
|
|
+
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/circ_buf.h>
|
|
+#include <linux/fsl_qman.h>
|
|
+
|
|
+#define DEFAULT_PTP_RX_BUF_SZ 256
|
|
+#define DEFAULT_PTP_TX_BUF_SZ 256
|
|
+
|
|
+/* 1588 private ioctl calls */
|
|
+#define PTP_ENBL_TXTS_IOCTL SIOCDEVPRIVATE
|
|
+#define PTP_DSBL_TXTS_IOCTL (SIOCDEVPRIVATE + 1)
|
|
+#define PTP_ENBL_RXTS_IOCTL (SIOCDEVPRIVATE + 2)
|
|
+#define PTP_DSBL_RXTS_IOCTL (SIOCDEVPRIVATE + 3)
|
|
+#define PTP_GET_TX_TIMESTAMP (SIOCDEVPRIVATE + 4)
|
|
+#define PTP_GET_RX_TIMESTAMP (SIOCDEVPRIVATE + 5)
|
|
+#define PTP_SET_TIME (SIOCDEVPRIVATE + 6)
|
|
+#define PTP_GET_TIME (SIOCDEVPRIVATE + 7)
|
|
+#define PTP_SET_FIPER_ALARM (SIOCDEVPRIVATE + 8)
|
|
+#define PTP_SET_ADJ (SIOCDEVPRIVATE + 9)
|
|
+#define PTP_GET_ADJ (SIOCDEVPRIVATE + 10)
|
|
+#define PTP_CLEANUP_TS (SIOCDEVPRIVATE + 11)
|
|
+
|
|
+/* PTP V2 message type */
|
|
+enum {
|
|
+ PTP_MSGTYPE_SYNC = 0x0,
|
|
+ PTP_MSGTYPE_DELREQ = 0x1,
|
|
+ PTP_MSGTYPE_PDELREQ = 0x2,
|
|
+ PTP_MSGTYPE_PDELRESP = 0x3,
|
|
+ PTP_MSGTYPE_FLWUP = 0x8,
|
|
+ PTP_MSGTYPE_DELRESP = 0x9,
|
|
+ PTP_MSGTYPE_PDELRES_FLWUP = 0xA,
|
|
+ PTP_MSGTYPE_ANNOUNCE = 0xB,
|
|
+ PTP_MSGTYPE_SGNLNG = 0xC,
|
|
+ PTP_MSGTYPE_MNGMNT = 0xD,
|
|
+};
|
|
+
|
|
+/* Byte offset of data in the PTP V2 headers */
|
|
+#define PTP_OFFS_MSG_TYPE 0
|
|
+#define PTP_OFFS_VER_PTP 1
|
|
+#define PTP_OFFS_MSG_LEN 2
|
|
+#define PTP_OFFS_DOM_NMB 4
|
|
+#define PTP_OFFS_FLAGS 6
|
|
+#define PTP_OFFS_CORFIELD 8
|
|
+#define PTP_OFFS_SRCPRTID 20
|
|
+#define PTP_OFFS_SEQ_ID 30
|
|
+#define PTP_OFFS_CTRL 32
|
|
+#define PTP_OFFS_LOGMEAN 33
|
|
+
|
|
+#define PTP_IP_OFFS 14
|
|
+#define PTP_UDP_OFFS 34
|
|
+#define PTP_HEADER_OFFS 42
|
|
+#define PTP_MSG_TYPE_OFFS (PTP_HEADER_OFFS + PTP_OFFS_MSG_TYPE)
|
|
+#define PTP_SPORT_ID_OFFS (PTP_HEADER_OFFS + PTP_OFFS_SRCPRTID)
|
|
+#define PTP_SEQ_ID_OFFS (PTP_HEADER_OFFS + PTP_OFFS_SEQ_ID)
|
|
+#define PTP_CTRL_OFFS (PTP_HEADER_OFFS + PTP_OFFS_CTRL)
|
|
+
|
|
+/* 1588-2008 network protocol enumeration values */
|
|
+#define DPA_PTP_PROT_IPV4 1
|
|
+#define DPA_PTP_PROT_IPV6 2
|
|
+#define DPA_PTP_PROT_802_3 3
|
|
+#define DPA_PTP_PROT_DONTCARE 0xFFFF
|
|
+
|
|
+#define DPA_PTP_SOURCE_PORT_LENGTH 10
|
|
+#define DPA_PTP_HEADER_SZE 34
|
|
+#define DPA_ETYPE_LEN 2
|
|
+#define DPA_VLAN_TAG_LEN 4
|
|
+#define NANOSEC_PER_SECOND 1000000000
|
|
+
|
|
+/* The threshold between the current found one and the oldest one */
|
|
+#define TS_ACCUMULATION_THRESHOLD 50
|
|
+
|
|
+/* Struct needed to identify a timestamp */
|
|
+struct dpa_ptp_ident {
|
|
+ u8 version;
|
|
+ u8 msg_type;
|
|
+ u16 netw_prot;
|
|
+ u16 seq_id;
|
|
+ u8 snd_port_id[DPA_PTP_SOURCE_PORT_LENGTH];
|
|
+};
|
|
+
|
|
+/* Timestamp format in 1588-2008 */
|
|
+struct dpa_ptp_time {
|
|
+ u64 sec; /* just 48 bit used */
|
|
+ u32 nsec;
|
|
+};
|
|
+
|
|
+/* needed for timestamp data over ioctl */
|
|
+struct dpa_ptp_data {
|
|
+ struct dpa_ptp_ident ident;
|
|
+ struct dpa_ptp_time ts;
|
|
+};
|
|
+
|
|
+struct dpa_ptp_circ_buf {
|
|
+ struct circ_buf circ_buf;
|
|
+ u32 size;
|
|
+ spinlock_t ptp_lock;
|
|
+};
|
|
+
|
|
+/* PTP TSU control structure */
|
|
+struct dpa_ptp_tsu {
|
|
+ struct dpa_priv_s *dpa_priv;
|
|
+ bool valid;
|
|
+ struct dpa_ptp_circ_buf rx_timestamps;
|
|
+ struct dpa_ptp_circ_buf tx_timestamps;
|
|
+
|
|
+ /* HW timestamping over ioctl enabled flag */
|
|
+ int hwts_tx_en_ioctl;
|
|
+ int hwts_rx_en_ioctl;
|
|
+};
|
|
+
|
|
+extern int dpa_ptp_init(struct dpa_priv_s *priv);
|
|
+extern void dpa_ptp_cleanup(struct dpa_priv_s *priv);
|
|
+extern void dpa_ptp_store_txstamp(const struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, void *data);
|
|
+extern void dpa_ptp_store_rxstamp(const struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, void *data);
|
|
+extern int dpa_ioctl_1588(struct net_device *dev, struct ifreq *ifr, int cmd);
|
|
+#endif
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_debugfs.c
|
|
@@ -0,0 +1,180 @@
|
|
+/* Copyright 2008-2013 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/fsl_qman.h> /* struct qm_mcr_querycgr */
|
|
+#include <linux/debugfs.h>
|
|
+#include "dpaa_debugfs.h"
|
|
+#include "dpaa_eth.h" /* struct dpa_priv_s, dpa_percpu_priv_s, dpa_bp */
|
|
+
|
|
+#define DPA_DEBUGFS_DESCRIPTION "FSL DPAA Ethernet debugfs entries"
|
|
+#define DPA_ETH_DEBUGFS_ROOT "fsl_dpa"
|
|
+
|
|
+static struct dentry *dpa_debugfs_root;
|
|
+
|
|
+static int __cold dpa_debugfs_loop_open(struct inode *inode, struct file *file);
|
|
+static ssize_t dpa_loop_write(struct file *f,
|
|
+ const char __user *buf, size_t count, loff_t *off);
|
|
+
|
|
+static const struct file_operations dpa_debugfs_lp_fops = {
|
|
+ .open = dpa_debugfs_loop_open,
|
|
+ .write = dpa_loop_write,
|
|
+ .read = seq_read,
|
|
+ .llseek = seq_lseek,
|
|
+ .release = single_release,
|
|
+};
|
|
+
|
|
+static int dpa_debugfs_loop_show(struct seq_file *file, void *offset)
|
|
+{
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ BUG_ON(offset == NULL);
|
|
+
|
|
+ priv = netdev_priv((struct net_device *)file->private);
|
|
+ seq_printf(file, "%d->%d\n", priv->loop_id, priv->loop_to);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int user_input_convert(const char __user *user_buf, size_t count,
|
|
+ long *val)
|
|
+{
|
|
+ char buf[12];
|
|
+
|
|
+ if (count > sizeof(buf) - 1)
|
|
+ return -EINVAL;
|
|
+ if (copy_from_user(buf, user_buf, count))
|
|
+ return -EFAULT;
|
|
+ buf[count] = '\0';
|
|
+ if (kstrtol(buf, 0, val))
|
|
+ return -EINVAL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t dpa_loop_write(struct file *f,
|
|
+ const char __user *buf, size_t count, loff_t *off)
|
|
+{
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct net_device *netdev;
|
|
+ struct seq_file *sf;
|
|
+ int ret;
|
|
+ long val;
|
|
+
|
|
+ ret = user_input_convert(buf, count, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ sf = (struct seq_file *)f->private_data;
|
|
+ netdev = (struct net_device *)sf->private;
|
|
+ priv = netdev_priv(netdev);
|
|
+
|
|
+ priv->loop_to = ((val < 0) || (val > 20)) ? -1 : val;
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static int __cold dpa_debugfs_loop_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ int _errno;
|
|
+ const struct net_device *net_dev;
|
|
+
|
|
+ _errno = single_open(file, dpa_debugfs_loop_show, inode->i_private);
|
|
+ if (unlikely(_errno < 0)) {
|
|
+ net_dev = (struct net_device *)inode->i_private;
|
|
+
|
|
+ if (netif_msg_drv((struct dpa_priv_s *)netdev_priv(net_dev)))
|
|
+ netdev_err(net_dev, "single_open() = %d\n",
|
|
+ _errno);
|
|
+ }
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+
|
|
+int dpa_netdev_debugfs_create(struct net_device *net_dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ static int cnt;
|
|
+ char loop_file_name[100];
|
|
+
|
|
+ if (unlikely(dpa_debugfs_root == NULL)) {
|
|
+ pr_err(KBUILD_MODNAME ": %s:%hu:%s(): \t%s\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__,
|
|
+ "root debugfs missing, possible module ordering issue");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ sprintf(loop_file_name, "eth%d_loop", ++cnt);
|
|
+ priv->debugfs_loop_file = debugfs_create_file(loop_file_name,
|
|
+ S_IRUGO,
|
|
+ dpa_debugfs_root,
|
|
+ net_dev,
|
|
+ &dpa_debugfs_lp_fops);
|
|
+ if (unlikely(priv->debugfs_loop_file == NULL)) {
|
|
+ netdev_err(net_dev, "debugfs_create_file(%s/%s)",
|
|
+ dpa_debugfs_root->d_iname,
|
|
+ loop_file_name);
|
|
+
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void dpa_netdev_debugfs_remove(struct net_device *net_dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+
|
|
+ debugfs_remove(priv->debugfs_loop_file);
|
|
+}
|
|
+
|
|
+int __init dpa_debugfs_module_init(void)
|
|
+{
|
|
+ int _errno = 0;
|
|
+
|
|
+ pr_info(KBUILD_MODNAME ": " DPA_DEBUGFS_DESCRIPTION "\n");
|
|
+
|
|
+ dpa_debugfs_root = debugfs_create_dir(DPA_ETH_DEBUGFS_ROOT, NULL);
|
|
+
|
|
+ if (unlikely(dpa_debugfs_root == NULL)) {
|
|
+ _errno = -ENOMEM;
|
|
+ pr_err(KBUILD_MODNAME ": %s:%hu:%s():\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__);
|
|
+ pr_err("\tdebugfs_create_dir(%s/"KBUILD_MODNAME") = %d\n",
|
|
+ DPA_ETH_DEBUGFS_ROOT, _errno);
|
|
+ }
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+void __exit dpa_debugfs_module_exit(void)
|
|
+{
|
|
+ debugfs_remove(dpa_debugfs_root);
|
|
+}
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_debugfs.h
|
|
@@ -0,0 +1,43 @@
|
|
+/* Copyright 2008-2013 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifndef DPAA_DEBUGFS_H_
|
|
+#define DPAA_DEBUGFS_H_
|
|
+
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/dcache.h> /* struct dentry needed in dpaa_eth.h */
|
|
+
|
|
+int dpa_netdev_debugfs_create(struct net_device *net_dev);
|
|
+void dpa_netdev_debugfs_remove(struct net_device *net_dev);
|
|
+int __init dpa_debugfs_module_init(void);
|
|
+void __exit dpa_debugfs_module_exit(void);
|
|
+
|
|
+#endif /* DPAA_DEBUGFS_H_ */
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth.c
|
|
@@ -0,0 +1,1210 @@
|
|
+/* Copyright 2008-2013 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": %s:%hu:%s() " fmt, \
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__
|
|
+#else
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": " fmt
|
|
+#endif
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_mdio.h>
|
|
+#include <linux/of_net.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/if_arp.h> /* arp_hdr_len() */
|
|
+#include <linux/if_vlan.h> /* VLAN_HLEN */
|
|
+#include <linux/icmp.h> /* struct icmphdr */
|
|
+#include <linux/ip.h> /* struct iphdr */
|
|
+#include <linux/ipv6.h> /* struct ipv6hdr */
|
|
+#include <linux/udp.h> /* struct udphdr */
|
|
+#include <linux/tcp.h> /* struct tcphdr */
|
|
+#include <linux/net.h> /* net_ratelimit() */
|
|
+#include <linux/if_ether.h> /* ETH_P_IP and ETH_P_IPV6 */
|
|
+#include <linux/highmem.h>
|
|
+#include <linux/percpu.h>
|
|
+#include <linux/dma-mapping.h>
|
|
+#include <linux/fsl_bman.h>
|
|
+#ifdef CONFIG_SOC_BUS
|
|
+#include <linux/sys_soc.h> /* soc_device_match */
|
|
+#endif
|
|
+
|
|
+#include "fsl_fman.h"
|
|
+#include "fm_ext.h"
|
|
+#include "fm_port_ext.h"
|
|
+
|
|
+#include "mac.h"
|
|
+#include "dpaa_eth.h"
|
|
+#include "dpaa_eth_common.h"
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+#include "dpaa_debugfs.h"
|
|
+#endif /* CONFIG_FSL_DPAA_DBG_LOOP */
|
|
+
|
|
+/* CREATE_TRACE_POINTS only needs to be defined once. Other dpa files
|
|
+ * using trace events only need to #include <trace/events/sched.h>
|
|
+ */
|
|
+#define CREATE_TRACE_POINTS
|
|
+#include "dpaa_eth_trace.h"
|
|
+
|
|
+#define DPA_NAPI_WEIGHT 64
|
|
+
|
|
+/* Valid checksum indication */
|
|
+#define DPA_CSUM_VALID 0xFFFF
|
|
+
|
|
+#define DPA_DESCRIPTION "FSL DPAA Ethernet driver"
|
|
+
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
+
|
|
+MODULE_AUTHOR("Andy Fleming <afleming@freescale.com>");
|
|
+
|
|
+MODULE_DESCRIPTION(DPA_DESCRIPTION);
|
|
+
|
|
+static uint8_t debug = -1;
|
|
+module_param(debug, byte, S_IRUGO);
|
|
+MODULE_PARM_DESC(debug, "Module/Driver verbosity level");
|
|
+
|
|
+/* This has to work in tandem with the DPA_CS_THRESHOLD_xxx values. */
|
|
+static uint16_t tx_timeout = 1000;
|
|
+module_param(tx_timeout, ushort, S_IRUGO);
|
|
+MODULE_PARM_DESC(tx_timeout, "The Tx timeout in ms");
|
|
+
|
|
+static const char rtx[][3] = {
|
|
+ [RX] = "RX",
|
|
+ [TX] = "TX"
|
|
+};
|
|
+
|
|
+#ifndef CONFIG_PPC
|
|
+bool dpaa_errata_a010022;
|
|
+EXPORT_SYMBOL(dpaa_errata_a010022);
|
|
+#endif
|
|
+
|
|
+/* BM */
|
|
+
|
|
+#define DPAA_ETH_MAX_PAD (L1_CACHE_BYTES * 8)
|
|
+
|
|
+static uint8_t dpa_priv_common_bpid;
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+struct net_device *dpa_loop_netdevs[20];
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+
|
|
+static int dpaa_suspend(struct device *dev)
|
|
+{
|
|
+ struct net_device *net_dev;
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct mac_device *mac_dev;
|
|
+ int err = 0;
|
|
+
|
|
+ net_dev = dev_get_drvdata(dev);
|
|
+
|
|
+ if (net_dev->flags & IFF_UP) {
|
|
+ priv = netdev_priv(net_dev);
|
|
+ mac_dev = priv->mac_dev;
|
|
+
|
|
+ if (priv->wol & DPAA_WOL_MAGIC) {
|
|
+ err = priv->mac_dev->set_wol(mac_dev->port_dev[RX],
|
|
+ priv->mac_dev->get_mac_handle(mac_dev), true);
|
|
+ if (err) {
|
|
+ netdev_err(net_dev, "set_wol() = %d\n", err);
|
|
+ goto set_wol_failed;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = fm_port_suspend(mac_dev->port_dev[RX]);
|
|
+ if (err) {
|
|
+ netdev_err(net_dev, "fm_port_suspend(RX) = %d\n", err);
|
|
+ goto rx_port_suspend_failed;
|
|
+ }
|
|
+
|
|
+ err = fm_port_suspend(mac_dev->port_dev[TX]);
|
|
+ if (err) {
|
|
+ netdev_err(net_dev, "fm_port_suspend(TX) = %d\n", err);
|
|
+ goto tx_port_suspend_failed;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+tx_port_suspend_failed:
|
|
+ fm_port_resume(mac_dev->port_dev[RX]);
|
|
+rx_port_suspend_failed:
|
|
+ if (priv->wol & DPAA_WOL_MAGIC) {
|
|
+ priv->mac_dev->set_wol(mac_dev->port_dev[RX],
|
|
+ priv->mac_dev->get_mac_handle(mac_dev), false);
|
|
+ }
|
|
+set_wol_failed:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int dpaa_resume(struct device *dev)
|
|
+{
|
|
+ struct net_device *net_dev;
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct mac_device *mac_dev;
|
|
+ int err = 0;
|
|
+
|
|
+ net_dev = dev_get_drvdata(dev);
|
|
+
|
|
+ if (net_dev->flags & IFF_UP) {
|
|
+ priv = netdev_priv(net_dev);
|
|
+ mac_dev = priv->mac_dev;
|
|
+
|
|
+ err = fm_mac_resume(mac_dev->get_mac_handle(mac_dev));
|
|
+ if (err) {
|
|
+ netdev_err(net_dev, "fm_mac_resume = %d\n", err);
|
|
+ goto resume_failed;
|
|
+ }
|
|
+
|
|
+ err = fm_port_resume(mac_dev->port_dev[TX]);
|
|
+ if (err) {
|
|
+ netdev_err(net_dev, "fm_port_resume(TX) = %d\n", err);
|
|
+ goto resume_failed;
|
|
+ }
|
|
+
|
|
+ err = fm_port_resume(mac_dev->port_dev[RX]);
|
|
+ if (err) {
|
|
+ netdev_err(net_dev, "fm_port_resume(RX) = %d\n", err);
|
|
+ goto resume_failed;
|
|
+ }
|
|
+
|
|
+ if (priv->wol & DPAA_WOL_MAGIC) {
|
|
+ err = priv->mac_dev->set_wol(mac_dev->port_dev[RX],
|
|
+ priv->mac_dev->get_mac_handle(mac_dev), false);
|
|
+ if (err) {
|
|
+ netdev_err(net_dev, "set_wol() = %d\n", err);
|
|
+ goto resume_failed;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+resume_failed:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops dpaa_pm_ops = {
|
|
+ .suspend = dpaa_suspend,
|
|
+ .resume = dpaa_resume,
|
|
+};
|
|
+
|
|
+#define DPAA_PM_OPS (&dpaa_pm_ops)
|
|
+
|
|
+#else /* CONFIG_PM */
|
|
+
|
|
+#define DPAA_PM_OPS NULL
|
|
+
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+/* Checks whether the checksum field in Parse Results array is valid
|
|
+ * (equals 0xFFFF) and increments the .cse counter otherwise
|
|
+ */
|
|
+static inline void
|
|
+dpa_csum_validation(const struct dpa_priv_s *priv,
|
|
+ struct dpa_percpu_priv_s *percpu_priv,
|
|
+ const struct qm_fd *fd)
|
|
+{
|
|
+ dma_addr_t addr = qm_fd_addr(fd);
|
|
+ struct dpa_bp *dpa_bp = priv->dpa_bp;
|
|
+ void *frm = phys_to_virt(addr);
|
|
+ fm_prs_result_t *parse_result;
|
|
+
|
|
+ if (unlikely(!frm))
|
|
+ return;
|
|
+
|
|
+ dma_sync_single_for_cpu(dpa_bp->dev, addr, DPA_RX_PRIV_DATA_SIZE +
|
|
+ DPA_PARSE_RESULTS_SIZE, DMA_BIDIRECTIONAL);
|
|
+
|
|
+ parse_result = (fm_prs_result_t *)(frm + DPA_RX_PRIV_DATA_SIZE);
|
|
+
|
|
+ if (parse_result->cksum != DPA_CSUM_VALID)
|
|
+ percpu_priv->rx_errors.cse++;
|
|
+}
|
|
+
|
|
+static void _dpa_rx_error(struct net_device *net_dev,
|
|
+ const struct dpa_priv_s *priv,
|
|
+ struct dpa_percpu_priv_s *percpu_priv,
|
|
+ const struct qm_fd *fd,
|
|
+ u32 fqid)
|
|
+{
|
|
+ /* limit common, possibly innocuous Rx FIFO Overflow errors'
|
|
+ * interference with zero-loss convergence benchmark results.
|
|
+ */
|
|
+ if (likely(fd->status & FM_FD_STAT_ERR_PHYSICAL))
|
|
+ pr_warn_once("fsl-dpa: non-zero error counters in fman statistics (sysfs)\n");
|
|
+ else
|
|
+ if (netif_msg_hw(priv) && net_ratelimit())
|
|
+ netdev_dbg(net_dev, "Err FD status = 0x%08x\n",
|
|
+ fd->status & FM_FD_STAT_RX_ERRORS);
|
|
+#ifdef CONFIG_FSL_DPAA_HOOKS
|
|
+ if (dpaa_eth_hooks.rx_error &&
|
|
+ dpaa_eth_hooks.rx_error(net_dev, fd, fqid) == DPAA_ETH_STOLEN)
|
|
+ /* it's up to the hook to perform resource cleanup */
|
|
+ return;
|
|
+#endif
|
|
+ percpu_priv->stats.rx_errors++;
|
|
+
|
|
+ if (fd->status & FM_PORT_FRM_ERR_DMA)
|
|
+ percpu_priv->rx_errors.dme++;
|
|
+ if (fd->status & FM_PORT_FRM_ERR_PHYSICAL)
|
|
+ percpu_priv->rx_errors.fpe++;
|
|
+ if (fd->status & FM_PORT_FRM_ERR_SIZE)
|
|
+ percpu_priv->rx_errors.fse++;
|
|
+ if (fd->status & FM_PORT_FRM_ERR_PRS_HDR_ERR)
|
|
+ percpu_priv->rx_errors.phe++;
|
|
+ if (fd->status & FM_FD_STAT_L4CV)
|
|
+ dpa_csum_validation(priv, percpu_priv, fd);
|
|
+
|
|
+ dpa_fd_release(net_dev, fd);
|
|
+}
|
|
+
|
|
+static void _dpa_tx_error(struct net_device *net_dev,
|
|
+ const struct dpa_priv_s *priv,
|
|
+ struct dpa_percpu_priv_s *percpu_priv,
|
|
+ const struct qm_fd *fd,
|
|
+ u32 fqid)
|
|
+{
|
|
+ struct sk_buff *skb;
|
|
+
|
|
+ if (netif_msg_hw(priv) && net_ratelimit())
|
|
+ netdev_warn(net_dev, "FD status = 0x%08x\n",
|
|
+ fd->status & FM_FD_STAT_TX_ERRORS);
|
|
+#ifdef CONFIG_FSL_DPAA_HOOKS
|
|
+ if (dpaa_eth_hooks.tx_error &&
|
|
+ dpaa_eth_hooks.tx_error(net_dev, fd, fqid) == DPAA_ETH_STOLEN)
|
|
+ /* now the hook must ensure proper cleanup */
|
|
+ return;
|
|
+#endif
|
|
+ percpu_priv->stats.tx_errors++;
|
|
+
|
|
+ /* If we intended the buffers from this frame to go into the bpools
|
|
+ * when the FMan transmit was done, we need to put it in manually.
|
|
+ */
|
|
+ if (fd->bpid != 0xff) {
|
|
+ dpa_fd_release(net_dev, fd);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ skb = _dpa_cleanup_tx_fd(priv, fd);
|
|
+ dev_kfree_skb(skb);
|
|
+}
|
|
+
|
|
+/* Helper function to factor out frame validation logic on all Rx paths. Its
|
|
+ * purpose is to extract from the Parse Results structure information about
|
|
+ * the integrity of the frame, its checksum, the length of the parsed headers
|
|
+ * and whether the frame is suitable for GRO.
|
|
+ *
|
|
+ * Assumes no parser errors, since any error frame is dropped before this
|
|
+ * function is called.
|
|
+ *
|
|
+ * @skb will have its ip_summed field overwritten;
|
|
+ * @use_gro will only be written with 0, if the frame is definitely not
|
|
+ * GRO-able; otherwise, it will be left unchanged;
|
|
+ * @hdr_size will be written with a safe value, at least the size of the
|
|
+ * headers' length.
|
|
+ */
|
|
+void __hot _dpa_process_parse_results(const fm_prs_result_t *parse_results,
|
|
+ const struct qm_fd *fd,
|
|
+ struct sk_buff *skb, int *use_gro)
|
|
+{
|
|
+ if (fd->status & FM_FD_STAT_L4CV) {
|
|
+ /* The parser has run and performed L4 checksum validation.
|
|
+ * We know there were no parser errors (and implicitly no
|
|
+ * L4 csum error), otherwise we wouldn't be here.
|
|
+ */
|
|
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
+
|
|
+ /* Don't go through GRO for certain types of traffic that
|
|
+ * we know are not GRO-able, such as dgram-based protocols.
|
|
+ * In the worst-case scenarios, such as small-pkt terminating
|
|
+ * UDP, the extra GRO processing would be overkill.
|
|
+ *
|
|
+ * The only protocol the Parser supports that is also GRO-able
|
|
+ * is currently TCP.
|
|
+ */
|
|
+ if (!fm_l4_frame_is_tcp(parse_results))
|
|
+ *use_gro = 0;
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* We're here because either the parser didn't run or the L4 checksum
|
|
+ * was not verified. This may include the case of a UDP frame with
|
|
+ * checksum zero or an L4 proto other than TCP/UDP
|
|
+ */
|
|
+ skb->ip_summed = CHECKSUM_NONE;
|
|
+
|
|
+ /* Bypass GRO for unknown traffic or if no PCDs are applied */
|
|
+ *use_gro = 0;
|
|
+}
|
|
+
|
|
+int dpaa_eth_poll(struct napi_struct *napi, int budget)
|
|
+{
|
|
+ struct dpa_napi_portal *np =
|
|
+ container_of(napi, struct dpa_napi_portal, napi);
|
|
+
|
|
+ int cleaned = qman_p_poll_dqrr(np->p, budget);
|
|
+
|
|
+ if (cleaned < budget) {
|
|
+ int tmp;
|
|
+ napi_complete(napi);
|
|
+ tmp = qman_p_irqsource_add(np->p, QM_PIRQ_DQRI);
|
|
+ DPA_BUG_ON(tmp);
|
|
+ }
|
|
+
|
|
+ return cleaned;
|
|
+}
|
|
+EXPORT_SYMBOL(dpaa_eth_poll);
|
|
+
|
|
+static void __hot _dpa_tx_conf(struct net_device *net_dev,
|
|
+ const struct dpa_priv_s *priv,
|
|
+ struct dpa_percpu_priv_s *percpu_priv,
|
|
+ const struct qm_fd *fd,
|
|
+ u32 fqid)
|
|
+{
|
|
+ struct sk_buff *skb;
|
|
+
|
|
+ /* do we need the timestamp for the error frames? */
|
|
+
|
|
+ if (unlikely(fd->status & FM_FD_STAT_TX_ERRORS) != 0) {
|
|
+ if (netif_msg_hw(priv) && net_ratelimit())
|
|
+ netdev_warn(net_dev, "FD status = 0x%08x\n",
|
|
+ fd->status & FM_FD_STAT_TX_ERRORS);
|
|
+
|
|
+ percpu_priv->stats.tx_errors++;
|
|
+ }
|
|
+
|
|
+ /* hopefully we need not get the timestamp before the hook */
|
|
+#ifdef CONFIG_FSL_DPAA_HOOKS
|
|
+ if (dpaa_eth_hooks.tx_confirm && dpaa_eth_hooks.tx_confirm(net_dev,
|
|
+ fd, fqid) == DPAA_ETH_STOLEN)
|
|
+ /* it's the hook that must now perform cleanup */
|
|
+ return;
|
|
+#endif
|
|
+ /* This might not perfectly reflect the reality, if the core dequeuing
|
|
+ * the Tx confirmation is different from the one that did the enqueue,
|
|
+ * but at least it'll show up in the total count.
|
|
+ */
|
|
+ percpu_priv->tx_confirm++;
|
|
+
|
|
+ skb = _dpa_cleanup_tx_fd(priv, fd);
|
|
+
|
|
+ dev_kfree_skb(skb);
|
|
+}
|
|
+
|
|
+enum qman_cb_dqrr_result
|
|
+priv_rx_error_dqrr(struct qman_portal *portal,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_dqrr_entry *dq)
|
|
+{
|
|
+ struct net_device *net_dev;
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ int *count_ptr;
|
|
+
|
|
+ net_dev = ((struct dpa_fq *)fq)->net_dev;
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ percpu_priv = raw_cpu_ptr(priv->percpu_priv);
|
|
+ count_ptr = raw_cpu_ptr(priv->dpa_bp->percpu_count);
|
|
+
|
|
+ if (dpaa_eth_napi_schedule(percpu_priv, portal))
|
|
+ return qman_cb_dqrr_stop;
|
|
+
|
|
+ if (unlikely(dpaa_eth_refill_bpools(priv->dpa_bp, count_ptr)))
|
|
+ /* Unable to refill the buffer pool due to insufficient
|
|
+ * system memory. Just release the frame back into the pool,
|
|
+ * otherwise we'll soon end up with an empty buffer pool.
|
|
+ */
|
|
+ dpa_fd_release(net_dev, &dq->fd);
|
|
+ else
|
|
+ _dpa_rx_error(net_dev, priv, percpu_priv, &dq->fd, fq->fqid);
|
|
+
|
|
+ return qman_cb_dqrr_consume;
|
|
+}
|
|
+
|
|
+
|
|
+enum qman_cb_dqrr_result __hot
|
|
+priv_rx_default_dqrr(struct qman_portal *portal,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_dqrr_entry *dq)
|
|
+{
|
|
+ struct net_device *net_dev;
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ int *count_ptr;
|
|
+ struct dpa_bp *dpa_bp;
|
|
+
|
|
+ net_dev = ((struct dpa_fq *)fq)->net_dev;
|
|
+ priv = netdev_priv(net_dev);
|
|
+ dpa_bp = priv->dpa_bp;
|
|
+
|
|
+ /* Trace the Rx fd */
|
|
+ trace_dpa_rx_fd(net_dev, fq, &dq->fd);
|
|
+
|
|
+ /* IRQ handler, non-migratable; safe to use raw_cpu_ptr here */
|
|
+ percpu_priv = raw_cpu_ptr(priv->percpu_priv);
|
|
+ count_ptr = raw_cpu_ptr(dpa_bp->percpu_count);
|
|
+
|
|
+ if (unlikely(dpaa_eth_napi_schedule(percpu_priv, portal)))
|
|
+ return qman_cb_dqrr_stop;
|
|
+
|
|
+ /* Vale of plenty: make sure we didn't run out of buffers */
|
|
+
|
|
+ if (unlikely(dpaa_eth_refill_bpools(dpa_bp, count_ptr)))
|
|
+ /* Unable to refill the buffer pool due to insufficient
|
|
+ * system memory. Just release the frame back into the pool,
|
|
+ * otherwise we'll soon end up with an empty buffer pool.
|
|
+ */
|
|
+ dpa_fd_release(net_dev, &dq->fd);
|
|
+ else
|
|
+ _dpa_rx(net_dev, portal, priv, percpu_priv, &dq->fd, fq->fqid,
|
|
+ count_ptr);
|
|
+
|
|
+ return qman_cb_dqrr_consume;
|
|
+}
|
|
+
|
|
+enum qman_cb_dqrr_result
|
|
+priv_tx_conf_error_dqrr(struct qman_portal *portal,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_dqrr_entry *dq)
|
|
+{
|
|
+ struct net_device *net_dev;
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+
|
|
+ net_dev = ((struct dpa_fq *)fq)->net_dev;
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ percpu_priv = raw_cpu_ptr(priv->percpu_priv);
|
|
+
|
|
+ if (dpaa_eth_napi_schedule(percpu_priv, portal))
|
|
+ return qman_cb_dqrr_stop;
|
|
+
|
|
+ _dpa_tx_error(net_dev, priv, percpu_priv, &dq->fd, fq->fqid);
|
|
+
|
|
+ return qman_cb_dqrr_consume;
|
|
+}
|
|
+
|
|
+enum qman_cb_dqrr_result __hot
|
|
+priv_tx_conf_default_dqrr(struct qman_portal *portal,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_dqrr_entry *dq)
|
|
+{
|
|
+ struct net_device *net_dev;
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+
|
|
+ net_dev = ((struct dpa_fq *)fq)->net_dev;
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ /* Trace the fd */
|
|
+ trace_dpa_tx_conf_fd(net_dev, fq, &dq->fd);
|
|
+
|
|
+ /* Non-migratable context, safe to use raw_cpu_ptr */
|
|
+ percpu_priv = raw_cpu_ptr(priv->percpu_priv);
|
|
+
|
|
+ if (dpaa_eth_napi_schedule(percpu_priv, portal))
|
|
+ return qman_cb_dqrr_stop;
|
|
+
|
|
+ _dpa_tx_conf(net_dev, priv, percpu_priv, &dq->fd, fq->fqid);
|
|
+
|
|
+ return qman_cb_dqrr_consume;
|
|
+}
|
|
+
|
|
+void priv_ern(struct qman_portal *portal,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_mr_entry *msg)
|
|
+{
|
|
+ struct net_device *net_dev;
|
|
+ const struct dpa_priv_s *priv;
|
|
+ struct sk_buff *skb;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ struct qm_fd fd = msg->ern.fd;
|
|
+
|
|
+ net_dev = ((struct dpa_fq *)fq)->net_dev;
|
|
+ priv = netdev_priv(net_dev);
|
|
+ /* Non-migratable context, safe to use raw_cpu_ptr */
|
|
+ percpu_priv = raw_cpu_ptr(priv->percpu_priv);
|
|
+
|
|
+ percpu_priv->stats.tx_dropped++;
|
|
+ percpu_priv->stats.tx_fifo_errors++;
|
|
+ count_ern(percpu_priv, msg);
|
|
+
|
|
+ /* If we intended this buffer to go into the pool
|
|
+ * when the FM was done, we need to put it in
|
|
+ * manually.
|
|
+ */
|
|
+ if (msg->ern.fd.bpid != 0xff) {
|
|
+ dpa_fd_release(net_dev, &fd);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ skb = _dpa_cleanup_tx_fd(priv, &fd);
|
|
+ dev_kfree_skb_any(skb);
|
|
+}
|
|
+
|
|
+const struct dpa_fq_cbs_t private_fq_cbs = {
|
|
+ .rx_defq = { .cb = { .dqrr = priv_rx_default_dqrr } },
|
|
+ .tx_defq = { .cb = { .dqrr = priv_tx_conf_default_dqrr } },
|
|
+ .rx_errq = { .cb = { .dqrr = priv_rx_error_dqrr } },
|
|
+ .tx_errq = { .cb = { .dqrr = priv_tx_conf_error_dqrr } },
|
|
+ .egress_ern = { .cb = { .ern = priv_ern } }
|
|
+};
|
|
+EXPORT_SYMBOL(private_fq_cbs);
|
|
+
|
|
+static void dpaa_eth_napi_enable(struct dpa_priv_s *priv)
|
|
+{
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ int i, j;
|
|
+
|
|
+ for_each_possible_cpu(i) {
|
|
+ percpu_priv = per_cpu_ptr(priv->percpu_priv, i);
|
|
+
|
|
+ for (j = 0; j < qman_portal_max; j++)
|
|
+ napi_enable(&percpu_priv->np[j].napi);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void dpaa_eth_napi_disable(struct dpa_priv_s *priv)
|
|
+{
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ int i, j;
|
|
+
|
|
+ for_each_possible_cpu(i) {
|
|
+ percpu_priv = per_cpu_ptr(priv->percpu_priv, i);
|
|
+
|
|
+ for (j = 0; j < qman_portal_max; j++)
|
|
+ napi_disable(&percpu_priv->np[j].napi);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int __cold dpa_eth_priv_start(struct net_device *net_dev)
|
|
+{
|
|
+ int err;
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ dpaa_eth_napi_enable(priv);
|
|
+
|
|
+ err = dpa_start(net_dev);
|
|
+ if (err < 0)
|
|
+ dpaa_eth_napi_disable(priv);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+static int __cold dpa_eth_priv_stop(struct net_device *net_dev)
|
|
+{
|
|
+ int _errno;
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ _errno = dpa_stop(net_dev);
|
|
+ /* Allow NAPI to consume any frame still in the Rx/TxConfirm
|
|
+ * ingress queues. This is to avoid a race between the current
|
|
+ * context and ksoftirqd which could leave NAPI disabled while
|
|
+ * in fact there's still Rx traffic to be processed.
|
|
+ */
|
|
+ usleep_range(5000, 10000);
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ dpaa_eth_napi_disable(priv);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
+static void dpaa_eth_poll_controller(struct net_device *net_dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ struct dpa_percpu_priv_s *percpu_priv =
|
|
+ raw_cpu_ptr(priv->percpu_priv);
|
|
+ struct qman_portal *p;
|
|
+ const struct qman_portal_config *pc;
|
|
+ struct dpa_napi_portal *np;
|
|
+
|
|
+ p = (struct qman_portal *)qman_get_affine_portal(smp_processor_id());
|
|
+ pc = qman_p_get_portal_config(p);
|
|
+ np = &percpu_priv->np[pc->index];
|
|
+
|
|
+ qman_p_irqsource_remove(np->p, QM_PIRQ_DQRI);
|
|
+ qman_p_poll_dqrr(np->p, np->napi.weight);
|
|
+ qman_p_irqsource_add(np->p, QM_PIRQ_DQRI);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static const struct net_device_ops dpa_private_ops = {
|
|
+ .ndo_open = dpa_eth_priv_start,
|
|
+ .ndo_start_xmit = dpa_tx,
|
|
+ .ndo_stop = dpa_eth_priv_stop,
|
|
+ .ndo_tx_timeout = dpa_timeout,
|
|
+ .ndo_get_stats64 = dpa_get_stats64,
|
|
+ .ndo_set_mac_address = dpa_set_mac_address,
|
|
+ .ndo_validate_addr = eth_validate_addr,
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_USE_NDO_SELECT_QUEUE
|
|
+ .ndo_select_queue = dpa_select_queue,
|
|
+#endif
|
|
+ .ndo_change_mtu = dpa_change_mtu,
|
|
+ .ndo_set_rx_mode = dpa_set_rx_mode,
|
|
+ .ndo_init = dpa_ndo_init,
|
|
+ .ndo_set_features = dpa_set_features,
|
|
+ .ndo_fix_features = dpa_fix_features,
|
|
+ .ndo_do_ioctl = dpa_ioctl,
|
|
+#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
+ .ndo_poll_controller = dpaa_eth_poll_controller,
|
|
+#endif
|
|
+};
|
|
+
|
|
+static int dpa_private_napi_add(struct net_device *net_dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ int i, cpu;
|
|
+
|
|
+ for_each_possible_cpu(cpu) {
|
|
+ percpu_priv = per_cpu_ptr(priv->percpu_priv, cpu);
|
|
+
|
|
+ percpu_priv->np = devm_kzalloc(net_dev->dev.parent,
|
|
+ qman_portal_max * sizeof(struct dpa_napi_portal),
|
|
+ GFP_KERNEL);
|
|
+
|
|
+ if (unlikely(percpu_priv->np == NULL)) {
|
|
+ dev_err(net_dev->dev.parent, "devm_kzalloc() failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < qman_portal_max; i++)
|
|
+ netif_napi_add(net_dev, &percpu_priv->np[i].napi,
|
|
+ dpaa_eth_poll, DPA_NAPI_WEIGHT);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void dpa_private_napi_del(struct net_device *net_dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ int i, cpu;
|
|
+
|
|
+ for_each_possible_cpu(cpu) {
|
|
+ percpu_priv = per_cpu_ptr(priv->percpu_priv, cpu);
|
|
+
|
|
+ if (percpu_priv->np) {
|
|
+ for (i = 0; i < qman_portal_max; i++)
|
|
+ netif_napi_del(&percpu_priv->np[i].napi);
|
|
+
|
|
+ devm_kfree(net_dev->dev.parent, percpu_priv->np);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_private_napi_del);
|
|
+
|
|
+static int dpa_private_netdev_init(struct net_device *net_dev)
|
|
+{
|
|
+ int i;
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ const uint8_t *mac_addr;
|
|
+
|
|
+ /* Although we access another CPU's private data here
|
|
+ * we do it at initialization so it is safe
|
|
+ */
|
|
+ for_each_possible_cpu(i) {
|
|
+ percpu_priv = per_cpu_ptr(priv->percpu_priv, i);
|
|
+ percpu_priv->net_dev = net_dev;
|
|
+ }
|
|
+
|
|
+ net_dev->netdev_ops = &dpa_private_ops;
|
|
+ mac_addr = priv->mac_dev->addr;
|
|
+
|
|
+ net_dev->mem_start = priv->mac_dev->res->start;
|
|
+ net_dev->mem_end = priv->mac_dev->res->end;
|
|
+
|
|
+ net_dev->hw_features |= (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
|
|
+ NETIF_F_LLTX);
|
|
+
|
|
+ /* Advertise S/G and HIGHDMA support for private interfaces */
|
|
+ net_dev->hw_features |= NETIF_F_SG | NETIF_F_HIGHDMA;
|
|
+ /* Recent kernels enable GSO automatically, if
|
|
+ * we declare NETIF_F_SG. For conformity, we'll
|
|
+ * still declare GSO explicitly.
|
|
+ */
|
|
+ net_dev->features |= NETIF_F_GSO;
|
|
+
|
|
+ /* Advertise GRO support */
|
|
+ net_dev->features |= NETIF_F_GRO;
|
|
+
|
|
+ return dpa_netdev_init(net_dev, mac_addr, tx_timeout);
|
|
+}
|
|
+
|
|
+static struct dpa_bp * __cold
|
|
+dpa_priv_bp_probe(struct device *dev)
|
|
+{
|
|
+ struct dpa_bp *dpa_bp;
|
|
+
|
|
+ dpa_bp = devm_kzalloc(dev, sizeof(*dpa_bp), GFP_KERNEL);
|
|
+ if (unlikely(dpa_bp == NULL)) {
|
|
+ dev_err(dev, "devm_kzalloc() failed\n");
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+ }
|
|
+
|
|
+ dpa_bp->percpu_count = devm_alloc_percpu(dev, *dpa_bp->percpu_count);
|
|
+ dpa_bp->target_count = CONFIG_FSL_DPAA_ETH_MAX_BUF_COUNT;
|
|
+
|
|
+ dpa_bp->seed_cb = dpa_bp_priv_seed;
|
|
+ dpa_bp->free_buf_cb = _dpa_bp_free_pf;
|
|
+
|
|
+ return dpa_bp;
|
|
+}
|
|
+
|
|
+/* Place all ingress FQs (Rx Default, Rx Error, PCD FQs) in a dedicated CGR.
|
|
+ * We won't be sending congestion notifications to FMan; for now, we just use
|
|
+ * this CGR to generate enqueue rejections to FMan in order to drop the frames
|
|
+ * before they reach our ingress queues and eat up memory.
|
|
+ */
|
|
+static int dpaa_eth_priv_ingress_cgr_init(struct dpa_priv_s *priv)
|
|
+{
|
|
+ struct qm_mcc_initcgr initcgr;
|
|
+ u32 cs_th;
|
|
+ int err;
|
|
+
|
|
+ err = qman_alloc_cgrid(&priv->ingress_cgr.cgrid);
|
|
+ if (err < 0) {
|
|
+ pr_err("Error %d allocating CGR ID\n", err);
|
|
+ goto out_error;
|
|
+ }
|
|
+
|
|
+ /* Enable CS TD, but disable Congestion State Change Notifications. */
|
|
+ initcgr.we_mask = QM_CGR_WE_CS_THRES;
|
|
+ initcgr.cgr.cscn_en = QM_CGR_EN;
|
|
+ cs_th = CONFIG_FSL_DPAA_INGRESS_CS_THRESHOLD;
|
|
+ qm_cgr_cs_thres_set64(&initcgr.cgr.cs_thres, cs_th, 1);
|
|
+
|
|
+ initcgr.we_mask |= QM_CGR_WE_CSTD_EN;
|
|
+ initcgr.cgr.cstd_en = QM_CGR_EN;
|
|
+
|
|
+ /* This is actually a hack, because this CGR will be associated with
|
|
+ * our affine SWP. However, we'll place our ingress FQs in it.
|
|
+ */
|
|
+ err = qman_create_cgr(&priv->ingress_cgr, QMAN_CGR_FLAG_USE_INIT,
|
|
+ &initcgr);
|
|
+ if (err < 0) {
|
|
+ pr_err("Error %d creating ingress CGR with ID %d\n", err,
|
|
+ priv->ingress_cgr.cgrid);
|
|
+ qman_release_cgrid(priv->ingress_cgr.cgrid);
|
|
+ goto out_error;
|
|
+ }
|
|
+ pr_debug("Created ingress CGR %d for netdev with hwaddr %pM\n",
|
|
+ priv->ingress_cgr.cgrid, priv->mac_dev->addr);
|
|
+
|
|
+ /* struct qman_cgr allows special cgrid values (i.e. outside the 0..255
|
|
+ * range), but we have no common initialization path between the
|
|
+ * different variants of the DPAA Eth driver, so we do it here rather
|
|
+ * than modifying every other variant than "private Eth".
|
|
+ */
|
|
+ priv->use_ingress_cgr = true;
|
|
+
|
|
+out_error:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int dpa_priv_bp_create(struct net_device *net_dev, struct dpa_bp *dpa_bp,
|
|
+ size_t count)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ int i;
|
|
+
|
|
+ if (netif_msg_probe(priv))
|
|
+ dev_dbg(net_dev->dev.parent,
|
|
+ "Using private BM buffer pools\n");
|
|
+
|
|
+ priv->bp_count = count;
|
|
+
|
|
+ for (i = 0; i < count; i++) {
|
|
+ int err;
|
|
+ err = dpa_bp_alloc(&dpa_bp[i]);
|
|
+ if (err < 0) {
|
|
+ dpa_bp_free(priv);
|
|
+ priv->dpa_bp = NULL;
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ priv->dpa_bp = &dpa_bp[i];
|
|
+ }
|
|
+
|
|
+ dpa_priv_common_bpid = priv->dpa_bp->bpid;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id dpa_match[];
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+static int dpa_new_loop_id(void)
|
|
+{
|
|
+ static int if_id;
|
|
+
|
|
+ return if_id++;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int
|
|
+dpaa_eth_priv_probe(struct platform_device *_of_dev)
|
|
+{
|
|
+ int err = 0, i, channel;
|
|
+ struct device *dev;
|
|
+ struct device_node *dpa_node;
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ size_t count = 1;
|
|
+ struct net_device *net_dev = NULL;
|
|
+ struct dpa_priv_s *priv = NULL;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ struct fm_port_fqs port_fqs;
|
|
+ struct dpa_buffer_layout_s *buf_layout = NULL;
|
|
+ struct mac_device *mac_dev;
|
|
+
|
|
+ dev = &_of_dev->dev;
|
|
+
|
|
+ dpa_node = dev->of_node;
|
|
+
|
|
+ if (!of_device_is_available(dpa_node))
|
|
+ return -ENODEV;
|
|
+
|
|
+ /* Get the buffer pools assigned to this interface;
|
|
+ * run only once the default pool probing code
|
|
+ */
|
|
+ dpa_bp = (dpa_bpid2pool(dpa_priv_common_bpid)) ? :
|
|
+ dpa_priv_bp_probe(dev);
|
|
+ if (IS_ERR(dpa_bp))
|
|
+ return PTR_ERR(dpa_bp);
|
|
+
|
|
+ /* Allocate this early, so we can store relevant information in
|
|
+ * the private area (needed by 1588 code in dpa_mac_probe)
|
|
+ */
|
|
+ net_dev = alloc_etherdev_mq(sizeof(*priv), DPAA_ETH_TX_QUEUES);
|
|
+ if (!net_dev) {
|
|
+ dev_err(dev, "alloc_etherdev_mq() failed\n");
|
|
+ goto alloc_etherdev_mq_failed;
|
|
+ }
|
|
+
|
|
+ /* Do this here, so we can be verbose early */
|
|
+ SET_NETDEV_DEV(net_dev, dev);
|
|
+ dev_set_drvdata(dev, net_dev);
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ priv->net_dev = net_dev;
|
|
+ strcpy(priv->if_type, "private");
|
|
+
|
|
+ priv->msg_enable = netif_msg_init(debug, -1);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ priv->loop_id = dpa_new_loop_id();
|
|
+ priv->loop_to = -1; /* disabled by default */
|
|
+ dpa_loop_netdevs[priv->loop_id] = net_dev;
|
|
+#endif
|
|
+
|
|
+ mac_dev = dpa_mac_probe(_of_dev);
|
|
+ if (IS_ERR(mac_dev) || !mac_dev) {
|
|
+ err = PTR_ERR(mac_dev);
|
|
+ goto mac_probe_failed;
|
|
+ }
|
|
+
|
|
+ /* We have physical ports, so we need to establish
|
|
+ * the buffer layout.
|
|
+ */
|
|
+ buf_layout = devm_kzalloc(dev, 2 * sizeof(*buf_layout),
|
|
+ GFP_KERNEL);
|
|
+ if (!buf_layout) {
|
|
+ dev_err(dev, "devm_kzalloc() failed\n");
|
|
+ goto alloc_failed;
|
|
+ }
|
|
+ dpa_set_buffers_layout(mac_dev, buf_layout);
|
|
+
|
|
+ /* For private ports, need to compute the size of the default
|
|
+ * buffer pool, based on FMan port buffer layout;also update
|
|
+ * the maximum buffer size for private ports if necessary
|
|
+ */
|
|
+ dpa_bp->size = dpa_bp_size(&buf_layout[RX]);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_JUMBO_FRAME
|
|
+ /* We only want to use jumbo frame optimization if we actually have
|
|
+ * L2 MAX FRM set for jumbo frames as well.
|
|
+ */
|
|
+#ifndef CONFIG_PPC
|
|
+ if (likely(!dpaa_errata_a010022))
|
|
+#endif
|
|
+ if(fm_get_max_frm() < 9600)
|
|
+ dev_warn(dev,
|
|
+ "Invalid configuration: if jumbo frames support is on, FSL_FM_MAX_FRAME_SIZE should be set to 9600\n");
|
|
+#endif
|
|
+
|
|
+ INIT_LIST_HEAD(&priv->dpa_fq_list);
|
|
+
|
|
+ memset(&port_fqs, 0, sizeof(port_fqs));
|
|
+
|
|
+ err = dpa_fq_probe_mac(dev, &priv->dpa_fq_list, &port_fqs, true, RX);
|
|
+ if (!err)
|
|
+ err = dpa_fq_probe_mac(dev, &priv->dpa_fq_list,
|
|
+ &port_fqs, true, TX);
|
|
+
|
|
+ if (err < 0)
|
|
+ goto fq_probe_failed;
|
|
+
|
|
+ /* bp init */
|
|
+
|
|
+ err = dpa_priv_bp_create(net_dev, dpa_bp, count);
|
|
+
|
|
+ if (err < 0)
|
|
+ goto bp_create_failed;
|
|
+
|
|
+ priv->mac_dev = mac_dev;
|
|
+
|
|
+ channel = dpa_get_channel();
|
|
+
|
|
+ if (channel < 0) {
|
|
+ err = channel;
|
|
+ goto get_channel_failed;
|
|
+ }
|
|
+
|
|
+ priv->channel = (uint16_t)channel;
|
|
+ dpaa_eth_add_channel(priv->channel);
|
|
+
|
|
+ dpa_fq_setup(priv, &private_fq_cbs, priv->mac_dev->port_dev[TX]);
|
|
+
|
|
+ /* Create a congestion group for this netdev, with
|
|
+ * dynamically-allocated CGR ID.
|
|
+ * Must be executed after probing the MAC, but before
|
|
+ * assigning the egress FQs to the CGRs.
|
|
+ */
|
|
+ err = dpaa_eth_cgr_init(priv);
|
|
+ if (err < 0) {
|
|
+ dev_err(dev, "Error initializing CGR\n");
|
|
+ goto tx_cgr_init_failed;
|
|
+ }
|
|
+ err = dpaa_eth_priv_ingress_cgr_init(priv);
|
|
+ if (err < 0) {
|
|
+ dev_err(dev, "Error initializing ingress CGR\n");
|
|
+ goto rx_cgr_init_failed;
|
|
+ }
|
|
+
|
|
+ /* Add the FQs to the interface, and make them active */
|
|
+ err = dpa_fqs_init(dev, &priv->dpa_fq_list, false);
|
|
+ if (err < 0)
|
|
+ goto fq_alloc_failed;
|
|
+
|
|
+ priv->buf_layout = buf_layout;
|
|
+ priv->tx_headroom = dpa_get_headroom(&priv->buf_layout[TX]);
|
|
+ priv->rx_headroom = dpa_get_headroom(&priv->buf_layout[RX]);
|
|
+
|
|
+ /* All real interfaces need their ports initialized */
|
|
+ dpaa_eth_init_ports(mac_dev, dpa_bp, count, &port_fqs,
|
|
+ buf_layout, dev);
|
|
+
|
|
+#ifdef CONFIG_FMAN_PFC
|
|
+ for (i = 0; i < CONFIG_FMAN_PFC_COS_COUNT; i++) {
|
|
+ err = fm_port_set_pfc_priorities_mapping_to_qman_wq(
|
|
+ mac_dev->port_dev[TX], i, i);
|
|
+ if (unlikely(err != 0)) {
|
|
+ dev_err(dev, "Error maping PFC %u to WQ %u\n", i, i);
|
|
+ goto pfc_mapping_failed;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ priv->percpu_priv = devm_alloc_percpu(dev, *priv->percpu_priv);
|
|
+
|
|
+ if (priv->percpu_priv == NULL) {
|
|
+ dev_err(dev, "devm_alloc_percpu() failed\n");
|
|
+ err = -ENOMEM;
|
|
+ goto alloc_percpu_failed;
|
|
+ }
|
|
+ for_each_possible_cpu(i) {
|
|
+ percpu_priv = per_cpu_ptr(priv->percpu_priv, i);
|
|
+ memset(percpu_priv, 0, sizeof(*percpu_priv));
|
|
+ }
|
|
+
|
|
+ /* Initialize NAPI */
|
|
+ err = dpa_private_napi_add(net_dev);
|
|
+
|
|
+ if (err < 0)
|
|
+ goto napi_add_failed;
|
|
+
|
|
+ err = dpa_private_netdev_init(net_dev);
|
|
+
|
|
+ if (err < 0)
|
|
+ goto netdev_init_failed;
|
|
+
|
|
+ dpaa_eth_sysfs_init(&net_dev->dev);
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+ device_set_wakeup_capable(dev, true);
|
|
+#endif
|
|
+
|
|
+ pr_info("fsl_dpa: Probed interface %s\n", net_dev->name);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+netdev_init_failed:
|
|
+napi_add_failed:
|
|
+ dpa_private_napi_del(net_dev);
|
|
+alloc_percpu_failed:
|
|
+#ifdef CONFIG_FMAN_PFC
|
|
+pfc_mapping_failed:
|
|
+#endif
|
|
+ dpa_fq_free(dev, &priv->dpa_fq_list);
|
|
+fq_alloc_failed:
|
|
+ qman_delete_cgr_safe(&priv->ingress_cgr);
|
|
+ qman_release_cgrid(priv->ingress_cgr.cgrid);
|
|
+rx_cgr_init_failed:
|
|
+ qman_delete_cgr_safe(&priv->cgr_data.cgr);
|
|
+ qman_release_cgrid(priv->cgr_data.cgr.cgrid);
|
|
+tx_cgr_init_failed:
|
|
+get_channel_failed:
|
|
+ dpa_bp_free(priv);
|
|
+bp_create_failed:
|
|
+fq_probe_failed:
|
|
+alloc_failed:
|
|
+mac_probe_failed:
|
|
+ dev_set_drvdata(dev, NULL);
|
|
+ free_netdev(net_dev);
|
|
+alloc_etherdev_mq_failed:
|
|
+ if (atomic_read(&dpa_bp->refs) == 0)
|
|
+ devm_kfree(dev, dpa_bp);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct of_device_id dpa_match[] = {
|
|
+ {
|
|
+ .compatible = "fsl,dpa-ethernet"
|
|
+ },
|
|
+ {}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, dpa_match);
|
|
+
|
|
+static struct platform_driver dpa_driver = {
|
|
+ .driver = {
|
|
+ .name = KBUILD_MODNAME,
|
|
+ .of_match_table = dpa_match,
|
|
+ .owner = THIS_MODULE,
|
|
+ .pm = DPAA_PM_OPS,
|
|
+ },
|
|
+ .probe = dpaa_eth_priv_probe,
|
|
+ .remove = dpa_remove
|
|
+};
|
|
+
|
|
+#ifndef CONFIG_PPC
|
|
+static bool __init __cold soc_has_errata_a010022(void)
|
|
+{
|
|
+#ifdef CONFIG_SOC_BUS
|
|
+ const struct soc_device_attribute soc_msi_matches[] = {
|
|
+ { .family = "QorIQ LS1043A",
|
|
+ .data = NULL },
|
|
+ { },
|
|
+ };
|
|
+
|
|
+ if (soc_device_match(soc_msi_matches))
|
|
+ return true;
|
|
+
|
|
+ return false;
|
|
+#else
|
|
+ return true; /* cannot identify SoC */
|
|
+#endif
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int __init __cold dpa_load(void)
|
|
+{
|
|
+ int _errno;
|
|
+
|
|
+ pr_info(DPA_DESCRIPTION "\n");
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ dpa_debugfs_module_init();
|
|
+#endif /* CONFIG_FSL_DPAA_DBG_LOOP */
|
|
+
|
|
+ /* initialise dpaa_eth mirror values */
|
|
+ dpa_rx_extra_headroom = fm_get_rx_extra_headroom();
|
|
+ dpa_max_frm = fm_get_max_frm();
|
|
+ dpa_num_cpus = num_possible_cpus();
|
|
+
|
|
+#ifndef CONFIG_PPC
|
|
+ /* Detect if the current SoC requires the 4K alignment workaround */
|
|
+ dpaa_errata_a010022 = soc_has_errata_a010022();
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ memset(dpa_loop_netdevs, 0, sizeof(dpa_loop_netdevs));
|
|
+#endif
|
|
+
|
|
+ _errno = platform_driver_register(&dpa_driver);
|
|
+ if (unlikely(_errno < 0)) {
|
|
+ pr_err(KBUILD_MODNAME
|
|
+ ": %s:%hu:%s(): platform_driver_register() = %d\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__, _errno);
|
|
+ }
|
|
+
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+module_init(dpa_load);
|
|
+
|
|
+static void __exit __cold dpa_unload(void)
|
|
+{
|
|
+ pr_debug(KBUILD_MODNAME ": -> %s:%s()\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+
|
|
+ platform_driver_unregister(&dpa_driver);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ dpa_debugfs_module_exit();
|
|
+#endif /* CONFIG_FSL_DPAA_DBG_LOOP */
|
|
+
|
|
+ /* Only one channel is used and needs to be relased after all
|
|
+ * interfaces are removed
|
|
+ */
|
|
+ dpa_release_channel();
|
|
+
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+}
|
|
+module_exit(dpa_unload);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth.h
|
|
@@ -0,0 +1,697 @@
|
|
+/* Copyright 2008-2012 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifndef __DPA_H
|
|
+#define __DPA_H
|
|
+
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/fsl_qman.h> /* struct qman_fq */
|
|
+
|
|
+#include "fm_ext.h"
|
|
+#include "dpaa_eth_trace.h"
|
|
+
|
|
+extern int dpa_rx_extra_headroom;
|
|
+extern int dpa_max_frm;
|
|
+extern int dpa_num_cpus;
|
|
+
|
|
+#define dpa_get_rx_extra_headroom() dpa_rx_extra_headroom
|
|
+#define dpa_get_max_frm() dpa_max_frm
|
|
+
|
|
+#define dpa_get_max_mtu() \
|
|
+ (dpa_get_max_frm() - (VLAN_ETH_HLEN + ETH_FCS_LEN))
|
|
+
|
|
+#define __hot
|
|
+
|
|
+/* Simple enum of FQ types - used for array indexing */
|
|
+enum port_type {RX, TX};
|
|
+
|
|
+/* TODO: This structure should be renamed & moved to the FMD wrapper */
|
|
+struct dpa_buffer_layout_s {
|
|
+ uint16_t priv_data_size;
|
|
+ bool parse_results;
|
|
+ bool time_stamp;
|
|
+ bool hash_results;
|
|
+ uint8_t manip_extra_space;
|
|
+ uint16_t data_align;
|
|
+};
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define DPA_BUG_ON(cond) BUG_ON(cond)
|
|
+#else
|
|
+#define DPA_BUG_ON(cond)
|
|
+#endif
|
|
+
|
|
+#define DPA_TX_PRIV_DATA_SIZE 16
|
|
+#define DPA_PARSE_RESULTS_SIZE sizeof(fm_prs_result_t)
|
|
+#define DPA_TIME_STAMP_SIZE 8
|
|
+#define DPA_HASH_RESULTS_SIZE 8
|
|
+#define DPA_RX_PRIV_DATA_SIZE (DPA_TX_PRIV_DATA_SIZE + \
|
|
+ dpa_get_rx_extra_headroom())
|
|
+
|
|
+#define FM_FD_STAT_RX_ERRORS \
|
|
+ (FM_PORT_FRM_ERR_DMA | FM_PORT_FRM_ERR_PHYSICAL | \
|
|
+ FM_PORT_FRM_ERR_SIZE | FM_PORT_FRM_ERR_CLS_DISCARD | \
|
|
+ FM_PORT_FRM_ERR_EXTRACTION | FM_PORT_FRM_ERR_NO_SCHEME | \
|
|
+ FM_PORT_FRM_ERR_ILL_PLCR | FM_PORT_FRM_ERR_PRS_TIMEOUT | \
|
|
+ FM_PORT_FRM_ERR_PRS_ILL_INSTRUCT | FM_PORT_FRM_ERR_PRS_HDR_ERR)
|
|
+
|
|
+#define FM_FD_STAT_TX_ERRORS \
|
|
+ (FM_PORT_FRM_ERR_UNSUPPORTED_FORMAT | \
|
|
+ FM_PORT_FRM_ERR_LENGTH | FM_PORT_FRM_ERR_DMA)
|
|
+
|
|
+#ifndef CONFIG_FSL_DPAA_ETH_JUMBO_FRAME
|
|
+/* The raw buffer size must be cacheline aligned.
|
|
+ * Normally we use 2K buffers.
|
|
+ */
|
|
+#define DPA_BP_RAW_SIZE 2048
|
|
+#else
|
|
+/* For jumbo frame optimizations, use buffers large enough to accommodate
|
|
+ * 9.6K frames, FD maximum offset, skb sh_info overhead and some extra
|
|
+ * space to account for further alignments.
|
|
+ */
|
|
+#define DPA_MAX_FRM_SIZE 9600
|
|
+#ifdef CONFIG_PPC
|
|
+#define DPA_BP_RAW_SIZE \
|
|
+ ((DPA_MAX_FRM_SIZE + DPA_MAX_FD_OFFSET + \
|
|
+ sizeof(struct skb_shared_info) + 128) & ~(SMP_CACHE_BYTES - 1))
|
|
+#else /* CONFIG_PPC */
|
|
+#define DPA_BP_RAW_SIZE ((unlikely(dpaa_errata_a010022)) ? 2048 : \
|
|
+ ((DPA_MAX_FRM_SIZE + DPA_MAX_FD_OFFSET + \
|
|
+ sizeof(struct skb_shared_info) + 128) & ~(SMP_CACHE_BYTES - 1)))
|
|
+#endif /* CONFIG_PPC */
|
|
+#endif /* CONFIG_FSL_DPAA_ETH_JUMBO_FRAME */
|
|
+
|
|
+/* This is what FMan is ever allowed to use.
|
|
+ * FMan-DMA requires 16-byte alignment for Rx buffers, but SKB_DATA_ALIGN is
|
|
+ * even stronger (SMP_CACHE_BYTES-aligned), so we just get away with that,
|
|
+ * via SKB_WITH_OVERHEAD(). We can't rely on netdev_alloc_frag() giving us
|
|
+ * half-page-aligned buffers (can we?), so we reserve some more space
|
|
+ * for start-of-buffer alignment.
|
|
+ */
|
|
+#define dpa_bp_size(buffer_layout) (SKB_WITH_OVERHEAD(DPA_BP_RAW_SIZE) - \
|
|
+ SMP_CACHE_BYTES)
|
|
+/* We must ensure that skb_shinfo is always cacheline-aligned. */
|
|
+#define DPA_SKB_SIZE(size) ((size) & ~(SMP_CACHE_BYTES - 1))
|
|
+
|
|
+/* Maximum size of a buffer for which recycling is allowed.
|
|
+ * We need an upper limit such that forwarded skbs that get reallocated on Tx
|
|
+ * aren't allowed to grow unboundedly. On the other hand, we need to make sure
|
|
+ * that skbs allocated by us will not fail to be recycled due to their size.
|
|
+ *
|
|
+ * For a requested size, the kernel allocator provides the next power of two
|
|
+ * sized block, which the stack will use as is, regardless of the actual size
|
|
+ * it required; since we must accommodate at most 9.6K buffers (L2 maximum
|
|
+ * supported frame size), set the recycling upper limit to 16K.
|
|
+ */
|
|
+#define DPA_RECYCLE_MAX_SIZE 16384
|
|
+
|
|
+#if defined(CONFIG_FSL_SDK_FMAN_TEST)
|
|
+/*TODO: temporary for fman pcd testing */
|
|
+#define FMAN_PCD_TESTS_MAX_NUM_RANGES 20
|
|
+#endif
|
|
+
|
|
+#define DPAA_ETH_FQ_DELTA 0x10000
|
|
+
|
|
+#define DPAA_ETH_PCD_FQ_BASE(device_addr) \
|
|
+ (((device_addr) & 0x1fffff) >> 6)
|
|
+
|
|
+#define DPAA_ETH_PCD_FQ_HI_PRIO_BASE(device_addr) \
|
|
+ (DPAA_ETH_FQ_DELTA + DPAA_ETH_PCD_FQ_BASE(device_addr))
|
|
+
|
|
+/* Largest value that the FQD's OAL field can hold.
|
|
+ * This is DPAA-1.x specific.
|
|
+ * TODO: This rather belongs in fsl_qman.h
|
|
+ */
|
|
+#define FSL_QMAN_MAX_OAL 127
|
|
+
|
|
+/* Maximum offset value for a contig or sg FD (represented on 9 bits) */
|
|
+#define DPA_MAX_FD_OFFSET ((1 << 9) - 1)
|
|
+
|
|
+/* Default alignment for start of data in an Rx FD */
|
|
+#define DPA_FD_DATA_ALIGNMENT 16
|
|
+
|
|
+/* Values for the L3R field of the FM Parse Results
|
|
+ */
|
|
+/* L3 Type field: First IP Present IPv4 */
|
|
+#define FM_L3_PARSE_RESULT_IPV4 0x8000
|
|
+/* L3 Type field: First IP Present IPv6 */
|
|
+#define FM_L3_PARSE_RESULT_IPV6 0x4000
|
|
+
|
|
+/* Values for the L4R field of the FM Parse Results
|
|
+ * See $8.8.4.7.20 - L4 HXS - L4 Results from DPAA-Rev2 Reference Manual.
|
|
+ */
|
|
+/* L4 Type field: UDP */
|
|
+#define FM_L4_PARSE_RESULT_UDP 0x40
|
|
+/* L4 Type field: TCP */
|
|
+#define FM_L4_PARSE_RESULT_TCP 0x20
|
|
+/* FD status field indicating whether the FM Parser has attempted to validate
|
|
+ * the L4 csum of the frame.
|
|
+ * Note that having this bit set doesn't necessarily imply that the checksum
|
|
+ * is valid. One would have to check the parse results to find that out.
|
|
+ */
|
|
+#define FM_FD_STAT_L4CV 0x00000004
|
|
+
|
|
+
|
|
+#define FM_FD_STAT_ERR_PHYSICAL FM_PORT_FRM_ERR_PHYSICAL
|
|
+
|
|
+/* Check if the parsed frame was found to be a TCP segment.
|
|
+ *
|
|
+ * @parse_result_ptr must be of type (fm_prs_result_t *).
|
|
+ */
|
|
+#define fm_l4_frame_is_tcp(parse_result_ptr) \
|
|
+ ((parse_result_ptr)->l4r & FM_L4_PARSE_RESULT_TCP)
|
|
+
|
|
+/* number of Tx queues to FMan */
|
|
+#ifdef CONFIG_FMAN_PFC
|
|
+#define DPAA_ETH_TX_QUEUES (NR_CPUS * CONFIG_FMAN_PFC_COS_COUNT)
|
|
+#else
|
|
+#define DPAA_ETH_TX_QUEUES NR_CPUS
|
|
+#endif
|
|
+
|
|
+#define DPAA_ETH_RX_QUEUES 128
|
|
+
|
|
+/* Convenience macros for storing/retrieving the skb back-pointers. They must
|
|
+ * accommodate both recycling and confirmation paths - i.e. cases when the buf
|
|
+ * was allocated by ourselves, respectively by the stack. In the former case,
|
|
+ * we could store the skb at negative offset; in the latter case, we can't,
|
|
+ * so we'll use 0 as offset.
|
|
+ *
|
|
+ * NB: @off is an offset from a (struct sk_buff **) pointer!
|
|
+ */
|
|
+#define DPA_WRITE_SKB_PTR(skb, skbh, addr, off) \
|
|
+{ \
|
|
+ skbh = (struct sk_buff **)addr; \
|
|
+ *(skbh + (off)) = skb; \
|
|
+}
|
|
+#define DPA_READ_SKB_PTR(skb, skbh, addr, off) \
|
|
+{ \
|
|
+ skbh = (struct sk_buff **)addr; \
|
|
+ skb = *(skbh + (off)); \
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+/* Magic Packet wakeup */
|
|
+#define DPAA_WOL_MAGIC 0x00000001
|
|
+#endif
|
|
+
|
|
+#if defined(CONFIG_FSL_SDK_FMAN_TEST)
|
|
+struct pcd_range {
|
|
+ uint32_t base;
|
|
+ uint32_t count;
|
|
+};
|
|
+#endif
|
|
+
|
|
+/* More detailed FQ types - used for fine-grained WQ assignments */
|
|
+enum dpa_fq_type {
|
|
+ FQ_TYPE_RX_DEFAULT = 1, /* Rx Default FQs */
|
|
+ FQ_TYPE_RX_ERROR, /* Rx Error FQs */
|
|
+ FQ_TYPE_RX_PCD, /* User-defined PCDs */
|
|
+ FQ_TYPE_TX, /* "Real" Tx FQs */
|
|
+ FQ_TYPE_TX_CONFIRM, /* Tx default Conf FQ (actually an Rx FQ) */
|
|
+ FQ_TYPE_TX_CONF_MQ, /* Tx conf FQs (one for each Tx FQ) */
|
|
+ FQ_TYPE_TX_ERROR, /* Tx Error FQs (these are actually Rx FQs) */
|
|
+ FQ_TYPE_RX_PCD_HI_PRIO, /* User-defined high-priority PCDs */
|
|
+};
|
|
+
|
|
+struct dpa_fq {
|
|
+ struct qman_fq fq_base;
|
|
+ struct list_head list;
|
|
+ struct net_device *net_dev;
|
|
+ bool init;
|
|
+ uint32_t fqid;
|
|
+ uint32_t flags;
|
|
+ uint16_t channel;
|
|
+ uint8_t wq;
|
|
+ enum dpa_fq_type fq_type;
|
|
+};
|
|
+
|
|
+struct dpa_fq_cbs_t {
|
|
+ struct qman_fq rx_defq;
|
|
+ struct qman_fq tx_defq;
|
|
+ struct qman_fq rx_errq;
|
|
+ struct qman_fq tx_errq;
|
|
+ struct qman_fq egress_ern;
|
|
+};
|
|
+
|
|
+struct fqid_cell {
|
|
+ uint32_t start;
|
|
+ uint32_t count;
|
|
+};
|
|
+
|
|
+struct dpa_bp {
|
|
+ struct bman_pool *pool;
|
|
+ uint8_t bpid;
|
|
+ struct device *dev;
|
|
+ union {
|
|
+ /* The buffer pools used for the private ports are initialized
|
|
+ * with target_count buffers for each CPU; at runtime the
|
|
+ * number of buffers per CPU is constantly brought back to this
|
|
+ * level
|
|
+ */
|
|
+ int target_count;
|
|
+ /* The configured value for the number of buffers in the pool,
|
|
+ * used for shared port buffer pools
|
|
+ */
|
|
+ int config_count;
|
|
+ };
|
|
+ size_t size;
|
|
+ bool seed_pool;
|
|
+ /* physical address of the contiguous memory used by the pool to store
|
|
+ * the buffers
|
|
+ */
|
|
+ dma_addr_t paddr;
|
|
+ /* virtual address of the contiguous memory used by the pool to store
|
|
+ * the buffers
|
|
+ */
|
|
+ void __iomem *vaddr;
|
|
+ /* current number of buffers in the bpool alloted to this CPU */
|
|
+ int __percpu *percpu_count;
|
|
+ atomic_t refs;
|
|
+ /* some bpools need to be seeded before use by this cb */
|
|
+ int (*seed_cb)(struct dpa_bp *);
|
|
+ /* some bpools need to be emptied before freeing; this cb is used
|
|
+ * for freeing of individual buffers taken from the pool
|
|
+ */
|
|
+ void (*free_buf_cb)(void *addr);
|
|
+};
|
|
+
|
|
+struct dpa_rx_errors {
|
|
+ u64 dme; /* DMA Error */
|
|
+ u64 fpe; /* Frame Physical Error */
|
|
+ u64 fse; /* Frame Size Error */
|
|
+ u64 phe; /* Header Error */
|
|
+ u64 cse; /* Checksum Validation Error */
|
|
+};
|
|
+
|
|
+/* Counters for QMan ERN frames - one counter per rejection code */
|
|
+struct dpa_ern_cnt {
|
|
+ u64 cg_tdrop; /* Congestion group taildrop */
|
|
+ u64 wred; /* WRED congestion */
|
|
+ u64 err_cond; /* Error condition */
|
|
+ u64 early_window; /* Order restoration, frame too early */
|
|
+ u64 late_window; /* Order restoration, frame too late */
|
|
+ u64 fq_tdrop; /* FQ taildrop */
|
|
+ u64 fq_retired; /* FQ is retired */
|
|
+ u64 orp_zero; /* ORP disabled */
|
|
+};
|
|
+
|
|
+struct dpa_napi_portal {
|
|
+ struct napi_struct napi;
|
|
+ struct qman_portal *p;
|
|
+};
|
|
+
|
|
+struct dpa_percpu_priv_s {
|
|
+ struct net_device *net_dev;
|
|
+ struct dpa_napi_portal *np;
|
|
+ u64 in_interrupt;
|
|
+ u64 tx_returned;
|
|
+ u64 tx_confirm;
|
|
+ /* fragmented (non-linear) skbuffs received from the stack */
|
|
+ u64 tx_frag_skbuffs;
|
|
+ /* number of S/G frames received */
|
|
+ u64 rx_sg;
|
|
+
|
|
+ struct rtnl_link_stats64 stats;
|
|
+ struct dpa_rx_errors rx_errors;
|
|
+ struct dpa_ern_cnt ern_cnt;
|
|
+};
|
|
+
|
|
+struct dpa_priv_s {
|
|
+ struct dpa_percpu_priv_s __percpu *percpu_priv;
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ /* Store here the needed Tx headroom for convenience and speed
|
|
+ * (even though it can be computed based on the fields of buf_layout)
|
|
+ */
|
|
+ uint16_t tx_headroom;
|
|
+ struct net_device *net_dev;
|
|
+ struct mac_device *mac_dev;
|
|
+ struct qman_fq *egress_fqs[DPAA_ETH_TX_QUEUES];
|
|
+ struct qman_fq *conf_fqs[DPAA_ETH_TX_QUEUES];
|
|
+
|
|
+ size_t bp_count;
|
|
+
|
|
+ uint16_t channel; /* "fsl,qman-channel-id" */
|
|
+ struct list_head dpa_fq_list;
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ struct dentry *debugfs_loop_file;
|
|
+#endif
|
|
+
|
|
+ uint32_t msg_enable; /* net_device message level */
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ struct dpa_ptp_tsu *tsu;
|
|
+#endif
|
|
+
|
|
+#if defined(CONFIG_FSL_SDK_FMAN_TEST)
|
|
+/* TODO: this is temporary until pcd support is implemented in dpaa */
|
|
+ int priv_pcd_num_ranges;
|
|
+ struct pcd_range priv_pcd_ranges[FMAN_PCD_TESTS_MAX_NUM_RANGES];
|
|
+#endif
|
|
+
|
|
+ struct {
|
|
+ /**
|
|
+ * All egress queues to a given net device belong to one
|
|
+ * (and the same) congestion group.
|
|
+ */
|
|
+ struct qman_cgr cgr;
|
|
+ /* If congested, when it began. Used for performance stats. */
|
|
+ u32 congestion_start_jiffies;
|
|
+ /* Number of jiffies the Tx port was congested. */
|
|
+ u32 congested_jiffies;
|
|
+ /**
|
|
+ * Counter for the number of times the CGR
|
|
+ * entered congestion state
|
|
+ */
|
|
+ u32 cgr_congested_count;
|
|
+ } cgr_data;
|
|
+ /* Use a per-port CGR for ingress traffic. */
|
|
+ bool use_ingress_cgr;
|
|
+ struct qman_cgr ingress_cgr;
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ bool ts_tx_en; /* Tx timestamping enabled */
|
|
+ bool ts_rx_en; /* Rx timestamping enabled */
|
|
+#endif /* CONFIG_FSL_DPAA_TS */
|
|
+
|
|
+ struct dpa_buffer_layout_s *buf_layout;
|
|
+ uint16_t rx_headroom;
|
|
+ char if_type[30];
|
|
+
|
|
+ void *peer;
|
|
+#ifdef CONFIG_PM
|
|
+ u32 wol;
|
|
+#endif
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ int loop_id;
|
|
+ int loop_to;
|
|
+#endif
|
|
+#ifdef CONFIG_FSL_DPAA_CEETM
|
|
+ bool ceetm_en; /* CEETM QoS enabled */
|
|
+#endif
|
|
+};
|
|
+
|
|
+struct fm_port_fqs {
|
|
+ struct dpa_fq *tx_defq;
|
|
+ struct dpa_fq *tx_errq;
|
|
+ struct dpa_fq *rx_defq;
|
|
+ struct dpa_fq *rx_errq;
|
|
+};
|
|
+
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+extern struct net_device *dpa_loop_netdevs[20];
|
|
+#endif
|
|
+
|
|
+/* functions with different implementation for SG and non-SG: */
|
|
+int dpa_bp_priv_seed(struct dpa_bp *dpa_bp);
|
|
+int dpaa_eth_refill_bpools(struct dpa_bp *dpa_bp, int *count_ptr);
|
|
+void __hot _dpa_rx(struct net_device *net_dev,
|
|
+ struct qman_portal *portal,
|
|
+ const struct dpa_priv_s *priv,
|
|
+ struct dpa_percpu_priv_s *percpu_priv,
|
|
+ const struct qm_fd *fd,
|
|
+ u32 fqid,
|
|
+ int *count_ptr);
|
|
+int __hot dpa_tx(struct sk_buff *skb, struct net_device *net_dev);
|
|
+int __hot dpa_tx_extended(struct sk_buff *skb, struct net_device *net_dev,
|
|
+ struct qman_fq *egress_fq, struct qman_fq *conf_fq);
|
|
+struct sk_buff *_dpa_cleanup_tx_fd(const struct dpa_priv_s *priv,
|
|
+ const struct qm_fd *fd);
|
|
+void __hot _dpa_process_parse_results(const fm_prs_result_t *parse_results,
|
|
+ const struct qm_fd *fd,
|
|
+ struct sk_buff *skb,
|
|
+ int *use_gro);
|
|
+#ifndef CONFIG_FSL_DPAA_TS
|
|
+bool dpa_skb_is_recyclable(struct sk_buff *skb);
|
|
+bool dpa_buf_is_recyclable(struct sk_buff *skb,
|
|
+ uint32_t min_size,
|
|
+ uint16_t min_offset,
|
|
+ unsigned char **new_buf_start);
|
|
+#endif
|
|
+int __hot skb_to_contig_fd(struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, struct qm_fd *fd,
|
|
+ int *count_ptr, int *offset);
|
|
+int __hot skb_to_sg_fd(struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, struct qm_fd *fd);
|
|
+int __cold __attribute__((nonnull))
|
|
+ _dpa_fq_free(struct device *dev, struct qman_fq *fq);
|
|
+
|
|
+/* Turn on HW checksum computation for this outgoing frame.
|
|
+ * If the current protocol is not something we support in this regard
|
|
+ * (or if the stack has already computed the SW checksum), we do nothing.
|
|
+ *
|
|
+ * Returns 0 if all goes well (or HW csum doesn't apply), and a negative value
|
|
+ * otherwise.
|
|
+ *
|
|
+ * Note that this function may modify the fd->cmd field and the skb data buffer
|
|
+ * (the Parse Results area).
|
|
+ */
|
|
+int dpa_enable_tx_csum(struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, struct qm_fd *fd, char *parse_results);
|
|
+
|
|
+static inline int dpaa_eth_napi_schedule(struct dpa_percpu_priv_s *percpu_priv,
|
|
+ struct qman_portal *portal)
|
|
+{
|
|
+ /* In case of threaded ISR for RT enable kernel,
|
|
+ * in_irq() does not return appropriate value, so use
|
|
+ * in_serving_softirq to distinguish softirq or irq context.
|
|
+ */
|
|
+ if (unlikely(in_irq() || !in_serving_softirq())) {
|
|
+ /* Disable QMan IRQ and invoke NAPI */
|
|
+ int ret = qman_p_irqsource_remove(portal, QM_PIRQ_DQRI);
|
|
+ if (likely(!ret)) {
|
|
+ const struct qman_portal_config *pc =
|
|
+ qman_p_get_portal_config(portal);
|
|
+ struct dpa_napi_portal *np =
|
|
+ &percpu_priv->np[pc->index];
|
|
+
|
|
+ np->p = portal;
|
|
+ napi_schedule(&np->napi);
|
|
+ percpu_priv->in_interrupt++;
|
|
+ return 1;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline ssize_t __const __must_check __attribute__((nonnull))
|
|
+dpa_fd_length(const struct qm_fd *fd)
|
|
+{
|
|
+ return fd->length20;
|
|
+}
|
|
+
|
|
+static inline ssize_t __const __must_check __attribute__((nonnull))
|
|
+dpa_fd_offset(const struct qm_fd *fd)
|
|
+{
|
|
+ return fd->offset;
|
|
+}
|
|
+
|
|
+/* Verifies if the skb length is below the interface MTU */
|
|
+static inline int dpa_check_rx_mtu(struct sk_buff *skb, int mtu)
|
|
+{
|
|
+ if (unlikely(skb->len > mtu))
|
|
+ if ((skb->protocol != htons(ETH_P_8021Q))
|
|
+ || (skb->len > mtu + 4))
|
|
+ return -1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline uint16_t dpa_get_headroom(struct dpa_buffer_layout_s *bl)
|
|
+{
|
|
+ uint16_t headroom;
|
|
+ /* The frame headroom must accommodate:
|
|
+ * - the driver private data area
|
|
+ * - parse results, hash results, timestamp if selected
|
|
+ * - manip extra space
|
|
+ * If either hash results or time stamp are selected, both will
|
|
+ * be copied to/from the frame headroom, as TS is located between PR and
|
|
+ * HR in the IC and IC copy size has a granularity of 16bytes
|
|
+ * (see description of FMBM_RICP and FMBM_TICP registers in DPAARM)
|
|
+ *
|
|
+ * Also make sure the headroom is a multiple of data_align bytes
|
|
+ */
|
|
+ headroom = (uint16_t)(bl->priv_data_size +
|
|
+ (bl->parse_results ? DPA_PARSE_RESULTS_SIZE : 0) +
|
|
+ (bl->hash_results || bl->time_stamp ?
|
|
+ DPA_TIME_STAMP_SIZE + DPA_HASH_RESULTS_SIZE : 0) +
|
|
+ bl->manip_extra_space);
|
|
+
|
|
+ return bl->data_align ? ALIGN(headroom, bl->data_align) : headroom;
|
|
+}
|
|
+
|
|
+int fm_mac_dump_regs(struct mac_device *h_dev, char *buf, int n);
|
|
+int fm_mac_dump_rx_stats(struct mac_device *h_dev, char *buf, int n);
|
|
+int fm_mac_dump_tx_stats(struct mac_device *h_dev, char *buf, int n);
|
|
+
|
|
+void dpaa_eth_sysfs_remove(struct device *dev);
|
|
+void dpaa_eth_sysfs_init(struct device *dev);
|
|
+int dpaa_eth_poll(struct napi_struct *napi, int budget);
|
|
+
|
|
+void dpa_private_napi_del(struct net_device *net_dev);
|
|
+
|
|
+/* Equivalent to a memset(0), but works faster */
|
|
+static inline void clear_fd(struct qm_fd *fd)
|
|
+{
|
|
+ fd->opaque_addr = 0;
|
|
+ fd->opaque = 0;
|
|
+ fd->cmd = 0;
|
|
+}
|
|
+
|
|
+static inline int _dpa_tx_fq_to_id(const struct dpa_priv_s *priv,
|
|
+ struct qman_fq *tx_fq)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < DPAA_ETH_TX_QUEUES; i++)
|
|
+ if (priv->egress_fqs[i] == tx_fq)
|
|
+ return i;
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static inline int __hot dpa_xmit(struct dpa_priv_s *priv,
|
|
+ struct rtnl_link_stats64 *percpu_stats,
|
|
+ struct qm_fd *fd, struct qman_fq *egress_fq,
|
|
+ struct qman_fq *conf_fq)
|
|
+{
|
|
+ int err, i;
|
|
+
|
|
+ if (fd->bpid == 0xff)
|
|
+ fd->cmd |= qman_fq_fqid(conf_fq);
|
|
+
|
|
+ /* Trace this Tx fd */
|
|
+ trace_dpa_tx_fd(priv->net_dev, egress_fq, fd);
|
|
+
|
|
+ for (i = 0; i < 100000; i++) {
|
|
+ err = qman_enqueue(egress_fq, fd, 0);
|
|
+ if (err != -EBUSY)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (unlikely(err < 0)) {
|
|
+ /* TODO differentiate b/w -EBUSY (EQCR full) and other codes? */
|
|
+ percpu_stats->tx_errors++;
|
|
+ percpu_stats->tx_fifo_errors++;
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ percpu_stats->tx_packets++;
|
|
+ percpu_stats->tx_bytes += dpa_fd_length(fd);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Use multiple WQs for FQ assignment:
|
|
+ * - Tx Confirmation queues go to WQ1.
|
|
+ * - Rx Default, Tx and PCD queues go to WQ3 (no differentiation between
|
|
+ * Rx and Tx traffic, or between Rx Default and Rx PCD frames).
|
|
+ * - Rx Error and Tx Error queues go to WQ2 (giving them a better chance
|
|
+ * to be scheduled, in case there are many more FQs in WQ3).
|
|
+ * This ensures that Tx-confirmed buffers are timely released. In particular,
|
|
+ * it avoids congestion on the Tx Confirm FQs, which can pile up PFDRs if they
|
|
+ * are greatly outnumbered by other FQs in the system (usually PCDs), while
|
|
+ * dequeue scheduling is round-robin.
|
|
+ */
|
|
+static inline void _dpa_assign_wq(struct dpa_fq *fq)
|
|
+{
|
|
+ switch (fq->fq_type) {
|
|
+ case FQ_TYPE_TX_CONFIRM:
|
|
+ case FQ_TYPE_TX_CONF_MQ:
|
|
+ fq->wq = 1;
|
|
+ break;
|
|
+ case FQ_TYPE_RX_DEFAULT:
|
|
+ case FQ_TYPE_TX:
|
|
+ fq->wq = 3;
|
|
+ break;
|
|
+ case FQ_TYPE_RX_ERROR:
|
|
+ case FQ_TYPE_TX_ERROR:
|
|
+ case FQ_TYPE_RX_PCD_HI_PRIO:
|
|
+ fq->wq = 2;
|
|
+ break;
|
|
+ case FQ_TYPE_RX_PCD:
|
|
+ fq->wq = 5;
|
|
+ break;
|
|
+ default:
|
|
+ WARN(1, "Invalid FQ type %d for FQID %d!\n",
|
|
+ fq->fq_type, fq->fqid);
|
|
+ }
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_USE_NDO_SELECT_QUEUE
|
|
+/* Use in lieu of skb_get_queue_mapping() */
|
|
+#ifdef CONFIG_FMAN_PFC
|
|
+#define dpa_get_queue_mapping(skb) \
|
|
+ (((skb)->priority < CONFIG_FMAN_PFC_COS_COUNT) ? \
|
|
+ ((skb)->priority * dpa_num_cpus + smp_processor_id()) : \
|
|
+ ((CONFIG_FMAN_PFC_COS_COUNT - 1) * \
|
|
+ dpa_num_cpus + smp_processor_id()));
|
|
+
|
|
+#else
|
|
+#define dpa_get_queue_mapping(skb) \
|
|
+ raw_smp_processor_id()
|
|
+#endif
|
|
+#else
|
|
+/* Use the queue selected by XPS */
|
|
+#define dpa_get_queue_mapping(skb) \
|
|
+ skb_get_queue_mapping(skb)
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_PTP_1588_CLOCK_DPAA
|
|
+struct ptp_priv_s {
|
|
+ struct device_node *node;
|
|
+ struct platform_device *of_dev;
|
|
+ struct mac_device *mac_dev;
|
|
+};
|
|
+extern struct ptp_priv_s ptp_priv;
|
|
+#endif
|
|
+
|
|
+static inline void _dpa_bp_free_pf(void *addr)
|
|
+{
|
|
+ put_page(virt_to_head_page(addr));
|
|
+}
|
|
+
|
|
+/* TODO: LS1043A SoC has a HW issue regarding FMan DMA transactions; The issue
|
|
+ * manifests itself at high traffic rates when frames exceed 4K memory
|
|
+ * boundaries; For the moment, we use a SW workaround to avoid frames larger
|
|
+ * than 4K or that exceed 4K alignments.
|
|
+ */
|
|
+
|
|
+#ifndef CONFIG_PPC
|
|
+extern bool dpaa_errata_a010022; /* SoC affected by A010022 errata */
|
|
+
|
|
+#define HAS_DMA_ISSUE(start, size) \
|
|
+ (((u64)(start) + (size)) > (((u64)(start) + 0x1000) & ~0xFFF))
|
|
+#define BOUNDARY_4K(start, size) (((u64)(start) + (u64)(size)) & ~0xFFF)
|
|
+
|
|
+#endif /* !CONFIG_PPC */
|
|
+
|
|
+#endif /* __DPA_H */
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_base.c
|
|
@@ -0,0 +1,263 @@
|
|
+/* Copyright 2008-2013 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": %s:%hu:%s() " fmt, \
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__
|
|
+#else
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": " fmt
|
|
+#endif
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/of_net.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/percpu.h>
|
|
+#include <linux/highmem.h>
|
|
+#include <linux/sort.h>
|
|
+#include <linux/fsl_qman.h>
|
|
+#include "dpaa_eth.h"
|
|
+#include "dpaa_eth_common.h"
|
|
+#include "dpaa_eth_base.h"
|
|
+
|
|
+#define DPA_DESCRIPTION "FSL DPAA Advanced drivers:"
|
|
+
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
+
|
|
+uint8_t advanced_debug = -1;
|
|
+module_param(advanced_debug, byte, S_IRUGO);
|
|
+MODULE_PARM_DESC(advanced_debug, "Module/Driver verbosity level");
|
|
+EXPORT_SYMBOL(advanced_debug);
|
|
+
|
|
+static int dpa_bp_cmp(const void *dpa_bp0, const void *dpa_bp1)
|
|
+{
|
|
+ return ((struct dpa_bp *)dpa_bp0)->size -
|
|
+ ((struct dpa_bp *)dpa_bp1)->size;
|
|
+}
|
|
+
|
|
+struct dpa_bp * __cold __must_check /* __attribute__((nonnull)) */
|
|
+dpa_bp_probe(struct platform_device *_of_dev, size_t *count)
|
|
+{
|
|
+ int i, lenp, na, ns, err;
|
|
+ struct device *dev;
|
|
+ struct device_node *dev_node;
|
|
+ const __be32 *bpool_cfg;
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ u32 bpid;
|
|
+
|
|
+ dev = &_of_dev->dev;
|
|
+
|
|
+ *count = of_count_phandle_with_args(dev->of_node,
|
|
+ "fsl,bman-buffer-pools", NULL);
|
|
+ if (*count < 1) {
|
|
+ dev_err(dev, "missing fsl,bman-buffer-pools device tree entry\n");
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+
|
|
+ dpa_bp = devm_kzalloc(dev, *count * sizeof(*dpa_bp), GFP_KERNEL);
|
|
+ if (dpa_bp == NULL) {
|
|
+ dev_err(dev, "devm_kzalloc() failed\n");
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+ }
|
|
+
|
|
+ dev_node = of_find_node_by_path("/");
|
|
+ if (unlikely(dev_node == NULL)) {
|
|
+ dev_err(dev, "of_find_node_by_path(/) failed\n");
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+
|
|
+ na = of_n_addr_cells(dev_node);
|
|
+ ns = of_n_size_cells(dev_node);
|
|
+
|
|
+ for (i = 0; i < *count; i++) {
|
|
+ of_node_put(dev_node);
|
|
+
|
|
+ dev_node = of_parse_phandle(dev->of_node,
|
|
+ "fsl,bman-buffer-pools", i);
|
|
+ if (dev_node == NULL) {
|
|
+ dev_err(dev, "of_find_node_by_phandle() failed\n");
|
|
+ return ERR_PTR(-EFAULT);
|
|
+ }
|
|
+
|
|
+ if (unlikely(!of_device_is_compatible(dev_node, "fsl,bpool"))) {
|
|
+ dev_err(dev,
|
|
+ "!of_device_is_compatible(%s, fsl,bpool)\n",
|
|
+ dev_node->full_name);
|
|
+ dpa_bp = ERR_PTR(-EINVAL);
|
|
+ goto _return_of_node_put;
|
|
+ }
|
|
+
|
|
+ err = of_property_read_u32(dev_node, "fsl,bpid", &bpid);
|
|
+ if (err) {
|
|
+ dev_err(dev, "Cannot find buffer pool ID in the device tree\n");
|
|
+ dpa_bp = ERR_PTR(-EINVAL);
|
|
+ goto _return_of_node_put;
|
|
+ }
|
|
+ dpa_bp[i].bpid = (uint8_t)bpid;
|
|
+
|
|
+ bpool_cfg = of_get_property(dev_node, "fsl,bpool-ethernet-cfg",
|
|
+ &lenp);
|
|
+ if (bpool_cfg && (lenp == (2 * ns + na) * sizeof(*bpool_cfg))) {
|
|
+ const uint32_t *seed_pool;
|
|
+
|
|
+ dpa_bp[i].config_count =
|
|
+ (int)of_read_number(bpool_cfg, ns);
|
|
+ dpa_bp[i].size =
|
|
+ (size_t)of_read_number(bpool_cfg + ns, ns);
|
|
+ dpa_bp[i].paddr =
|
|
+ of_read_number(bpool_cfg + 2 * ns, na);
|
|
+
|
|
+ seed_pool = of_get_property(dev_node,
|
|
+ "fsl,bpool-ethernet-seeds", &lenp);
|
|
+ dpa_bp[i].seed_pool = !!seed_pool;
|
|
+
|
|
+ } else {
|
|
+ dev_err(dev,
|
|
+ "Missing/invalid fsl,bpool-ethernet-cfg device tree entry for node %s\n",
|
|
+ dev_node->full_name);
|
|
+ dpa_bp = ERR_PTR(-EINVAL);
|
|
+ goto _return_of_node_put;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ sort(dpa_bp, *count, sizeof(*dpa_bp), dpa_bp_cmp, NULL);
|
|
+
|
|
+ return dpa_bp;
|
|
+
|
|
+_return_of_node_put:
|
|
+ if (dev_node)
|
|
+ of_node_put(dev_node);
|
|
+
|
|
+ return dpa_bp;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_bp_probe);
|
|
+
|
|
+int dpa_bp_shared_port_seed(struct dpa_bp *bp)
|
|
+{
|
|
+ void __iomem **ptr;
|
|
+
|
|
+ /* In MAC-less and Shared-MAC scenarios the physical
|
|
+ * address of the buffer pool in device tree is set
|
|
+ * to 0 to specify that another entity (USDPAA) will
|
|
+ * allocate and seed the buffers
|
|
+ */
|
|
+ if (!bp->paddr)
|
|
+ return 0;
|
|
+
|
|
+ /* allocate memory region for buffers */
|
|
+ devm_request_mem_region(bp->dev, bp->paddr,
|
|
+ bp->size * bp->config_count, KBUILD_MODNAME);
|
|
+ /* managed ioremap unmapping */
|
|
+ ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL);
|
|
+ if (!ptr)
|
|
+ return -EIO;
|
|
+#ifndef CONFIG_PPC
|
|
+ bp->vaddr = ioremap_cache_ns(bp->paddr, bp->size * bp->config_count);
|
|
+#else
|
|
+ bp->vaddr = ioremap_prot(bp->paddr, bp->size * bp->config_count, 0);
|
|
+#endif
|
|
+ if (bp->vaddr == NULL) {
|
|
+ pr_err("Could not map memory for pool %d\n", bp->bpid);
|
|
+ devres_free(ptr);
|
|
+ return -EIO;
|
|
+ }
|
|
+ *ptr = bp->vaddr;
|
|
+ devres_add(bp->dev, ptr);
|
|
+
|
|
+ /* seed pool with buffers from that memory region */
|
|
+ if (bp->seed_pool) {
|
|
+ int count = bp->target_count;
|
|
+ dma_addr_t addr = bp->paddr;
|
|
+
|
|
+ while (count) {
|
|
+ struct bm_buffer bufs[8];
|
|
+ uint8_t num_bufs = 0;
|
|
+
|
|
+ do {
|
|
+ BUG_ON(addr > 0xffffffffffffull);
|
|
+ bufs[num_bufs].bpid = bp->bpid;
|
|
+ bm_buffer_set64(&bufs[num_bufs++], addr);
|
|
+ addr += bp->size;
|
|
+
|
|
+ } while (--count && (num_bufs < 8));
|
|
+
|
|
+ while (bman_release(bp->pool, bufs, num_bufs, 0))
|
|
+ cpu_relax();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_bp_shared_port_seed);
|
|
+
|
|
+int dpa_bp_create(struct net_device *net_dev, struct dpa_bp *dpa_bp,
|
|
+ size_t count)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ int i;
|
|
+
|
|
+ priv->dpa_bp = dpa_bp;
|
|
+ priv->bp_count = count;
|
|
+
|
|
+ for (i = 0; i < count; i++) {
|
|
+ int err;
|
|
+ err = dpa_bp_alloc(&dpa_bp[i]);
|
|
+ if (err < 0) {
|
|
+ dpa_bp_free(priv);
|
|
+ priv->dpa_bp = NULL;
|
|
+ return err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_bp_create);
|
|
+
|
|
+static int __init __cold dpa_advanced_load(void)
|
|
+{
|
|
+ pr_info(DPA_DESCRIPTION "\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+module_init(dpa_advanced_load);
|
|
+
|
|
+static void __exit __cold dpa_advanced_unload(void)
|
|
+{
|
|
+ pr_debug(KBUILD_MODNAME ": -> %s:%s()\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+
|
|
+}
|
|
+module_exit(dpa_advanced_unload);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_base.h
|
|
@@ -0,0 +1,50 @@
|
|
+/* Copyright 2008-2013 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifndef __DPAA_ETH_BASE_H
|
|
+#define __DPAA_ETH_BASE_H
|
|
+
|
|
+#include <linux/etherdevice.h> /* struct net_device */
|
|
+#include <linux/fsl_bman.h> /* struct bm_buffer */
|
|
+#include <linux/of_platform.h> /* struct platform_device */
|
|
+#include <linux/net_tstamp.h> /* struct hwtstamp_config */
|
|
+
|
|
+extern uint8_t advanced_debug;
|
|
+extern const struct dpa_fq_cbs_t shared_fq_cbs;
|
|
+extern int __hot dpa_shared_tx(struct sk_buff *skb, struct net_device *net_dev);
|
|
+
|
|
+struct dpa_bp * __cold __must_check /* __attribute__((nonnull)) */
|
|
+dpa_bp_probe(struct platform_device *_of_dev, size_t *count);
|
|
+int dpa_bp_create(struct net_device *net_dev, struct dpa_bp *dpa_bp,
|
|
+ size_t count);
|
|
+int dpa_bp_shared_port_seed(struct dpa_bp *bp);
|
|
+
|
|
+#endif /* __DPAA_ETH_BASE_H */
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_ceetm.c
|
|
@@ -0,0 +1,1991 @@
|
|
+/* Copyright 2008-2016 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include "dpaa_eth_ceetm.h"
|
|
+
|
|
+#define DPA_CEETM_DESCRIPTION "FSL DPAA CEETM qdisc"
|
|
+
|
|
+const struct nla_policy ceetm_policy[TCA_CEETM_MAX + 1] = {
|
|
+ [TCA_CEETM_COPT] = { .len = sizeof(struct tc_ceetm_copt) },
|
|
+ [TCA_CEETM_QOPS] = { .len = sizeof(struct tc_ceetm_qopt) },
|
|
+};
|
|
+
|
|
+struct Qdisc_ops ceetm_qdisc_ops;
|
|
+
|
|
+/* Obtain the DCP and the SP ids from the FMan port */
|
|
+static void get_dcp_and_sp(struct net_device *dev, enum qm_dc_portal *dcp_id,
|
|
+ unsigned int *sp_id)
|
|
+{
|
|
+ uint32_t channel;
|
|
+ t_LnxWrpFmPortDev *port_dev;
|
|
+ struct dpa_priv_s *dpa_priv = netdev_priv(dev);
|
|
+ struct mac_device *mac_dev = dpa_priv->mac_dev;
|
|
+
|
|
+ port_dev = (t_LnxWrpFmPortDev *)mac_dev->port_dev[TX];
|
|
+ channel = port_dev->txCh;
|
|
+
|
|
+ *sp_id = channel & CHANNEL_SP_MASK;
|
|
+ pr_debug(KBUILD_BASENAME " : FM sub-portal ID %d\n", *sp_id);
|
|
+
|
|
+ if (channel < DCP0_MAX_CHANNEL) {
|
|
+ *dcp_id = qm_dc_portal_fman0;
|
|
+ pr_debug(KBUILD_BASENAME " : DCP ID 0\n");
|
|
+ } else {
|
|
+ *dcp_id = qm_dc_portal_fman1;
|
|
+ pr_debug(KBUILD_BASENAME " : DCP ID 1\n");
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Enqueue Rejection Notification callback */
|
|
+static void ceetm_ern(struct qman_portal *portal, struct qman_fq *fq,
|
|
+ const struct qm_mr_entry *msg)
|
|
+{
|
|
+ struct net_device *net_dev;
|
|
+ struct ceetm_class *cls;
|
|
+ struct ceetm_class_stats *cstats = NULL;
|
|
+ const struct dpa_priv_s *dpa_priv;
|
|
+ struct dpa_percpu_priv_s *dpa_percpu_priv;
|
|
+ struct sk_buff *skb;
|
|
+ struct qm_fd fd = msg->ern.fd;
|
|
+
|
|
+ net_dev = ((struct ceetm_fq *)fq)->net_dev;
|
|
+ dpa_priv = netdev_priv(net_dev);
|
|
+ dpa_percpu_priv = raw_cpu_ptr(dpa_priv->percpu_priv);
|
|
+
|
|
+ /* Increment DPA counters */
|
|
+ dpa_percpu_priv->stats.tx_dropped++;
|
|
+ dpa_percpu_priv->stats.tx_fifo_errors++;
|
|
+
|
|
+ /* Increment CEETM counters */
|
|
+ cls = ((struct ceetm_fq *)fq)->ceetm_cls;
|
|
+ switch (cls->type) {
|
|
+ case CEETM_PRIO:
|
|
+ cstats = this_cpu_ptr(cls->prio.cstats);
|
|
+ break;
|
|
+ case CEETM_WBFS:
|
|
+ cstats = this_cpu_ptr(cls->wbfs.cstats);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (cstats)
|
|
+ cstats->ern_drop_count++;
|
|
+
|
|
+ if (fd.bpid != 0xff) {
|
|
+ dpa_fd_release(net_dev, &fd);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ skb = _dpa_cleanup_tx_fd(dpa_priv, &fd);
|
|
+ dev_kfree_skb_any(skb);
|
|
+}
|
|
+
|
|
+/* Congestion State Change Notification callback */
|
|
+static void ceetm_cscn(struct qm_ceetm_ccg *ccg, void *cb_ctx, int congested)
|
|
+{
|
|
+ struct ceetm_fq *ceetm_fq = (struct ceetm_fq *)cb_ctx;
|
|
+ struct dpa_priv_s *dpa_priv = netdev_priv(ceetm_fq->net_dev);
|
|
+ struct ceetm_class *cls = ceetm_fq->ceetm_cls;
|
|
+ struct ceetm_class_stats *cstats = NULL;
|
|
+
|
|
+ switch (cls->type) {
|
|
+ case CEETM_PRIO:
|
|
+ cstats = this_cpu_ptr(cls->prio.cstats);
|
|
+ break;
|
|
+ case CEETM_WBFS:
|
|
+ cstats = this_cpu_ptr(cls->wbfs.cstats);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (congested) {
|
|
+ dpa_priv->cgr_data.congestion_start_jiffies = jiffies;
|
|
+ netif_tx_stop_all_queues(dpa_priv->net_dev);
|
|
+ dpa_priv->cgr_data.cgr_congested_count++;
|
|
+ if (cstats)
|
|
+ cstats->congested_count++;
|
|
+ } else {
|
|
+ dpa_priv->cgr_data.congested_jiffies +=
|
|
+ (jiffies - dpa_priv->cgr_data.congestion_start_jiffies);
|
|
+ netif_tx_wake_all_queues(dpa_priv->net_dev);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Allocate a ceetm fq */
|
|
+static int ceetm_alloc_fq(struct ceetm_fq **fq, struct net_device *dev,
|
|
+ struct ceetm_class *cls)
|
|
+{
|
|
+ *fq = kzalloc(sizeof(**fq), GFP_KERNEL);
|
|
+ if (!*fq)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ (*fq)->net_dev = dev;
|
|
+ (*fq)->ceetm_cls = cls;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Configure a ceetm Class Congestion Group */
|
|
+static int ceetm_config_ccg(struct qm_ceetm_ccg **ccg,
|
|
+ struct qm_ceetm_channel *channel, unsigned int id,
|
|
+ struct ceetm_fq *fq, struct dpa_priv_s *dpa_priv)
|
|
+{
|
|
+ int err;
|
|
+ u32 cs_th;
|
|
+ u16 ccg_mask;
|
|
+ struct qm_ceetm_ccg_params ccg_params;
|
|
+
|
|
+ err = qman_ceetm_ccg_claim(ccg, channel, id, ceetm_cscn, fq);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ /* Configure the count mode (frames/bytes), enable congestion state
|
|
+ * notifications, configure the congestion entry and exit thresholds,
|
|
+ * enable tail-drop, configure the tail-drop mode, and set the
|
|
+ * overhead accounting limit
|
|
+ */
|
|
+ ccg_mask = QM_CCGR_WE_MODE |
|
|
+ QM_CCGR_WE_CSCN_EN |
|
|
+ QM_CCGR_WE_CS_THRES_IN | QM_CCGR_WE_CS_THRES_OUT |
|
|
+ QM_CCGR_WE_TD_EN | QM_CCGR_WE_TD_MODE |
|
|
+ QM_CCGR_WE_OAL;
|
|
+
|
|
+ ccg_params.mode = 0; /* count bytes */
|
|
+ ccg_params.cscn_en = 1; /* generate notifications */
|
|
+ ccg_params.td_en = 1; /* enable tail-drop */
|
|
+ ccg_params.td_mode = 0; /* tail-drop on congestion state */
|
|
+ ccg_params.oal = (signed char)(min(sizeof(struct sk_buff) +
|
|
+ dpa_priv->tx_headroom, (size_t)FSL_QMAN_MAX_OAL));
|
|
+
|
|
+ /* Set the congestion state thresholds according to the link speed */
|
|
+ if (dpa_priv->mac_dev->if_support & SUPPORTED_10000baseT_Full)
|
|
+ cs_th = CONFIG_FSL_DPAA_CS_THRESHOLD_10G;
|
|
+ else
|
|
+ cs_th = CONFIG_FSL_DPAA_CS_THRESHOLD_1G;
|
|
+
|
|
+ qm_cgr_cs_thres_set64(&ccg_params.cs_thres_in, cs_th, 1);
|
|
+ qm_cgr_cs_thres_set64(&ccg_params.cs_thres_out,
|
|
+ cs_th * CEETM_CCGR_RATIO, 1);
|
|
+
|
|
+ err = qman_ceetm_ccg_set(*ccg, ccg_mask, &ccg_params);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Configure a ceetm Logical Frame Queue */
|
|
+static int ceetm_config_lfq(struct qm_ceetm_cq *cq, struct ceetm_fq *fq,
|
|
+ struct qm_ceetm_lfq **lfq)
|
|
+{
|
|
+ int err;
|
|
+ u64 context_a;
|
|
+ u32 context_b;
|
|
+
|
|
+ err = qman_ceetm_lfq_claim(lfq, cq);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ /* Get the former contexts in order to preserve context B */
|
|
+ err = qman_ceetm_lfq_get_context(*lfq, &context_a, &context_b);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ context_a = CEETM_CONTEXT_A;
|
|
+ err = qman_ceetm_lfq_set_context(*lfq, context_a, context_b);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ (*lfq)->ern = ceetm_ern;
|
|
+
|
|
+ err = qman_ceetm_create_fq(*lfq, &fq->fq);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Configure a prio ceetm class */
|
|
+static int ceetm_config_prio_cls(struct ceetm_class *cls,
|
|
+ struct net_device *dev,
|
|
+ struct qm_ceetm_channel *channel,
|
|
+ unsigned int id)
|
|
+{
|
|
+ int err;
|
|
+ struct dpa_priv_s *dpa_priv = netdev_priv(dev);
|
|
+
|
|
+ err = ceetm_alloc_fq(&cls->prio.fq, dev, cls);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ /* Claim and configure the CCG */
|
|
+ err = ceetm_config_ccg(&cls->prio.ccg, channel, id, cls->prio.fq,
|
|
+ dpa_priv);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ /* Claim and configure the CQ */
|
|
+ err = qman_ceetm_cq_claim(&cls->prio.cq, channel, id, cls->prio.ccg);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ if (cls->shaped) {
|
|
+ err = qman_ceetm_channel_set_cq_cr_eligibility(channel, id, 1);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ err = qman_ceetm_channel_set_cq_er_eligibility(channel, id, 1);
|
|
+ if (err)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* Claim and configure a LFQ */
|
|
+ err = ceetm_config_lfq(cls->prio.cq, cls->prio.fq, &cls->prio.lfq);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Configure a wbfs ceetm class */
|
|
+static int ceetm_config_wbfs_cls(struct ceetm_class *cls,
|
|
+ struct net_device *dev,
|
|
+ struct qm_ceetm_channel *channel,
|
|
+ unsigned int id, int type)
|
|
+{
|
|
+ int err;
|
|
+ struct dpa_priv_s *dpa_priv = netdev_priv(dev);
|
|
+
|
|
+ err = ceetm_alloc_fq(&cls->wbfs.fq, dev, cls);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ /* Claim and configure the CCG */
|
|
+ err = ceetm_config_ccg(&cls->wbfs.ccg, channel, id, cls->wbfs.fq,
|
|
+ dpa_priv);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ /* Claim and configure the CQ */
|
|
+ if (type == WBFS_GRP_B)
|
|
+ err = qman_ceetm_cq_claim_B(&cls->wbfs.cq, channel, id,
|
|
+ cls->wbfs.ccg);
|
|
+ else
|
|
+ err = qman_ceetm_cq_claim_A(&cls->wbfs.cq, channel, id,
|
|
+ cls->wbfs.ccg);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ /* Configure the CQ weight: real number multiplied by 100 to get rid
|
|
+ * of the fraction
|
|
+ */
|
|
+ err = qman_ceetm_set_queue_weight_in_ratio(cls->wbfs.cq,
|
|
+ cls->wbfs.weight * 100);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ /* Claim and configure a LFQ */
|
|
+ err = ceetm_config_lfq(cls->wbfs.cq, cls->wbfs.fq, &cls->wbfs.lfq);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Find class in qdisc hash table using given handle */
|
|
+static inline struct ceetm_class *ceetm_find(u32 handle, struct Qdisc *sch)
|
|
+{
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct Qdisc_class_common *clc;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : find class %X in qdisc %X\n",
|
|
+ __func__, handle, sch->handle);
|
|
+
|
|
+ clc = qdisc_class_find(&priv->clhash, handle);
|
|
+ return clc ? container_of(clc, struct ceetm_class, common) : NULL;
|
|
+}
|
|
+
|
|
+/* Insert a class in the qdisc's class hash */
|
|
+static void ceetm_link_class(struct Qdisc *sch,
|
|
+ struct Qdisc_class_hash *clhash,
|
|
+ struct Qdisc_class_common *common)
|
|
+{
|
|
+ sch_tree_lock(sch);
|
|
+ qdisc_class_hash_insert(clhash, common);
|
|
+ sch_tree_unlock(sch);
|
|
+ qdisc_class_hash_grow(sch, clhash);
|
|
+}
|
|
+
|
|
+/* Destroy a ceetm class */
|
|
+static void ceetm_cls_destroy(struct Qdisc *sch, struct ceetm_class *cl)
|
|
+{
|
|
+ if (!cl)
|
|
+ return;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : destroy class %X from under %X\n",
|
|
+ __func__, cl->common.classid, sch->handle);
|
|
+
|
|
+ switch (cl->type) {
|
|
+ case CEETM_ROOT:
|
|
+ if (cl->root.child) {
|
|
+ qdisc_destroy(cl->root.child);
|
|
+ cl->root.child = NULL;
|
|
+ }
|
|
+
|
|
+ if (cl->root.ch && qman_ceetm_channel_release(cl->root.ch))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the channel %d\n",
|
|
+ __func__, cl->root.ch->idx);
|
|
+
|
|
+ break;
|
|
+
|
|
+ case CEETM_PRIO:
|
|
+ if (cl->prio.child) {
|
|
+ qdisc_destroy(cl->prio.child);
|
|
+ cl->prio.child = NULL;
|
|
+ }
|
|
+
|
|
+ if (cl->prio.lfq && qman_ceetm_lfq_release(cl->prio.lfq))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the LFQ %d\n",
|
|
+ __func__, cl->prio.lfq->idx);
|
|
+
|
|
+ if (cl->prio.cq && qman_ceetm_cq_release(cl->prio.cq))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the CQ %d\n",
|
|
+ __func__, cl->prio.cq->idx);
|
|
+
|
|
+ if (cl->prio.ccg && qman_ceetm_ccg_release(cl->prio.ccg))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the CCG %d\n",
|
|
+ __func__, cl->prio.ccg->idx);
|
|
+
|
|
+ kfree(cl->prio.fq);
|
|
+
|
|
+ if (cl->prio.cstats)
|
|
+ free_percpu(cl->prio.cstats);
|
|
+
|
|
+ break;
|
|
+
|
|
+ case CEETM_WBFS:
|
|
+ if (cl->wbfs.lfq && qman_ceetm_lfq_release(cl->wbfs.lfq))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the LFQ %d\n",
|
|
+ __func__, cl->wbfs.lfq->idx);
|
|
+
|
|
+ if (cl->wbfs.cq && qman_ceetm_cq_release(cl->wbfs.cq))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the CQ %d\n",
|
|
+ __func__, cl->wbfs.cq->idx);
|
|
+
|
|
+ if (cl->wbfs.ccg && qman_ceetm_ccg_release(cl->wbfs.ccg))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the CCG %d\n",
|
|
+ __func__, cl->wbfs.ccg->idx);
|
|
+
|
|
+ kfree(cl->wbfs.fq);
|
|
+
|
|
+ if (cl->wbfs.cstats)
|
|
+ free_percpu(cl->wbfs.cstats);
|
|
+ }
|
|
+
|
|
+ tcf_destroy_chain(&cl->filter_list);
|
|
+ kfree(cl);
|
|
+}
|
|
+
|
|
+/* Destroy a ceetm qdisc */
|
|
+static void ceetm_destroy(struct Qdisc *sch)
|
|
+{
|
|
+ unsigned int ntx, i;
|
|
+ struct hlist_node *next;
|
|
+ struct ceetm_class *cl;
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : destroy qdisc %X\n",
|
|
+ __func__, sch->handle);
|
|
+
|
|
+ /* All filters need to be removed before destroying the classes */
|
|
+ tcf_destroy_chain(&priv->filter_list);
|
|
+
|
|
+ for (i = 0; i < priv->clhash.hashsize; i++) {
|
|
+ hlist_for_each_entry(cl, &priv->clhash.hash[i], common.hnode)
|
|
+ tcf_destroy_chain(&cl->filter_list);
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < priv->clhash.hashsize; i++) {
|
|
+ hlist_for_each_entry_safe(cl, next, &priv->clhash.hash[i],
|
|
+ common.hnode)
|
|
+ ceetm_cls_destroy(sch, cl);
|
|
+ }
|
|
+
|
|
+ qdisc_class_hash_destroy(&priv->clhash);
|
|
+
|
|
+ switch (priv->type) {
|
|
+ case CEETM_ROOT:
|
|
+ dpa_disable_ceetm(dev);
|
|
+
|
|
+ if (priv->root.lni && qman_ceetm_lni_release(priv->root.lni))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the LNI %d\n",
|
|
+ __func__, priv->root.lni->idx);
|
|
+
|
|
+ if (priv->root.sp && qman_ceetm_sp_release(priv->root.sp))
|
|
+ pr_err(KBUILD_BASENAME
|
|
+ " : %s : error releasing the SP %d\n",
|
|
+ __func__, priv->root.sp->idx);
|
|
+
|
|
+ if (priv->root.qstats)
|
|
+ free_percpu(priv->root.qstats);
|
|
+
|
|
+ if (!priv->root.qdiscs)
|
|
+ break;
|
|
+
|
|
+ /* Remove the pfifo qdiscs */
|
|
+ for (ntx = 0; ntx < dev->num_tx_queues; ntx++)
|
|
+ if (priv->root.qdiscs[ntx])
|
|
+ qdisc_destroy(priv->root.qdiscs[ntx]);
|
|
+
|
|
+ kfree(priv->root.qdiscs);
|
|
+ break;
|
|
+
|
|
+ case CEETM_PRIO:
|
|
+ if (priv->prio.parent)
|
|
+ priv->prio.parent->root.child = NULL;
|
|
+ break;
|
|
+
|
|
+ case CEETM_WBFS:
|
|
+ if (priv->wbfs.parent)
|
|
+ priv->wbfs.parent->prio.child = NULL;
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int ceetm_dump(struct Qdisc *sch, struct sk_buff *skb)
|
|
+{
|
|
+ struct Qdisc *qdisc;
|
|
+ unsigned int ntx, i;
|
|
+ struct nlattr *nest;
|
|
+ struct tc_ceetm_qopt qopt;
|
|
+ struct ceetm_qdisc_stats *qstats;
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : qdisc %X\n", __func__, sch->handle);
|
|
+
|
|
+ sch_tree_lock(sch);
|
|
+ memset(&qopt, 0, sizeof(qopt));
|
|
+ qopt.type = priv->type;
|
|
+ qopt.shaped = priv->shaped;
|
|
+
|
|
+ switch (priv->type) {
|
|
+ case CEETM_ROOT:
|
|
+ /* Gather statistics from the underlying pfifo qdiscs */
|
|
+ sch->q.qlen = 0;
|
|
+ memset(&sch->bstats, 0, sizeof(sch->bstats));
|
|
+ memset(&sch->qstats, 0, sizeof(sch->qstats));
|
|
+
|
|
+ for (ntx = 0; ntx < dev->num_tx_queues; ntx++) {
|
|
+ qdisc = netdev_get_tx_queue(dev, ntx)->qdisc_sleeping;
|
|
+ sch->q.qlen += qdisc->q.qlen;
|
|
+ sch->bstats.bytes += qdisc->bstats.bytes;
|
|
+ sch->bstats.packets += qdisc->bstats.packets;
|
|
+ sch->qstats.qlen += qdisc->qstats.qlen;
|
|
+ sch->qstats.backlog += qdisc->qstats.backlog;
|
|
+ sch->qstats.drops += qdisc->qstats.drops;
|
|
+ sch->qstats.requeues += qdisc->qstats.requeues;
|
|
+ sch->qstats.overlimits += qdisc->qstats.overlimits;
|
|
+ }
|
|
+
|
|
+ for_each_online_cpu(i) {
|
|
+ qstats = per_cpu_ptr(priv->root.qstats, i);
|
|
+ sch->qstats.drops += qstats->drops;
|
|
+ }
|
|
+
|
|
+ qopt.rate = priv->root.rate;
|
|
+ qopt.ceil = priv->root.ceil;
|
|
+ qopt.overhead = priv->root.overhead;
|
|
+ break;
|
|
+
|
|
+ case CEETM_PRIO:
|
|
+ qopt.qcount = priv->prio.qcount;
|
|
+ break;
|
|
+
|
|
+ case CEETM_WBFS:
|
|
+ qopt.qcount = priv->wbfs.qcount;
|
|
+ qopt.cr = priv->wbfs.cr;
|
|
+ qopt.er = priv->wbfs.er;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ pr_err(KBUILD_BASENAME " : %s : invalid qdisc\n", __func__);
|
|
+ sch_tree_unlock(sch);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ nest = nla_nest_start(skb, TCA_OPTIONS);
|
|
+ if (!nest)
|
|
+ goto nla_put_failure;
|
|
+ if (nla_put(skb, TCA_CEETM_QOPS, sizeof(qopt), &qopt))
|
|
+ goto nla_put_failure;
|
|
+ nla_nest_end(skb, nest);
|
|
+
|
|
+ sch_tree_unlock(sch);
|
|
+ return skb->len;
|
|
+
|
|
+nla_put_failure:
|
|
+ sch_tree_unlock(sch);
|
|
+ nla_nest_cancel(skb, nest);
|
|
+ return -EMSGSIZE;
|
|
+}
|
|
+
|
|
+/* Configure a root ceetm qdisc */
|
|
+static int ceetm_init_root(struct Qdisc *sch, struct ceetm_qdisc *priv,
|
|
+ struct tc_ceetm_qopt *qopt)
|
|
+{
|
|
+ struct netdev_queue *dev_queue;
|
|
+ struct Qdisc *qdisc;
|
|
+ enum qm_dc_portal dcp_id;
|
|
+ unsigned int i, sp_id, parent_id;
|
|
+ int err;
|
|
+ u64 bps;
|
|
+ struct qm_ceetm_sp *sp;
|
|
+ struct qm_ceetm_lni *lni;
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+ struct dpa_priv_s *dpa_priv = netdev_priv(dev);
|
|
+ struct mac_device *mac_dev = dpa_priv->mac_dev;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : qdisc %X\n", __func__, sch->handle);
|
|
+
|
|
+ /* Validate inputs */
|
|
+ if (sch->parent != TC_H_ROOT) {
|
|
+ pr_err("CEETM: a root ceetm qdisc can not be attached to a class\n");
|
|
+ tcf_destroy_chain(&priv->filter_list);
|
|
+ qdisc_class_hash_destroy(&priv->clhash);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (!mac_dev) {
|
|
+ pr_err("CEETM: the interface is lacking a mac\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ /* pre-allocate underlying pfifo qdiscs */
|
|
+ priv->root.qdiscs = kcalloc(dev->num_tx_queues,
|
|
+ sizeof(priv->root.qdiscs[0]),
|
|
+ GFP_KERNEL);
|
|
+ if (!priv->root.qdiscs) {
|
|
+ err = -ENOMEM;
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < dev->num_tx_queues; i++) {
|
|
+ dev_queue = netdev_get_tx_queue(dev, i);
|
|
+ parent_id = TC_H_MAKE(TC_H_MAJ(sch->handle),
|
|
+ TC_H_MIN(i + PFIFO_MIN_OFFSET));
|
|
+
|
|
+ qdisc = qdisc_create_dflt(dev_queue, &pfifo_qdisc_ops,
|
|
+ parent_id);
|
|
+ if (!qdisc) {
|
|
+ err = -ENOMEM;
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ priv->root.qdiscs[i] = qdisc;
|
|
+ qdisc->flags |= TCQ_F_ONETXQUEUE;
|
|
+ }
|
|
+
|
|
+ sch->flags |= TCQ_F_MQROOT;
|
|
+
|
|
+ priv->root.qstats = alloc_percpu(struct ceetm_qdisc_stats);
|
|
+ if (!priv->root.qstats) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : alloc_percpu() failed\n",
|
|
+ __func__);
|
|
+ err = -ENOMEM;
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ priv->shaped = qopt->shaped;
|
|
+ priv->root.rate = qopt->rate;
|
|
+ priv->root.ceil = qopt->ceil;
|
|
+ priv->root.overhead = qopt->overhead;
|
|
+
|
|
+ /* Claim the SP */
|
|
+ get_dcp_and_sp(dev, &dcp_id, &sp_id);
|
|
+ err = qman_ceetm_sp_claim(&sp, dcp_id, sp_id);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to claim the SP\n",
|
|
+ __func__);
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ priv->root.sp = sp;
|
|
+
|
|
+ /* Claim the LNI - will use the same id as the SP id since SPs 0-7
|
|
+ * are connected to the TX FMan ports
|
|
+ */
|
|
+ err = qman_ceetm_lni_claim(&lni, dcp_id, sp_id);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to claim the LNI\n",
|
|
+ __func__);
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ priv->root.lni = lni;
|
|
+
|
|
+ err = qman_ceetm_sp_set_lni(sp, lni);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to link the SP and LNI\n",
|
|
+ __func__);
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ lni->sp = sp;
|
|
+
|
|
+ /* Configure the LNI shaper */
|
|
+ if (priv->shaped) {
|
|
+ err = qman_ceetm_lni_enable_shaper(lni, 1, priv->root.overhead);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the LNI shaper\n",
|
|
+ __func__);
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ bps = priv->root.rate << 3; /* Bps -> bps */
|
|
+ err = qman_ceetm_lni_set_commit_rate_bps(lni, bps, dev->mtu);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the LNI shaper\n",
|
|
+ __func__);
|
|
+ goto err_init_root;
|
|
+ }
|
|
+
|
|
+ bps = priv->root.ceil << 3; /* Bps -> bps */
|
|
+ err = qman_ceetm_lni_set_excess_rate_bps(lni, bps, dev->mtu);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the LNI shaper\n",
|
|
+ __func__);
|
|
+ goto err_init_root;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* TODO default configuration */
|
|
+
|
|
+ dpa_enable_ceetm(dev);
|
|
+ return 0;
|
|
+
|
|
+err_init_root:
|
|
+ ceetm_destroy(sch);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* Configure a prio ceetm qdisc */
|
|
+static int ceetm_init_prio(struct Qdisc *sch, struct ceetm_qdisc *priv,
|
|
+ struct tc_ceetm_qopt *qopt)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int i;
|
|
+ struct ceetm_class *parent_cl, *child_cl;
|
|
+ struct Qdisc *parent_qdisc;
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : qdisc %X\n", __func__, sch->handle);
|
|
+
|
|
+ if (sch->parent == TC_H_ROOT) {
|
|
+ pr_err("CEETM: a prio ceetm qdisc can not be root\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_prio;
|
|
+ }
|
|
+
|
|
+ parent_qdisc = qdisc_lookup(dev, TC_H_MAJ(sch->parent));
|
|
+ if (strcmp(parent_qdisc->ops->id, ceetm_qdisc_ops.id)) {
|
|
+ pr_err("CEETM: a ceetm qdisc can not be attached to other qdisc/class types\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_prio;
|
|
+ }
|
|
+
|
|
+ /* Obtain the parent root ceetm_class */
|
|
+ parent_cl = ceetm_find(sch->parent, parent_qdisc);
|
|
+
|
|
+ if (!parent_cl || parent_cl->type != CEETM_ROOT) {
|
|
+ pr_err("CEETM: a prio ceetm qdiscs can be added only under a root ceetm class\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_prio;
|
|
+ }
|
|
+
|
|
+ priv->prio.parent = parent_cl;
|
|
+ parent_cl->root.child = sch;
|
|
+
|
|
+ priv->shaped = parent_cl->shaped;
|
|
+ priv->prio.qcount = qopt->qcount;
|
|
+
|
|
+ /* Create and configure qcount child classes */
|
|
+ for (i = 0; i < priv->prio.qcount; i++) {
|
|
+ child_cl = kzalloc(sizeof(*child_cl), GFP_KERNEL);
|
|
+ if (!child_cl) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : kzalloc() failed\n",
|
|
+ __func__);
|
|
+ err = -ENOMEM;
|
|
+ goto err_init_prio;
|
|
+ }
|
|
+
|
|
+ child_cl->prio.cstats = alloc_percpu(struct ceetm_class_stats);
|
|
+ if (!child_cl->prio.cstats) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : alloc_percpu() failed\n",
|
|
+ __func__);
|
|
+ err = -ENOMEM;
|
|
+ goto err_init_prio_cls;
|
|
+ }
|
|
+
|
|
+ child_cl->common.classid = TC_H_MAKE(sch->handle, (i + 1));
|
|
+ child_cl->refcnt = 1;
|
|
+ child_cl->parent = sch;
|
|
+ child_cl->type = CEETM_PRIO;
|
|
+ child_cl->shaped = priv->shaped;
|
|
+ child_cl->prio.child = NULL;
|
|
+
|
|
+ /* All shaped CQs have CR and ER enabled by default */
|
|
+ child_cl->prio.cr = child_cl->shaped;
|
|
+ child_cl->prio.er = child_cl->shaped;
|
|
+ child_cl->prio.fq = NULL;
|
|
+ child_cl->prio.cq = NULL;
|
|
+
|
|
+ /* Configure the corresponding hardware CQ */
|
|
+ err = ceetm_config_prio_cls(child_cl, dev,
|
|
+ parent_cl->root.ch, i);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the ceetm prio class %X\n",
|
|
+ __func__, child_cl->common.classid);
|
|
+ goto err_init_prio_cls;
|
|
+ }
|
|
+
|
|
+ /* Add class handle in Qdisc */
|
|
+ ceetm_link_class(sch, &priv->clhash, &child_cl->common);
|
|
+ pr_debug(KBUILD_BASENAME " : %s : added ceetm prio class %X associated with CQ %d and CCG %d\n",
|
|
+ __func__, child_cl->common.classid,
|
|
+ child_cl->prio.cq->idx, child_cl->prio.ccg->idx);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_init_prio_cls:
|
|
+ ceetm_cls_destroy(sch, child_cl);
|
|
+err_init_prio:
|
|
+ ceetm_destroy(sch);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* Configure a wbfs ceetm qdisc */
|
|
+static int ceetm_init_wbfs(struct Qdisc *sch, struct ceetm_qdisc *priv,
|
|
+ struct tc_ceetm_qopt *qopt)
|
|
+{
|
|
+ int err, group_b, small_group;
|
|
+ unsigned int i, id, prio_a, prio_b;
|
|
+ struct ceetm_class *parent_cl, *child_cl, *root_cl;
|
|
+ struct Qdisc *parent_qdisc;
|
|
+ struct ceetm_qdisc *parent_priv;
|
|
+ struct qm_ceetm_channel *channel;
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : qdisc %X\n", __func__, sch->handle);
|
|
+
|
|
+ /* Validate inputs */
|
|
+ if (sch->parent == TC_H_ROOT) {
|
|
+ pr_err("CEETM: a wbfs ceetm qdiscs can not be root\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ /* Obtain the parent prio ceetm qdisc */
|
|
+ parent_qdisc = qdisc_lookup(dev, TC_H_MAJ(sch->parent));
|
|
+ if (strcmp(parent_qdisc->ops->id, ceetm_qdisc_ops.id)) {
|
|
+ pr_err("CEETM: a ceetm qdisc can not be attached to other qdisc/class types\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ /* Obtain the parent prio ceetm class */
|
|
+ parent_cl = ceetm_find(sch->parent, parent_qdisc);
|
|
+ parent_priv = qdisc_priv(parent_qdisc);
|
|
+
|
|
+ if (!parent_cl || parent_cl->type != CEETM_PRIO) {
|
|
+ pr_err("CEETM: a wbfs ceetm qdiscs can be added only under a prio ceetm class\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ if (!qopt->qcount || !qopt->qweight[0]) {
|
|
+ pr_err("CEETM: qcount and qweight are mandatory for a wbfs ceetm qdisc\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ priv->shaped = parent_cl->shaped;
|
|
+
|
|
+ if (!priv->shaped && (qopt->cr || qopt->er)) {
|
|
+ pr_err("CEETM: CR/ER can be enabled only for shaped wbfs ceetm qdiscs\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ if (priv->shaped && !(qopt->cr || qopt->er)) {
|
|
+ pr_err("CEETM: either CR or ER must be enabled for shaped wbfs ceetm qdiscs\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ /* Obtain the parent root ceetm class */
|
|
+ root_cl = parent_priv->prio.parent;
|
|
+ if ((root_cl->root.wbfs_grp_a && root_cl->root.wbfs_grp_b) ||
|
|
+ root_cl->root.wbfs_grp_large) {
|
|
+ pr_err("CEETM: no more wbfs classes are available\n");
|
|
+ err = -EINVAL;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ if ((root_cl->root.wbfs_grp_a || root_cl->root.wbfs_grp_b) &&
|
|
+ qopt->qcount == CEETM_MAX_WBFS_QCOUNT) {
|
|
+ pr_err("CEETM: only %d wbfs classes are available\n",
|
|
+ CEETM_MIN_WBFS_QCOUNT);
|
|
+ err = -EINVAL;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ priv->wbfs.parent = parent_cl;
|
|
+ parent_cl->prio.child = sch;
|
|
+
|
|
+ priv->wbfs.qcount = qopt->qcount;
|
|
+ priv->wbfs.cr = qopt->cr;
|
|
+ priv->wbfs.er = qopt->er;
|
|
+
|
|
+ channel = root_cl->root.ch;
|
|
+
|
|
+ /* Configure the hardware wbfs channel groups */
|
|
+ if (priv->wbfs.qcount == CEETM_MAX_WBFS_QCOUNT) {
|
|
+ /* Configure the large group A */
|
|
+ priv->wbfs.group_type = WBFS_GRP_LARGE;
|
|
+ small_group = false;
|
|
+ group_b = false;
|
|
+ prio_a = TC_H_MIN(parent_cl->common.classid) - 1;
|
|
+ prio_b = prio_a;
|
|
+
|
|
+ } else if (root_cl->root.wbfs_grp_a) {
|
|
+ /* Configure the group B */
|
|
+ priv->wbfs.group_type = WBFS_GRP_B;
|
|
+
|
|
+ err = qman_ceetm_channel_get_group(channel, &small_group,
|
|
+ &prio_a, &prio_b);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to get group details\n",
|
|
+ __func__);
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ small_group = true;
|
|
+ group_b = true;
|
|
+ prio_b = TC_H_MIN(parent_cl->common.classid) - 1;
|
|
+ /* If group A isn't configured, configure it as group B */
|
|
+ prio_a = prio_a ? : prio_b;
|
|
+
|
|
+ } else {
|
|
+ /* Configure the small group A */
|
|
+ priv->wbfs.group_type = WBFS_GRP_A;
|
|
+
|
|
+ err = qman_ceetm_channel_get_group(channel, &small_group,
|
|
+ &prio_a, &prio_b);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to get group details\n",
|
|
+ __func__);
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ small_group = true;
|
|
+ group_b = false;
|
|
+ prio_a = TC_H_MIN(parent_cl->common.classid) - 1;
|
|
+ /* If group B isn't configured, configure it as group A */
|
|
+ prio_b = prio_b ? : prio_a;
|
|
+ }
|
|
+
|
|
+ err = qman_ceetm_channel_set_group(channel, small_group, prio_a,
|
|
+ prio_b);
|
|
+ if (err)
|
|
+ goto err_init_wbfs;
|
|
+
|
|
+ if (priv->shaped) {
|
|
+ err = qman_ceetm_channel_set_group_cr_eligibility(channel,
|
|
+ group_b,
|
|
+ priv->wbfs.cr);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to set group CR eligibility\n",
|
|
+ __func__);
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ err = qman_ceetm_channel_set_group_er_eligibility(channel,
|
|
+ group_b,
|
|
+ priv->wbfs.er);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to set group ER eligibility\n",
|
|
+ __func__);
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Create qcount child classes */
|
|
+ for (i = 0; i < priv->wbfs.qcount; i++) {
|
|
+ child_cl = kzalloc(sizeof(*child_cl), GFP_KERNEL);
|
|
+ if (!child_cl) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : kzalloc() failed\n",
|
|
+ __func__);
|
|
+ err = -ENOMEM;
|
|
+ goto err_init_wbfs;
|
|
+ }
|
|
+
|
|
+ child_cl->wbfs.cstats = alloc_percpu(struct ceetm_class_stats);
|
|
+ if (!child_cl->wbfs.cstats) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : alloc_percpu() failed\n",
|
|
+ __func__);
|
|
+ err = -ENOMEM;
|
|
+ goto err_init_wbfs_cls;
|
|
+ }
|
|
+
|
|
+ child_cl->common.classid = TC_H_MAKE(sch->handle, (i + 1));
|
|
+ child_cl->refcnt = 1;
|
|
+ child_cl->parent = sch;
|
|
+ child_cl->type = CEETM_WBFS;
|
|
+ child_cl->shaped = priv->shaped;
|
|
+ child_cl->wbfs.fq = NULL;
|
|
+ child_cl->wbfs.cq = NULL;
|
|
+ child_cl->wbfs.weight = qopt->qweight[i];
|
|
+
|
|
+ if (priv->wbfs.group_type == WBFS_GRP_B)
|
|
+ id = WBFS_GRP_B_OFFSET + i;
|
|
+ else
|
|
+ id = WBFS_GRP_A_OFFSET + i;
|
|
+
|
|
+ err = ceetm_config_wbfs_cls(child_cl, dev, channel, id,
|
|
+ priv->wbfs.group_type);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the ceetm wbfs class %X\n",
|
|
+ __func__, child_cl->common.classid);
|
|
+ goto err_init_wbfs_cls;
|
|
+ }
|
|
+
|
|
+ /* Add class handle in Qdisc */
|
|
+ ceetm_link_class(sch, &priv->clhash, &child_cl->common);
|
|
+ pr_debug(KBUILD_BASENAME " : %s : added ceetm wbfs class %X associated with CQ %d and CCG %d\n",
|
|
+ __func__, child_cl->common.classid,
|
|
+ child_cl->wbfs.cq->idx, child_cl->wbfs.ccg->idx);
|
|
+ }
|
|
+
|
|
+ /* Signal the root class that a group has been configured */
|
|
+ switch (priv->wbfs.group_type) {
|
|
+ case WBFS_GRP_LARGE:
|
|
+ root_cl->root.wbfs_grp_large = true;
|
|
+ break;
|
|
+ case WBFS_GRP_A:
|
|
+ root_cl->root.wbfs_grp_a = true;
|
|
+ break;
|
|
+ case WBFS_GRP_B:
|
|
+ root_cl->root.wbfs_grp_b = true;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_init_wbfs_cls:
|
|
+ ceetm_cls_destroy(sch, child_cl);
|
|
+err_init_wbfs:
|
|
+ ceetm_destroy(sch);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* Configure a generic ceetm qdisc */
|
|
+static int ceetm_init(struct Qdisc *sch, struct nlattr *opt)
|
|
+{
|
|
+ struct tc_ceetm_qopt *qopt;
|
|
+ struct nlattr *tb[TCA_CEETM_QOPS + 1];
|
|
+ int ret;
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : qdisc %X\n", __func__, sch->handle);
|
|
+
|
|
+ if (!netif_is_multiqueue(dev))
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ if (!opt) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : tc error\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = nla_parse_nested(tb, TCA_CEETM_QOPS, opt, ceetm_policy);
|
|
+ if (ret < 0) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : tc error\n", __func__);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (!tb[TCA_CEETM_QOPS]) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : tc error\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (TC_H_MIN(sch->handle)) {
|
|
+ pr_err("CEETM: a qdisc should not have a minor\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ qopt = nla_data(tb[TCA_CEETM_QOPS]);
|
|
+
|
|
+ /* Initialize the class hash list. Each qdisc has its own class hash */
|
|
+ ret = qdisc_class_hash_init(&priv->clhash);
|
|
+ if (ret < 0) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : qdisc_class_hash_init failed\n",
|
|
+ __func__);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ priv->type = qopt->type;
|
|
+
|
|
+ switch (priv->type) {
|
|
+ case CEETM_ROOT:
|
|
+ ret = ceetm_init_root(sch, priv, qopt);
|
|
+ break;
|
|
+ case CEETM_PRIO:
|
|
+ ret = ceetm_init_prio(sch, priv, qopt);
|
|
+ break;
|
|
+ case CEETM_WBFS:
|
|
+ ret = ceetm_init_wbfs(sch, priv, qopt);
|
|
+ break;
|
|
+ default:
|
|
+ pr_err(KBUILD_BASENAME " : %s : invalid qdisc\n", __func__);
|
|
+ ceetm_destroy(sch);
|
|
+ ret = -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* Edit a root ceetm qdisc */
|
|
+static int ceetm_change_root(struct Qdisc *sch, struct ceetm_qdisc *priv,
|
|
+ struct net_device *dev,
|
|
+ struct tc_ceetm_qopt *qopt)
|
|
+{
|
|
+ int err = 0;
|
|
+ u64 bps;
|
|
+
|
|
+ if (priv->shaped != (bool)qopt->shaped) {
|
|
+ pr_err("CEETM: qdisc %X is %s\n", sch->handle,
|
|
+ priv->shaped ? "shaped" : "unshaped");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Nothing to modify for unshaped qdiscs */
|
|
+ if (!priv->shaped)
|
|
+ return 0;
|
|
+
|
|
+ /* Configure the LNI shaper */
|
|
+ if (priv->root.overhead != qopt->overhead) {
|
|
+ err = qman_ceetm_lni_enable_shaper(priv->root.lni, 1,
|
|
+ qopt->overhead);
|
|
+ if (err)
|
|
+ goto change_err;
|
|
+ priv->root.overhead = qopt->overhead;
|
|
+ }
|
|
+
|
|
+ if (priv->root.rate != qopt->rate) {
|
|
+ bps = qopt->rate << 3; /* Bps -> bps */
|
|
+ err = qman_ceetm_lni_set_commit_rate_bps(priv->root.lni, bps,
|
|
+ dev->mtu);
|
|
+ if (err)
|
|
+ goto change_err;
|
|
+ priv->root.rate = qopt->rate;
|
|
+ }
|
|
+
|
|
+ if (priv->root.ceil != qopt->ceil) {
|
|
+ bps = qopt->ceil << 3; /* Bps -> bps */
|
|
+ err = qman_ceetm_lni_set_excess_rate_bps(priv->root.lni, bps,
|
|
+ dev->mtu);
|
|
+ if (err)
|
|
+ goto change_err;
|
|
+ priv->root.ceil = qopt->ceil;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+change_err:
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the root ceetm qdisc %X\n",
|
|
+ __func__, sch->handle);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* Edit a wbfs ceetm qdisc */
|
|
+static int ceetm_change_wbfs(struct Qdisc *sch, struct ceetm_qdisc *priv,
|
|
+ struct tc_ceetm_qopt *qopt)
|
|
+{
|
|
+ int err;
|
|
+ bool group_b;
|
|
+ struct qm_ceetm_channel *channel;
|
|
+ struct ceetm_class *prio_class, *root_class;
|
|
+ struct ceetm_qdisc *prio_qdisc;
|
|
+
|
|
+ if (qopt->qcount) {
|
|
+ pr_err("CEETM: the qcount can not be modified\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (qopt->qweight[0]) {
|
|
+ pr_err("CEETM: the qweight can be modified through the wbfs classes\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (!priv->shaped && (qopt->cr || qopt->er)) {
|
|
+ pr_err("CEETM: CR/ER can be enabled only for shaped wbfs ceetm qdiscs\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (priv->shaped && !(qopt->cr || qopt->er)) {
|
|
+ pr_err("CEETM: either CR or ER must be enabled for shaped wbfs ceetm qdiscs\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Nothing to modify for unshaped qdiscs */
|
|
+ if (!priv->shaped)
|
|
+ return 0;
|
|
+
|
|
+ prio_class = priv->wbfs.parent;
|
|
+ prio_qdisc = qdisc_priv(prio_class->parent);
|
|
+ root_class = prio_qdisc->prio.parent;
|
|
+ channel = root_class->root.ch;
|
|
+ group_b = priv->wbfs.group_type == WBFS_GRP_B;
|
|
+
|
|
+ if (qopt->cr != priv->wbfs.cr) {
|
|
+ err = qman_ceetm_channel_set_group_cr_eligibility(channel,
|
|
+ group_b,
|
|
+ qopt->cr);
|
|
+ if (err)
|
|
+ goto change_err;
|
|
+ priv->wbfs.cr = qopt->cr;
|
|
+ }
|
|
+
|
|
+ if (qopt->er != priv->wbfs.er) {
|
|
+ err = qman_ceetm_channel_set_group_er_eligibility(channel,
|
|
+ group_b,
|
|
+ qopt->er);
|
|
+ if (err)
|
|
+ goto change_err;
|
|
+ priv->wbfs.er = qopt->er;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+change_err:
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the wbfs ceetm qdisc %X\n",
|
|
+ __func__, sch->handle);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* Edit a ceetm qdisc */
|
|
+static int ceetm_change(struct Qdisc *sch, struct nlattr *opt)
|
|
+{
|
|
+ struct tc_ceetm_qopt *qopt;
|
|
+ struct nlattr *tb[TCA_CEETM_QOPS + 1];
|
|
+ int ret;
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : qdisc %X\n", __func__, sch->handle);
|
|
+
|
|
+ ret = nla_parse_nested(tb, TCA_CEETM_QOPS, opt, ceetm_policy);
|
|
+ if (ret < 0) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : tc error\n", __func__);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (!tb[TCA_CEETM_QOPS]) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : tc error\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (TC_H_MIN(sch->handle)) {
|
|
+ pr_err("CEETM: a qdisc should not have a minor\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ qopt = nla_data(tb[TCA_CEETM_QOPS]);
|
|
+
|
|
+ if (priv->type != qopt->type) {
|
|
+ pr_err("CEETM: qdisc %X is not of the provided type\n",
|
|
+ sch->handle);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (priv->type) {
|
|
+ case CEETM_ROOT:
|
|
+ ret = ceetm_change_root(sch, priv, dev, qopt);
|
|
+ break;
|
|
+ case CEETM_PRIO:
|
|
+ pr_err("CEETM: prio qdiscs can not be modified\n");
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ case CEETM_WBFS:
|
|
+ ret = ceetm_change_wbfs(sch, priv, qopt);
|
|
+ break;
|
|
+ default:
|
|
+ pr_err(KBUILD_BASENAME " : %s : invalid qdisc\n", __func__);
|
|
+ ret = -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* Attach the underlying pfifo qdiscs */
|
|
+static void ceetm_attach(struct Qdisc *sch)
|
|
+{
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct Qdisc *qdisc, *old_qdisc;
|
|
+ unsigned int i;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : qdisc %X\n", __func__, sch->handle);
|
|
+
|
|
+ for (i = 0; i < dev->num_tx_queues; i++) {
|
|
+ qdisc = priv->root.qdiscs[i];
|
|
+ old_qdisc = dev_graft_qdisc(qdisc->dev_queue, qdisc);
|
|
+ if (old_qdisc)
|
|
+ qdisc_destroy(old_qdisc);
|
|
+ }
|
|
+}
|
|
+
|
|
+static unsigned long ceetm_cls_get(struct Qdisc *sch, u32 classid)
|
|
+{
|
|
+ struct ceetm_class *cl;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : classid %X from qdisc %X\n",
|
|
+ __func__, classid, sch->handle);
|
|
+ cl = ceetm_find(classid, sch);
|
|
+
|
|
+ if (cl)
|
|
+ cl->refcnt++; /* Will decrement in put() */
|
|
+ return (unsigned long)cl;
|
|
+}
|
|
+
|
|
+static void ceetm_cls_put(struct Qdisc *sch, unsigned long arg)
|
|
+{
|
|
+ struct ceetm_class *cl = (struct ceetm_class *)arg;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : classid %X from qdisc %X\n",
|
|
+ __func__, cl->common.classid, sch->handle);
|
|
+ cl->refcnt--;
|
|
+
|
|
+ if (cl->refcnt == 0)
|
|
+ ceetm_cls_destroy(sch, cl);
|
|
+}
|
|
+
|
|
+static int ceetm_cls_change_root(struct ceetm_class *cl,
|
|
+ struct tc_ceetm_copt *copt,
|
|
+ struct net_device *dev)
|
|
+{
|
|
+ int err;
|
|
+ u64 bps;
|
|
+
|
|
+ if ((bool)copt->shaped != cl->shaped) {
|
|
+ pr_err("CEETM: class %X is %s\n", cl->common.classid,
|
|
+ cl->shaped ? "shaped" : "unshaped");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (cl->shaped && cl->root.rate != copt->rate) {
|
|
+ bps = copt->rate << 3; /* Bps -> bps */
|
|
+ err = qman_ceetm_channel_set_commit_rate_bps(cl->root.ch, bps,
|
|
+ dev->mtu);
|
|
+ if (err)
|
|
+ goto change_cls_err;
|
|
+ cl->root.rate = copt->rate;
|
|
+ }
|
|
+
|
|
+ if (cl->shaped && cl->root.ceil != copt->ceil) {
|
|
+ bps = copt->ceil << 3; /* Bps -> bps */
|
|
+ err = qman_ceetm_channel_set_excess_rate_bps(cl->root.ch, bps,
|
|
+ dev->mtu);
|
|
+ if (err)
|
|
+ goto change_cls_err;
|
|
+ cl->root.ceil = copt->ceil;
|
|
+ }
|
|
+
|
|
+ if (!cl->shaped && cl->root.tbl != copt->tbl) {
|
|
+ err = qman_ceetm_channel_set_weight(cl->root.ch, copt->tbl);
|
|
+ if (err)
|
|
+ goto change_cls_err;
|
|
+ cl->root.tbl = copt->tbl;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+change_cls_err:
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the ceetm root class %X\n",
|
|
+ __func__, cl->common.classid);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int ceetm_cls_change_prio(struct ceetm_class *cl,
|
|
+ struct tc_ceetm_copt *copt)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ if (!cl->shaped && (copt->cr || copt->er)) {
|
|
+ pr_err("CEETM: only shaped classes can have CR and ER enabled\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (cl->prio.cr != (bool)copt->cr) {
|
|
+ err = qman_ceetm_channel_set_cq_cr_eligibility(
|
|
+ cl->prio.cq->parent,
|
|
+ cl->prio.cq->idx,
|
|
+ copt->cr);
|
|
+ if (err)
|
|
+ goto change_cls_err;
|
|
+ cl->prio.cr = copt->cr;
|
|
+ }
|
|
+
|
|
+ if (cl->prio.er != (bool)copt->er) {
|
|
+ err = qman_ceetm_channel_set_cq_er_eligibility(
|
|
+ cl->prio.cq->parent,
|
|
+ cl->prio.cq->idx,
|
|
+ copt->er);
|
|
+ if (err)
|
|
+ goto change_cls_err;
|
|
+ cl->prio.er = copt->er;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+change_cls_err:
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the ceetm prio class %X\n",
|
|
+ __func__, cl->common.classid);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int ceetm_cls_change_wbfs(struct ceetm_class *cl,
|
|
+ struct tc_ceetm_copt *copt)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ if (copt->weight != cl->wbfs.weight) {
|
|
+ /* Configure the CQ weight: real number multiplied by 100 to
|
|
+ * get rid of the fraction
|
|
+ */
|
|
+ err = qman_ceetm_set_queue_weight_in_ratio(cl->wbfs.cq,
|
|
+ copt->weight * 100);
|
|
+
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the ceetm wbfs class %X\n",
|
|
+ __func__, cl->common.classid);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ cl->wbfs.weight = copt->weight;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Add a ceetm root class or configure a ceetm root/prio/wbfs class */
|
|
+static int ceetm_cls_change(struct Qdisc *sch, u32 classid, u32 parentid,
|
|
+ struct nlattr **tca, unsigned long *arg)
|
|
+{
|
|
+ int err;
|
|
+ u64 bps;
|
|
+ struct ceetm_qdisc *priv;
|
|
+ struct ceetm_class *cl = (struct ceetm_class *)*arg;
|
|
+ struct nlattr *opt = tca[TCA_OPTIONS];
|
|
+ struct nlattr *tb[__TCA_CEETM_MAX];
|
|
+ struct tc_ceetm_copt *copt;
|
|
+ struct qm_ceetm_channel *channel;
|
|
+ struct net_device *dev = qdisc_dev(sch);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : classid %X under qdisc %X\n",
|
|
+ __func__, classid, sch->handle);
|
|
+
|
|
+ if (strcmp(sch->ops->id, ceetm_qdisc_ops.id)) {
|
|
+ pr_err("CEETM: a ceetm class can not be attached to other qdisc/class types\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ priv = qdisc_priv(sch);
|
|
+
|
|
+ if (!opt) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : tc error\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (!cl && sch->handle != parentid) {
|
|
+ pr_err("CEETM: classes can be attached to the root ceetm qdisc only\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (!cl && priv->type != CEETM_ROOT) {
|
|
+ pr_err("CEETM: only root ceetm classes can be attached to the root ceetm qdisc\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ err = nla_parse_nested(tb, TCA_CEETM_COPT, opt, ceetm_policy);
|
|
+ if (err < 0) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : tc error\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (!tb[TCA_CEETM_COPT]) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : tc error\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (TC_H_MIN(classid) >= PFIFO_MIN_OFFSET) {
|
|
+ pr_err("CEETM: only minors 0x01 to 0x20 can be used for ceetm root classes\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ copt = nla_data(tb[TCA_CEETM_COPT]);
|
|
+
|
|
+ /* Configure an existing ceetm class */
|
|
+ if (cl) {
|
|
+ if (copt->type != cl->type) {
|
|
+ pr_err("CEETM: class %X is not of the provided type\n",
|
|
+ cl->common.classid);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (copt->type) {
|
|
+ case CEETM_ROOT:
|
|
+ return ceetm_cls_change_root(cl, copt, dev);
|
|
+
|
|
+ case CEETM_PRIO:
|
|
+ return ceetm_cls_change_prio(cl, copt);
|
|
+
|
|
+ case CEETM_WBFS:
|
|
+ return ceetm_cls_change_wbfs(cl, copt);
|
|
+
|
|
+ default:
|
|
+ pr_err(KBUILD_BASENAME " : %s : invalid class\n",
|
|
+ __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Add a new root ceetm class */
|
|
+ if (copt->type != CEETM_ROOT) {
|
|
+ pr_err("CEETM: only root ceetm classes can be attached to the root ceetm qdisc\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (copt->shaped && !priv->shaped) {
|
|
+ pr_err("CEETM: can not add a shaped ceetm root class under an unshaped ceetm root qdisc\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ cl = kzalloc(sizeof(*cl), GFP_KERNEL);
|
|
+ if (!cl)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ cl->type = copt->type;
|
|
+ cl->shaped = copt->shaped;
|
|
+ cl->root.rate = copt->rate;
|
|
+ cl->root.ceil = copt->ceil;
|
|
+ cl->root.tbl = copt->tbl;
|
|
+
|
|
+ cl->common.classid = classid;
|
|
+ cl->refcnt = 1;
|
|
+ cl->parent = sch;
|
|
+ cl->root.child = NULL;
|
|
+ cl->root.wbfs_grp_a = false;
|
|
+ cl->root.wbfs_grp_b = false;
|
|
+ cl->root.wbfs_grp_large = false;
|
|
+
|
|
+ /* Claim a CEETM channel */
|
|
+ err = qman_ceetm_channel_claim(&channel, priv->root.lni);
|
|
+ if (err) {
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to claim a channel\n",
|
|
+ __func__);
|
|
+ goto claim_err;
|
|
+ }
|
|
+
|
|
+ cl->root.ch = channel;
|
|
+
|
|
+ if (cl->shaped) {
|
|
+ /* Configure the channel shaper */
|
|
+ err = qman_ceetm_channel_enable_shaper(channel, 1);
|
|
+ if (err)
|
|
+ goto channel_err;
|
|
+
|
|
+ bps = cl->root.rate << 3; /* Bps -> bps */
|
|
+ err = qman_ceetm_channel_set_commit_rate_bps(channel, bps,
|
|
+ dev->mtu);
|
|
+ if (err)
|
|
+ goto channel_err;
|
|
+
|
|
+ bps = cl->root.ceil << 3; /* Bps -> bps */
|
|
+ err = qman_ceetm_channel_set_excess_rate_bps(channel, bps,
|
|
+ dev->mtu);
|
|
+ if (err)
|
|
+ goto channel_err;
|
|
+
|
|
+ } else {
|
|
+ /* Configure the uFQ algorithm */
|
|
+ err = qman_ceetm_channel_set_weight(channel, cl->root.tbl);
|
|
+ if (err)
|
|
+ goto channel_err;
|
|
+ }
|
|
+
|
|
+ /* Add class handle in Qdisc */
|
|
+ ceetm_link_class(sch, &priv->clhash, &cl->common);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : configured class %X associated with channel %d\n",
|
|
+ __func__, classid, channel->idx);
|
|
+ *arg = (unsigned long)cl;
|
|
+ return 0;
|
|
+
|
|
+channel_err:
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to configure the channel %d\n",
|
|
+ __func__, channel->idx);
|
|
+ if (qman_ceetm_channel_release(channel))
|
|
+ pr_err(KBUILD_BASENAME " : %s : failed to release the channel %d\n",
|
|
+ __func__, channel->idx);
|
|
+claim_err:
|
|
+ kfree(cl);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void ceetm_cls_walk(struct Qdisc *sch, struct qdisc_walker *arg)
|
|
+{
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct ceetm_class *cl;
|
|
+ unsigned int i;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : qdisc %X\n", __func__, sch->handle);
|
|
+
|
|
+ if (arg->stop)
|
|
+ return;
|
|
+
|
|
+ for (i = 0; i < priv->clhash.hashsize; i++) {
|
|
+ hlist_for_each_entry(cl, &priv->clhash.hash[i], common.hnode) {
|
|
+ if (arg->count < arg->skip) {
|
|
+ arg->count++;
|
|
+ continue;
|
|
+ }
|
|
+ if (arg->fn(sch, (unsigned long)cl, arg) < 0) {
|
|
+ arg->stop = 1;
|
|
+ return;
|
|
+ }
|
|
+ arg->count++;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static int ceetm_cls_dump(struct Qdisc *sch, unsigned long arg,
|
|
+ struct sk_buff *skb, struct tcmsg *tcm)
|
|
+{
|
|
+ struct ceetm_class *cl = (struct ceetm_class *)arg;
|
|
+ struct nlattr *nest;
|
|
+ struct tc_ceetm_copt copt;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : class %X under qdisc %X\n",
|
|
+ __func__, cl->common.classid, sch->handle);
|
|
+
|
|
+ sch_tree_lock(sch);
|
|
+
|
|
+ tcm->tcm_parent = ((struct Qdisc *)cl->parent)->handle;
|
|
+ tcm->tcm_handle = cl->common.classid;
|
|
+
|
|
+ memset(&copt, 0, sizeof(copt));
|
|
+
|
|
+ copt.shaped = cl->shaped;
|
|
+ copt.type = cl->type;
|
|
+
|
|
+ switch (cl->type) {
|
|
+ case CEETM_ROOT:
|
|
+ if (cl->root.child)
|
|
+ tcm->tcm_info = cl->root.child->handle;
|
|
+
|
|
+ copt.rate = cl->root.rate;
|
|
+ copt.ceil = cl->root.ceil;
|
|
+ copt.tbl = cl->root.tbl;
|
|
+ break;
|
|
+
|
|
+ case CEETM_PRIO:
|
|
+ if (cl->prio.child)
|
|
+ tcm->tcm_info = cl->prio.child->handle;
|
|
+
|
|
+ copt.cr = cl->prio.cr;
|
|
+ copt.er = cl->prio.er;
|
|
+ break;
|
|
+
|
|
+ case CEETM_WBFS:
|
|
+ copt.weight = cl->wbfs.weight;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ nest = nla_nest_start(skb, TCA_OPTIONS);
|
|
+ if (!nest)
|
|
+ goto nla_put_failure;
|
|
+ if (nla_put(skb, TCA_CEETM_COPT, sizeof(copt), &copt))
|
|
+ goto nla_put_failure;
|
|
+ nla_nest_end(skb, nest);
|
|
+ sch_tree_unlock(sch);
|
|
+ return skb->len;
|
|
+
|
|
+nla_put_failure:
|
|
+ sch_tree_unlock(sch);
|
|
+ nla_nest_cancel(skb, nest);
|
|
+ return -EMSGSIZE;
|
|
+}
|
|
+
|
|
+static int ceetm_cls_delete(struct Qdisc *sch, unsigned long arg)
|
|
+{
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct ceetm_class *cl = (struct ceetm_class *)arg;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : class %X under qdisc %X\n",
|
|
+ __func__, cl->common.classid, sch->handle);
|
|
+
|
|
+ sch_tree_lock(sch);
|
|
+ qdisc_class_hash_remove(&priv->clhash, &cl->common);
|
|
+ cl->refcnt--;
|
|
+
|
|
+ /* The refcnt should be at least 1 since we have incremented it in
|
|
+ * get(). Will decrement again in put() where we will call destroy()
|
|
+ * to actually free the memory if it reaches 0.
|
|
+ */
|
|
+ WARN_ON(cl->refcnt == 0);
|
|
+
|
|
+ sch_tree_unlock(sch);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Get the class' child qdisc, if any */
|
|
+static struct Qdisc *ceetm_cls_leaf(struct Qdisc *sch, unsigned long arg)
|
|
+{
|
|
+ struct ceetm_class *cl = (struct ceetm_class *)arg;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : class %X under qdisc %X\n",
|
|
+ __func__, cl->common.classid, sch->handle);
|
|
+
|
|
+ switch (cl->type) {
|
|
+ case CEETM_ROOT:
|
|
+ return cl->root.child;
|
|
+
|
|
+ case CEETM_PRIO:
|
|
+ return cl->prio.child;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static int ceetm_cls_graft(struct Qdisc *sch, unsigned long arg,
|
|
+ struct Qdisc *new, struct Qdisc **old)
|
|
+{
|
|
+ if (new && strcmp(new->ops->id, ceetm_qdisc_ops.id)) {
|
|
+ pr_err("CEETM: only ceetm qdiscs can be attached to ceetm classes\n");
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ceetm_cls_dump_stats(struct Qdisc *sch, unsigned long arg,
|
|
+ struct gnet_dump *d)
|
|
+{
|
|
+ unsigned int i;
|
|
+ struct ceetm_class *cl = (struct ceetm_class *)arg;
|
|
+ struct gnet_stats_basic_packed tmp_bstats;
|
|
+ struct ceetm_class_stats *cstats = NULL;
|
|
+ struct qm_ceetm_cq *cq = NULL;
|
|
+ struct tc_ceetm_xstats xstats;
|
|
+
|
|
+ memset(&xstats, 0, sizeof(xstats));
|
|
+ memset(&tmp_bstats, 0, sizeof(tmp_bstats));
|
|
+
|
|
+ switch (cl->type) {
|
|
+ case CEETM_ROOT:
|
|
+ return 0;
|
|
+ case CEETM_PRIO:
|
|
+ cq = cl->prio.cq;
|
|
+ break;
|
|
+ case CEETM_WBFS:
|
|
+ cq = cl->wbfs.cq;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ for_each_online_cpu(i) {
|
|
+ switch (cl->type) {
|
|
+ case CEETM_PRIO:
|
|
+ cstats = per_cpu_ptr(cl->prio.cstats, i);
|
|
+ break;
|
|
+ case CEETM_WBFS:
|
|
+ cstats = per_cpu_ptr(cl->wbfs.cstats, i);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (cstats) {
|
|
+ xstats.ern_drop_count += cstats->ern_drop_count;
|
|
+ xstats.congested_count += cstats->congested_count;
|
|
+ tmp_bstats.bytes += cstats->bstats.bytes;
|
|
+ tmp_bstats.packets += cstats->bstats.packets;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (gnet_stats_copy_basic(d, NULL, &tmp_bstats) < 0)
|
|
+ return -1;
|
|
+
|
|
+ if (cq && qman_ceetm_cq_get_dequeue_statistics(cq, 0,
|
|
+ &xstats.frame_count,
|
|
+ &xstats.byte_count))
|
|
+ return -1;
|
|
+
|
|
+ return gnet_stats_copy_app(d, &xstats, sizeof(xstats));
|
|
+}
|
|
+
|
|
+static struct tcf_proto **ceetm_tcf_chain(struct Qdisc *sch, unsigned long arg)
|
|
+{
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct ceetm_class *cl = (struct ceetm_class *)arg;
|
|
+ struct tcf_proto **fl = cl ? &cl->filter_list : &priv->filter_list;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : class %X under qdisc %X\n", __func__,
|
|
+ cl ? cl->common.classid : 0, sch->handle);
|
|
+ return fl;
|
|
+}
|
|
+
|
|
+static unsigned long ceetm_tcf_bind(struct Qdisc *sch, unsigned long parent,
|
|
+ u32 classid)
|
|
+{
|
|
+ struct ceetm_class *cl = ceetm_find(classid, sch);
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : class %X under qdisc %X\n", __func__,
|
|
+ cl ? cl->common.classid : 0, sch->handle);
|
|
+ return (unsigned long)cl;
|
|
+}
|
|
+
|
|
+static void ceetm_tcf_unbind(struct Qdisc *sch, unsigned long arg)
|
|
+{
|
|
+ struct ceetm_class *cl = (struct ceetm_class *)arg;
|
|
+
|
|
+ pr_debug(KBUILD_BASENAME " : %s : class %X under qdisc %X\n", __func__,
|
|
+ cl ? cl->common.classid : 0, sch->handle);
|
|
+}
|
|
+
|
|
+const struct Qdisc_class_ops ceetm_cls_ops = {
|
|
+ .graft = ceetm_cls_graft,
|
|
+ .leaf = ceetm_cls_leaf,
|
|
+ .get = ceetm_cls_get,
|
|
+ .put = ceetm_cls_put,
|
|
+ .change = ceetm_cls_change,
|
|
+ .delete = ceetm_cls_delete,
|
|
+ .walk = ceetm_cls_walk,
|
|
+ .tcf_chain = ceetm_tcf_chain,
|
|
+ .bind_tcf = ceetm_tcf_bind,
|
|
+ .unbind_tcf = ceetm_tcf_unbind,
|
|
+ .dump = ceetm_cls_dump,
|
|
+ .dump_stats = ceetm_cls_dump_stats,
|
|
+};
|
|
+
|
|
+struct Qdisc_ops ceetm_qdisc_ops __read_mostly = {
|
|
+ .id = "ceetm",
|
|
+ .priv_size = sizeof(struct ceetm_qdisc),
|
|
+ .cl_ops = &ceetm_cls_ops,
|
|
+ .init = ceetm_init,
|
|
+ .destroy = ceetm_destroy,
|
|
+ .change = ceetm_change,
|
|
+ .dump = ceetm_dump,
|
|
+ .attach = ceetm_attach,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/* Run the filters and classifiers attached to the qdisc on the provided skb */
|
|
+static struct ceetm_class *ceetm_classify(struct sk_buff *skb,
|
|
+ struct Qdisc *sch, int *qerr,
|
|
+ bool *act_drop)
|
|
+{
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct ceetm_class *cl = NULL, *wbfs_cl;
|
|
+ struct tcf_result res;
|
|
+ struct tcf_proto *tcf;
|
|
+ int result;
|
|
+
|
|
+ *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
|
|
+ tcf = priv->filter_list;
|
|
+ while (tcf && (result = tc_classify(skb, tcf, &res)) >= 0) {
|
|
+#ifdef CONFIG_NET_CLS_ACT
|
|
+ switch (result) {
|
|
+ case TC_ACT_QUEUED:
|
|
+ case TC_ACT_STOLEN:
|
|
+ *qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN;
|
|
+ case TC_ACT_SHOT:
|
|
+ /* No valid class found due to action */
|
|
+ *act_drop = true;
|
|
+ return NULL;
|
|
+ }
|
|
+#endif
|
|
+ cl = (void *)res.class;
|
|
+ if (!cl) {
|
|
+ if (res.classid == sch->handle) {
|
|
+ /* The filter leads to the qdisc */
|
|
+ /* TODO default qdisc */
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ cl = ceetm_find(res.classid, sch);
|
|
+ if (!cl)
|
|
+ /* The filter leads to an invalid class */
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* The class might have its own filters attached */
|
|
+ tcf = cl->filter_list;
|
|
+ }
|
|
+
|
|
+ if (!cl) {
|
|
+ /* No valid class found */
|
|
+ /* TODO default qdisc */
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ switch (cl->type) {
|
|
+ case CEETM_ROOT:
|
|
+ if (cl->root.child) {
|
|
+ /* Run the prio qdisc classifiers */
|
|
+ return ceetm_classify(skb, cl->root.child, qerr,
|
|
+ act_drop);
|
|
+ } else {
|
|
+ /* The root class does not have a child prio qdisc */
|
|
+ /* TODO default qdisc */
|
|
+ return NULL;
|
|
+ }
|
|
+ case CEETM_PRIO:
|
|
+ if (cl->prio.child) {
|
|
+ /* If filters lead to a wbfs class, return it.
|
|
+ * Otherwise, return the prio class
|
|
+ */
|
|
+ wbfs_cl = ceetm_classify(skb, cl->prio.child, qerr,
|
|
+ act_drop);
|
|
+ /* A NULL result might indicate either an erroneous
|
|
+ * filter, or no filters at all. We will assume the
|
|
+ * latter
|
|
+ */
|
|
+ return wbfs_cl ? : cl;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* For wbfs and childless prio classes, return the class directly */
|
|
+ return cl;
|
|
+}
|
|
+
|
|
+int __hot ceetm_tx(struct sk_buff *skb, struct net_device *net_dev)
|
|
+{
|
|
+ int ret;
|
|
+ bool act_drop = false;
|
|
+ struct Qdisc *sch = net_dev->qdisc;
|
|
+ struct ceetm_class *cl;
|
|
+ struct dpa_priv_s *priv_dpa;
|
|
+ struct qman_fq *egress_fq, *conf_fq;
|
|
+ struct ceetm_qdisc *priv = qdisc_priv(sch);
|
|
+ struct ceetm_qdisc_stats *qstats = this_cpu_ptr(priv->root.qstats);
|
|
+ struct ceetm_class_stats *cstats;
|
|
+ const int queue_mapping = dpa_get_queue_mapping(skb);
|
|
+ spinlock_t *root_lock = qdisc_lock(sch);
|
|
+
|
|
+ spin_lock(root_lock);
|
|
+ cl = ceetm_classify(skb, sch, &ret, &act_drop);
|
|
+ spin_unlock(root_lock);
|
|
+
|
|
+#ifdef CONFIG_NET_CLS_ACT
|
|
+ if (act_drop) {
|
|
+ if (ret & __NET_XMIT_BYPASS)
|
|
+ qstats->drops++;
|
|
+ goto drop;
|
|
+ }
|
|
+#endif
|
|
+ /* TODO default class */
|
|
+ if (unlikely(!cl)) {
|
|
+ qstats->drops++;
|
|
+ goto drop;
|
|
+ }
|
|
+
|
|
+ priv_dpa = netdev_priv(net_dev);
|
|
+ conf_fq = priv_dpa->conf_fqs[queue_mapping];
|
|
+
|
|
+ /* Choose the proper tx fq and update the basic stats (bytes and
|
|
+ * packets sent by the class)
|
|
+ */
|
|
+ switch (cl->type) {
|
|
+ case CEETM_PRIO:
|
|
+ egress_fq = &cl->prio.fq->fq;
|
|
+ cstats = this_cpu_ptr(cl->prio.cstats);
|
|
+ break;
|
|
+ case CEETM_WBFS:
|
|
+ egress_fq = &cl->wbfs.fq->fq;
|
|
+ cstats = this_cpu_ptr(cl->wbfs.cstats);
|
|
+ break;
|
|
+ default:
|
|
+ qstats->drops++;
|
|
+ goto drop;
|
|
+ }
|
|
+
|
|
+ bstats_update(&cstats->bstats, skb);
|
|
+ return dpa_tx_extended(skb, net_dev, egress_fq, conf_fq);
|
|
+
|
|
+drop:
|
|
+ dev_kfree_skb_any(skb);
|
|
+ return NET_XMIT_SUCCESS;
|
|
+}
|
|
+
|
|
+static int __init ceetm_register(void)
|
|
+{
|
|
+ int _errno = 0;
|
|
+
|
|
+ pr_info(KBUILD_MODNAME ": " DPA_CEETM_DESCRIPTION "\n");
|
|
+
|
|
+ _errno = register_qdisc(&ceetm_qdisc_ops);
|
|
+ if (unlikely(_errno))
|
|
+ pr_err(KBUILD_MODNAME
|
|
+ ": %s:%hu:%s(): register_qdisc() = %d\n",
|
|
+ KBUILD_BASENAME ".c", __LINE__, __func__, _errno);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static void __exit ceetm_unregister(void)
|
|
+{
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME ".c", __func__);
|
|
+
|
|
+ unregister_qdisc(&ceetm_qdisc_ops);
|
|
+}
|
|
+
|
|
+module_init(ceetm_register);
|
|
+module_exit(ceetm_unregister);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_ceetm.h
|
|
@@ -0,0 +1,236 @@
|
|
+/* Copyright 2008-2016 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifndef __DPAA_ETH_CEETM_H
|
|
+#define __DPAA_ETH_CEETM_H
|
|
+
|
|
+#include <net/pkt_sched.h>
|
|
+#include <net/netlink.h>
|
|
+#include <lnxwrp_fm.h>
|
|
+
|
|
+#include "mac.h"
|
|
+#include "dpaa_eth_common.h"
|
|
+
|
|
+/* Mask to determine the sub-portal id from a channel number */
|
|
+#define CHANNEL_SP_MASK 0x1f
|
|
+/* The number of the last channel that services DCP0, connected to FMan 0.
|
|
+ * Value validated for B4 and T series platforms.
|
|
+ */
|
|
+#define DCP0_MAX_CHANNEL 0x80f
|
|
+/* A2V=1 - field A2 is valid
|
|
+ * A0V=1 - field A0 is valid - enables frame confirmation
|
|
+ * OVOM=1 - override operation mode bits with values from A2
|
|
+ * EBD=1 - external buffers are deallocated at the end of the FMan flow
|
|
+ * NL=0 - the BMI releases all the internal buffers
|
|
+ */
|
|
+#define CEETM_CONTEXT_A 0x1a00000080000000
|
|
+/* The ratio between the superior and inferior congestion state thresholds. The
|
|
+ * lower threshold is set to 7/8 of the superior one (as the default for WQ
|
|
+ * scheduling).
|
|
+ */
|
|
+#define CEETM_CCGR_RATIO 0.875
|
|
+/* For functional purposes, there are num_tx_queues pfifo qdiscs through which
|
|
+ * frames reach the driver. Their handles start from 1:21. Handles 1:1 to 1:20
|
|
+ * are reserved for the maximum 32 CEETM channels (majors and minors are in
|
|
+ * hex).
|
|
+ */
|
|
+#define PFIFO_MIN_OFFSET 0x21
|
|
+
|
|
+/* A maximum of 8 CQs can be linked to a CQ channel or to a WBFS scheduler. */
|
|
+#define CEETM_MAX_PRIO_QCOUNT 8
|
|
+#define CEETM_MAX_WBFS_QCOUNT 8
|
|
+#define CEETM_MIN_WBFS_QCOUNT 4
|
|
+
|
|
+/* The id offsets of the CQs belonging to WBFS groups (ids 8-11/15 for group A
|
|
+ * and/or 12-15 for group B).
|
|
+ */
|
|
+#define WBFS_GRP_A_OFFSET 8
|
|
+#define WBFS_GRP_B_OFFSET 12
|
|
+
|
|
+#define WBFS_GRP_A 1
|
|
+#define WBFS_GRP_B 2
|
|
+#define WBFS_GRP_LARGE 3
|
|
+
|
|
+enum {
|
|
+ TCA_CEETM_UNSPEC,
|
|
+ TCA_CEETM_COPT,
|
|
+ TCA_CEETM_QOPS,
|
|
+ __TCA_CEETM_MAX,
|
|
+};
|
|
+
|
|
+/* CEETM configuration types */
|
|
+enum {
|
|
+ CEETM_ROOT = 1,
|
|
+ CEETM_PRIO,
|
|
+ CEETM_WBFS
|
|
+};
|
|
+
|
|
+#define TCA_CEETM_MAX (__TCA_CEETM_MAX - 1)
|
|
+extern const struct nla_policy ceetm_policy[TCA_CEETM_MAX + 1];
|
|
+
|
|
+struct ceetm_class;
|
|
+struct ceetm_qdisc_stats;
|
|
+struct ceetm_class_stats;
|
|
+
|
|
+struct ceetm_fq {
|
|
+ struct qman_fq fq;
|
|
+ struct net_device *net_dev;
|
|
+ struct ceetm_class *ceetm_cls;
|
|
+};
|
|
+
|
|
+struct root_q {
|
|
+ struct Qdisc **qdiscs;
|
|
+ __u16 overhead;
|
|
+ __u32 rate;
|
|
+ __u32 ceil;
|
|
+ struct qm_ceetm_sp *sp;
|
|
+ struct qm_ceetm_lni *lni;
|
|
+ struct ceetm_qdisc_stats __percpu *qstats;
|
|
+};
|
|
+
|
|
+struct prio_q {
|
|
+ __u16 qcount;
|
|
+ struct ceetm_class *parent;
|
|
+};
|
|
+
|
|
+struct wbfs_q {
|
|
+ __u16 qcount;
|
|
+ int group_type;
|
|
+ struct ceetm_class *parent;
|
|
+ __u16 cr;
|
|
+ __u16 er;
|
|
+};
|
|
+
|
|
+struct ceetm_qdisc {
|
|
+ int type; /* LNI/CHNL/WBFS */
|
|
+ bool shaped;
|
|
+ union {
|
|
+ struct root_q root;
|
|
+ struct prio_q prio;
|
|
+ struct wbfs_q wbfs;
|
|
+ };
|
|
+ struct Qdisc_class_hash clhash;
|
|
+ struct tcf_proto *filter_list; /* qdisc attached filters */
|
|
+};
|
|
+
|
|
+/* CEETM Qdisc configuration parameters */
|
|
+struct tc_ceetm_qopt {
|
|
+ __u32 type;
|
|
+ __u16 shaped;
|
|
+ __u16 qcount;
|
|
+ __u16 overhead;
|
|
+ __u32 rate;
|
|
+ __u32 ceil;
|
|
+ __u16 cr;
|
|
+ __u16 er;
|
|
+ __u8 qweight[CEETM_MAX_WBFS_QCOUNT];
|
|
+};
|
|
+
|
|
+struct root_c {
|
|
+ unsigned int rate;
|
|
+ unsigned int ceil;
|
|
+ unsigned int tbl;
|
|
+ bool wbfs_grp_a;
|
|
+ bool wbfs_grp_b;
|
|
+ bool wbfs_grp_large;
|
|
+ struct Qdisc *child;
|
|
+ struct qm_ceetm_channel *ch;
|
|
+};
|
|
+
|
|
+struct prio_c {
|
|
+ bool cr;
|
|
+ bool er;
|
|
+ struct ceetm_fq *fq; /* Hardware FQ instance Handle */
|
|
+ struct qm_ceetm_lfq *lfq;
|
|
+ struct qm_ceetm_cq *cq; /* Hardware Class Queue instance Handle */
|
|
+ struct qm_ceetm_ccg *ccg;
|
|
+ /* only one wbfs can be linked to one priority CQ */
|
|
+ struct Qdisc *child;
|
|
+ struct ceetm_class_stats __percpu *cstats;
|
|
+};
|
|
+
|
|
+struct wbfs_c {
|
|
+ __u8 weight; /* The weight of the class between 1 and 248 */
|
|
+ struct ceetm_fq *fq; /* Hardware FQ instance Handle */
|
|
+ struct qm_ceetm_lfq *lfq;
|
|
+ struct qm_ceetm_cq *cq; /* Hardware Class Queue instance Handle */
|
|
+ struct qm_ceetm_ccg *ccg;
|
|
+ struct ceetm_class_stats __percpu *cstats;
|
|
+};
|
|
+
|
|
+struct ceetm_class {
|
|
+ struct Qdisc_class_common common;
|
|
+ int refcnt; /* usage count of this class */
|
|
+ struct tcf_proto *filter_list; /* class attached filters */
|
|
+ struct Qdisc *parent;
|
|
+ bool shaped;
|
|
+ int type; /* ROOT/PRIO/WBFS */
|
|
+ union {
|
|
+ struct root_c root;
|
|
+ struct prio_c prio;
|
|
+ struct wbfs_c wbfs;
|
|
+ };
|
|
+};
|
|
+
|
|
+/* CEETM Class configuration parameters */
|
|
+struct tc_ceetm_copt {
|
|
+ __u32 type;
|
|
+ __u16 shaped;
|
|
+ __u32 rate;
|
|
+ __u32 ceil;
|
|
+ __u16 tbl;
|
|
+ __u16 cr;
|
|
+ __u16 er;
|
|
+ __u8 weight;
|
|
+};
|
|
+
|
|
+/* CEETM stats */
|
|
+struct ceetm_qdisc_stats {
|
|
+ __u32 drops;
|
|
+};
|
|
+
|
|
+struct ceetm_class_stats {
|
|
+ /* Software counters */
|
|
+ struct gnet_stats_basic_packed bstats;
|
|
+ __u32 ern_drop_count;
|
|
+ __u32 congested_count;
|
|
+};
|
|
+
|
|
+struct tc_ceetm_xstats {
|
|
+ __u32 ern_drop_count;
|
|
+ __u32 congested_count;
|
|
+ /* Hardware counters */
|
|
+ __u64 frame_count;
|
|
+ __u64 byte_count;
|
|
+};
|
|
+
|
|
+int __hot ceetm_tx(struct sk_buff *skb, struct net_device *net_dev);
|
|
+#endif
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_common.c
|
|
@@ -0,0 +1,1812 @@
|
|
+/* Copyright 2008-2013 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/of_net.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/percpu.h>
|
|
+#include <linux/highmem.h>
|
|
+#include <linux/sort.h>
|
|
+#include <linux/fsl_qman.h>
|
|
+#include <linux/ip.h>
|
|
+#include <linux/ipv6.h>
|
|
+#include <linux/if_vlan.h> /* vlan_eth_hdr */
|
|
+#include "dpaa_eth.h"
|
|
+#include "dpaa_eth_common.h"
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+#include "dpaa_1588.h"
|
|
+#endif
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+#include "dpaa_debugfs.h"
|
|
+#endif /* CONFIG_FSL_DPAA_DBG_LOOP */
|
|
+#include "mac.h"
|
|
+
|
|
+/* Size in bytes of the FQ taildrop threshold */
|
|
+#define DPA_FQ_TD 0x200000
|
|
+
|
|
+#ifdef CONFIG_PTP_1588_CLOCK_DPAA
|
|
+struct ptp_priv_s ptp_priv;
|
|
+#endif
|
|
+
|
|
+static struct dpa_bp *dpa_bp_array[64];
|
|
+
|
|
+int dpa_max_frm;
|
|
+EXPORT_SYMBOL(dpa_max_frm);
|
|
+
|
|
+int dpa_rx_extra_headroom;
|
|
+EXPORT_SYMBOL(dpa_rx_extra_headroom);
|
|
+
|
|
+int dpa_num_cpus = NR_CPUS;
|
|
+
|
|
+static const struct fqid_cell tx_confirm_fqids[] = {
|
|
+ {0, DPAA_ETH_TX_QUEUES}
|
|
+};
|
|
+
|
|
+static struct fqid_cell default_fqids[][3] = {
|
|
+ [RX] = { {0, 1}, {0, 1}, {0, DPAA_ETH_RX_QUEUES} },
|
|
+ [TX] = { {0, 1}, {0, 1}, {0, DPAA_ETH_TX_QUEUES} }
|
|
+};
|
|
+
|
|
+static const char fsl_qman_frame_queues[][25] = {
|
|
+ [RX] = "fsl,qman-frame-queues-rx",
|
|
+ [TX] = "fsl,qman-frame-queues-tx"
|
|
+};
|
|
+#ifdef CONFIG_FSL_DPAA_HOOKS
|
|
+/* A set of callbacks for hooking into the fastpath at different points. */
|
|
+struct dpaa_eth_hooks_s dpaa_eth_hooks;
|
|
+EXPORT_SYMBOL(dpaa_eth_hooks);
|
|
+/* This function should only be called on the probe paths, since it makes no
|
|
+ * effort to guarantee consistency of the destination hooks structure.
|
|
+ */
|
|
+void fsl_dpaa_eth_set_hooks(struct dpaa_eth_hooks_s *hooks)
|
|
+{
|
|
+ if (hooks)
|
|
+ dpaa_eth_hooks = *hooks;
|
|
+ else
|
|
+ pr_err("NULL pointer to hooks!\n");
|
|
+}
|
|
+EXPORT_SYMBOL(fsl_dpaa_eth_set_hooks);
|
|
+#endif
|
|
+
|
|
+int dpa_netdev_init(struct net_device *net_dev,
|
|
+ const uint8_t *mac_addr,
|
|
+ uint16_t tx_timeout)
|
|
+{
|
|
+ int err;
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ struct device *dev = net_dev->dev.parent;
|
|
+
|
|
+ net_dev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
|
|
+
|
|
+ net_dev->features |= net_dev->hw_features;
|
|
+ net_dev->vlan_features = net_dev->features;
|
|
+
|
|
+ memcpy(net_dev->perm_addr, mac_addr, net_dev->addr_len);
|
|
+ memcpy(net_dev->dev_addr, mac_addr, net_dev->addr_len);
|
|
+
|
|
+ net_dev->ethtool_ops = &dpa_ethtool_ops;
|
|
+
|
|
+ net_dev->needed_headroom = priv->tx_headroom;
|
|
+ net_dev->watchdog_timeo = msecs_to_jiffies(tx_timeout);
|
|
+
|
|
+ err = register_netdev(net_dev);
|
|
+ if (err < 0) {
|
|
+ dev_err(dev, "register_netdev() = %d\n", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ /* create debugfs entry for this net_device */
|
|
+ err = dpa_netdev_debugfs_create(net_dev);
|
|
+ if (err) {
|
|
+ unregister_netdev(net_dev);
|
|
+ return err;
|
|
+ }
|
|
+#endif /* CONFIG_FSL_DPAA_DBG_LOOP */
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_netdev_init);
|
|
+
|
|
+int __cold dpa_start(struct net_device *net_dev)
|
|
+{
|
|
+ int err, i;
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct mac_device *mac_dev;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ mac_dev = priv->mac_dev;
|
|
+
|
|
+ err = mac_dev->init_phy(net_dev, priv->mac_dev);
|
|
+ if (err < 0) {
|
|
+ if (netif_msg_ifup(priv))
|
|
+ netdev_err(net_dev, "init_phy() = %d\n", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ for_each_port_device(i, mac_dev->port_dev) {
|
|
+ err = fm_port_enable(mac_dev->port_dev[i]);
|
|
+ if (err)
|
|
+ goto mac_start_failed;
|
|
+ }
|
|
+
|
|
+ err = priv->mac_dev->start(mac_dev);
|
|
+ if (err < 0) {
|
|
+ if (netif_msg_ifup(priv))
|
|
+ netdev_err(net_dev, "mac_dev->start() = %d\n", err);
|
|
+ goto mac_start_failed;
|
|
+ }
|
|
+
|
|
+ netif_tx_start_all_queues(net_dev);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+mac_start_failed:
|
|
+ for_each_port_device(i, mac_dev->port_dev)
|
|
+ fm_port_disable(mac_dev->port_dev[i]);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_start);
|
|
+
|
|
+int __cold dpa_stop(struct net_device *net_dev)
|
|
+{
|
|
+ int _errno, i, err;
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct mac_device *mac_dev;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ mac_dev = priv->mac_dev;
|
|
+
|
|
+ netif_tx_stop_all_queues(net_dev);
|
|
+ /* Allow the Fman (Tx) port to process in-flight frames before we
|
|
+ * try switching it off.
|
|
+ */
|
|
+ usleep_range(5000, 10000);
|
|
+
|
|
+ _errno = mac_dev->stop(mac_dev);
|
|
+ if (unlikely(_errno < 0))
|
|
+ if (netif_msg_ifdown(priv))
|
|
+ netdev_err(net_dev, "mac_dev->stop() = %d\n",
|
|
+ _errno);
|
|
+
|
|
+ for_each_port_device(i, mac_dev->port_dev) {
|
|
+ err = fm_port_disable(mac_dev->port_dev[i]);
|
|
+ _errno = err ? err : _errno;
|
|
+ }
|
|
+
|
|
+ if (mac_dev->phy_dev)
|
|
+ phy_disconnect(mac_dev->phy_dev);
|
|
+ mac_dev->phy_dev = NULL;
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_stop);
|
|
+
|
|
+void __cold dpa_timeout(struct net_device *net_dev)
|
|
+{
|
|
+ const struct dpa_priv_s *priv;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ percpu_priv = raw_cpu_ptr(priv->percpu_priv);
|
|
+
|
|
+ if (netif_msg_timer(priv))
|
|
+ netdev_crit(net_dev, "Transmit timeout!\n");
|
|
+
|
|
+ percpu_priv->stats.tx_errors++;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_timeout);
|
|
+
|
|
+/* net_device */
|
|
+
|
|
+/**
|
|
+ * @param net_dev the device for which statistics are calculated
|
|
+ * @param stats the function fills this structure with the device's statistics
|
|
+ * @return the address of the structure containing the statistics
|
|
+ *
|
|
+ * Calculates the statistics for the given device by adding the statistics
|
|
+ * collected by each CPU.
|
|
+ */
|
|
+void __cold
|
|
+dpa_get_stats64(struct net_device *net_dev,
|
|
+ struct rtnl_link_stats64 *stats)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ u64 *cpustats;
|
|
+ u64 *netstats = (u64 *)stats;
|
|
+ int i, j;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ int numstats = sizeof(struct rtnl_link_stats64) / sizeof(u64);
|
|
+
|
|
+ for_each_possible_cpu(i) {
|
|
+ percpu_priv = per_cpu_ptr(priv->percpu_priv, i);
|
|
+
|
|
+ cpustats = (u64 *)&percpu_priv->stats;
|
|
+
|
|
+ for (j = 0; j < numstats; j++)
|
|
+ netstats[j] += cpustats[j];
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_get_stats64);
|
|
+
|
|
+int dpa_change_mtu(struct net_device *net_dev, int new_mtu)
|
|
+{
|
|
+ const int max_mtu = dpa_get_max_mtu();
|
|
+
|
|
+ /* Make sure we don't exceed the Ethernet controller's MAXFRM */
|
|
+ if (new_mtu < 68 || new_mtu > max_mtu) {
|
|
+ netdev_err(net_dev, "Invalid L3 mtu %d (must be between %d and %d).\n",
|
|
+ new_mtu, 68, max_mtu);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ net_dev->mtu = new_mtu;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_change_mtu);
|
|
+
|
|
+/* .ndo_init callback */
|
|
+int dpa_ndo_init(struct net_device *net_dev)
|
|
+{
|
|
+ /* If fsl_fm_max_frm is set to a higher value than the all-common 1500,
|
|
+ * we choose conservatively and let the user explicitly set a higher
|
|
+ * MTU via ifconfig. Otherwise, the user may end up with different MTUs
|
|
+ * in the same LAN.
|
|
+ * If on the other hand fsl_fm_max_frm has been chosen below 1500,
|
|
+ * start with the maximum allowed.
|
|
+ */
|
|
+ int init_mtu = min(dpa_get_max_mtu(), ETH_DATA_LEN);
|
|
+
|
|
+ pr_debug("Setting initial MTU on net device: %d\n", init_mtu);
|
|
+ net_dev->mtu = init_mtu;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_ndo_init);
|
|
+
|
|
+int dpa_set_features(struct net_device *dev, netdev_features_t features)
|
|
+{
|
|
+ /* Not much to do here for now */
|
|
+ dev->features = features;
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_set_features);
|
|
+
|
|
+netdev_features_t dpa_fix_features(struct net_device *dev,
|
|
+ netdev_features_t features)
|
|
+{
|
|
+ netdev_features_t unsupported_features = 0;
|
|
+
|
|
+ /* In theory we should never be requested to enable features that
|
|
+ * we didn't set in netdev->features and netdev->hw_features at probe
|
|
+ * time, but double check just to be on the safe side.
|
|
+ * We don't support enabling Rx csum through ethtool yet
|
|
+ */
|
|
+ unsupported_features |= NETIF_F_RXCSUM;
|
|
+
|
|
+ features &= ~unsupported_features;
|
|
+
|
|
+ return features;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_fix_features);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+u64 dpa_get_timestamp_ns(const struct dpa_priv_s *priv, enum port_type rx_tx,
|
|
+ const void *data)
|
|
+{
|
|
+ u64 *ts, ns;
|
|
+
|
|
+ ts = fm_port_get_buffer_time_stamp(priv->mac_dev->port_dev[rx_tx],
|
|
+ data);
|
|
+
|
|
+ if (!ts || *ts == 0)
|
|
+ return 0;
|
|
+
|
|
+ be64_to_cpus(ts);
|
|
+
|
|
+ /* multiple DPA_PTP_NOMINAL_FREQ_PERIOD_NS for case of non power of 2 */
|
|
+ ns = *ts << DPA_PTP_NOMINAL_FREQ_PERIOD_SHIFT;
|
|
+
|
|
+ return ns;
|
|
+}
|
|
+
|
|
+int dpa_get_ts(const struct dpa_priv_s *priv, enum port_type rx_tx,
|
|
+ struct skb_shared_hwtstamps *shhwtstamps, const void *data)
|
|
+{
|
|
+ u64 ns;
|
|
+
|
|
+ ns = dpa_get_timestamp_ns(priv, rx_tx, data);
|
|
+
|
|
+ if (ns == 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
|
|
+ shhwtstamps->hwtstamp = ns_to_ktime(ns);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dpa_ts_tx_enable(struct net_device *dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(dev);
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+
|
|
+ if (mac_dev->fm_rtc_enable)
|
|
+ mac_dev->fm_rtc_enable(get_fm_handle(dev));
|
|
+ if (mac_dev->ptp_enable)
|
|
+ mac_dev->ptp_enable(mac_dev->get_mac_handle(mac_dev));
|
|
+
|
|
+ priv->ts_tx_en = true;
|
|
+}
|
|
+
|
|
+static void dpa_ts_tx_disable(struct net_device *dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(dev);
|
|
+
|
|
+#if 0
|
|
+/* the RTC might be needed by the Rx Ts, cannot disable here
|
|
+ * no separate ptp_disable API for Rx/Tx, cannot disable here
|
|
+ */
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+
|
|
+ if (mac_dev->fm_rtc_disable)
|
|
+ mac_dev->fm_rtc_disable(get_fm_handle(dev));
|
|
+
|
|
+ if (mac_dev->ptp_disable)
|
|
+ mac_dev->ptp_disable(mac_dev->get_mac_handle(mac_dev));
|
|
+#endif
|
|
+
|
|
+ priv->ts_tx_en = false;
|
|
+}
|
|
+
|
|
+static void dpa_ts_rx_enable(struct net_device *dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(dev);
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+
|
|
+ if (mac_dev->fm_rtc_enable)
|
|
+ mac_dev->fm_rtc_enable(get_fm_handle(dev));
|
|
+ if (mac_dev->ptp_enable)
|
|
+ mac_dev->ptp_enable(mac_dev->get_mac_handle(mac_dev));
|
|
+
|
|
+ priv->ts_rx_en = true;
|
|
+}
|
|
+
|
|
+static void dpa_ts_rx_disable(struct net_device *dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(dev);
|
|
+
|
|
+#if 0
|
|
+/* the RTC might be needed by the Tx Ts, cannot disable here
|
|
+ * no separate ptp_disable API for Rx/Tx, cannot disable here
|
|
+ */
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+
|
|
+ if (mac_dev->fm_rtc_disable)
|
|
+ mac_dev->fm_rtc_disable(get_fm_handle(dev));
|
|
+
|
|
+ if (mac_dev->ptp_disable)
|
|
+ mac_dev->ptp_disable(mac_dev->get_mac_handle(mac_dev));
|
|
+#endif
|
|
+
|
|
+ priv->ts_rx_en = false;
|
|
+}
|
|
+
|
|
+static int dpa_ts_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
|
|
+{
|
|
+ struct hwtstamp_config config;
|
|
+
|
|
+ if (copy_from_user(&config, rq->ifr_data, sizeof(config)))
|
|
+ return -EFAULT;
|
|
+
|
|
+ switch (config.tx_type) {
|
|
+ case HWTSTAMP_TX_OFF:
|
|
+ dpa_ts_tx_disable(dev);
|
|
+ break;
|
|
+ case HWTSTAMP_TX_ON:
|
|
+ dpa_ts_tx_enable(dev);
|
|
+ break;
|
|
+ default:
|
|
+ return -ERANGE;
|
|
+ }
|
|
+
|
|
+ if (config.rx_filter == HWTSTAMP_FILTER_NONE)
|
|
+ dpa_ts_rx_disable(dev);
|
|
+ else {
|
|
+ dpa_ts_rx_enable(dev);
|
|
+ /* TS is set for all frame types, not only those requested */
|
|
+ config.rx_filter = HWTSTAMP_FILTER_ALL;
|
|
+ }
|
|
+
|
|
+ return copy_to_user(rq->ifr_data, &config, sizeof(config)) ?
|
|
+ -EFAULT : 0;
|
|
+}
|
|
+#endif /* CONFIG_FSL_DPAA_TS */
|
|
+
|
|
+int dpa_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
|
|
+{
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ struct dpa_priv_s *priv = netdev_priv(dev);
|
|
+#endif
|
|
+ int ret = 0;
|
|
+
|
|
+ /* at least one timestamping feature must be enabled */
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ if (!netif_running(dev))
|
|
+#endif
|
|
+ return -EINVAL;
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ if (cmd == SIOCSHWTSTAMP)
|
|
+ return dpa_ts_ioctl(dev, rq, cmd);
|
|
+#endif /* CONFIG_FSL_DPAA_TS */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ if ((cmd >= PTP_ENBL_TXTS_IOCTL) && (cmd <= PTP_CLEANUP_TS)) {
|
|
+ if (priv->tsu && priv->tsu->valid)
|
|
+ ret = dpa_ioctl_1588(dev, rq, cmd);
|
|
+ else
|
|
+ ret = -ENODEV;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_ioctl);
|
|
+
|
|
+int __cold dpa_remove(struct platform_device *of_dev)
|
|
+{
|
|
+ int err;
|
|
+ struct device *dev;
|
|
+ struct net_device *net_dev;
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ dev = &of_dev->dev;
|
|
+ net_dev = dev_get_drvdata(dev);
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ dpaa_eth_sysfs_remove(dev);
|
|
+
|
|
+ dev_set_drvdata(dev, NULL);
|
|
+ unregister_netdev(net_dev);
|
|
+
|
|
+ err = dpa_fq_free(dev, &priv->dpa_fq_list);
|
|
+
|
|
+ qman_delete_cgr_safe(&priv->ingress_cgr);
|
|
+ qman_release_cgrid(priv->ingress_cgr.cgrid);
|
|
+ qman_delete_cgr_safe(&priv->cgr_data.cgr);
|
|
+ qman_release_cgrid(priv->cgr_data.cgr.cgrid);
|
|
+
|
|
+ dpa_private_napi_del(net_dev);
|
|
+
|
|
+ dpa_bp_free(priv);
|
|
+
|
|
+ if (priv->buf_layout)
|
|
+ devm_kfree(dev, priv->buf_layout);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ /* remove debugfs entry for this net_device */
|
|
+ dpa_netdev_debugfs_remove(net_dev);
|
|
+#endif /* CONFIG_FSL_DPAA_DBG_LOOP */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ if (priv->tsu && priv->tsu->valid)
|
|
+ dpa_ptp_cleanup(priv);
|
|
+#endif
|
|
+
|
|
+ free_netdev(net_dev);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_remove);
|
|
+
|
|
+struct mac_device * __cold __must_check
|
|
+__attribute__((nonnull))
|
|
+dpa_mac_probe(struct platform_device *_of_dev)
|
|
+{
|
|
+ struct device *dpa_dev, *dev;
|
|
+ struct device_node *mac_node;
|
|
+ struct platform_device *of_dev;
|
|
+ struct mac_device *mac_dev;
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ int lenp;
|
|
+ const phandle *phandle_prop;
|
|
+ struct net_device *net_dev = NULL;
|
|
+ struct dpa_priv_s *priv = NULL;
|
|
+ struct device_node *timer_node;
|
|
+#endif
|
|
+ dpa_dev = &_of_dev->dev;
|
|
+
|
|
+ mac_node = of_parse_phandle(_of_dev->dev.of_node, "fsl,fman-mac", 0);
|
|
+ if (unlikely(mac_node == NULL)) {
|
|
+ dev_err(dpa_dev, "Cannot find MAC device device tree node\n");
|
|
+ return ERR_PTR(-EFAULT);
|
|
+ }
|
|
+
|
|
+ of_dev = of_find_device_by_node(mac_node);
|
|
+ if (unlikely(of_dev == NULL)) {
|
|
+ dev_err(dpa_dev, "of_find_device_by_node(%s) failed\n",
|
|
+ mac_node->full_name);
|
|
+ of_node_put(mac_node);
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+ of_node_put(mac_node);
|
|
+
|
|
+ dev = &of_dev->dev;
|
|
+
|
|
+ mac_dev = dev_get_drvdata(dev);
|
|
+ if (unlikely(mac_dev == NULL)) {
|
|
+ dev_err(dpa_dev, "dev_get_drvdata(%s) failed\n",
|
|
+ dev_name(dev));
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ phandle_prop = of_get_property(mac_node, "ptimer-handle", &lenp);
|
|
+ if (phandle_prop && ((mac_dev->phy_if != PHY_INTERFACE_MODE_SGMII) ||
|
|
+ ((mac_dev->phy_if == PHY_INTERFACE_MODE_SGMII) &&
|
|
+ (mac_dev->speed == SPEED_1000)))) {
|
|
+ timer_node = of_find_node_by_phandle(*phandle_prop);
|
|
+ if (timer_node)
|
|
+ net_dev = dev_get_drvdata(dpa_dev);
|
|
+ if (timer_node && net_dev) {
|
|
+ priv = netdev_priv(net_dev);
|
|
+ if (!dpa_ptp_init(priv))
|
|
+ dev_info(dev, "%s: ptp 1588 is initialized.\n",
|
|
+ mac_node->full_name);
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_PTP_1588_CLOCK_DPAA
|
|
+ if ((mac_dev->phy_if != PHY_INTERFACE_MODE_SGMII) ||
|
|
+ ((mac_dev->phy_if == PHY_INTERFACE_MODE_SGMII) &&
|
|
+ (mac_dev->speed == SPEED_1000))) {
|
|
+ ptp_priv.node = of_parse_phandle(mac_node, "ptimer-handle", 0);
|
|
+ if (ptp_priv.node) {
|
|
+ ptp_priv.of_dev = of_find_device_by_node(ptp_priv.node);
|
|
+ if (unlikely(ptp_priv.of_dev == NULL)) {
|
|
+ dev_err(dpa_dev,
|
|
+ "Cannot find device represented by timer_node\n");
|
|
+ of_node_put(ptp_priv.node);
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+ ptp_priv.mac_dev = mac_dev;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+ return mac_dev;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_mac_probe);
|
|
+
|
|
+int dpa_set_mac_address(struct net_device *net_dev, void *addr)
|
|
+{
|
|
+ const struct dpa_priv_s *priv;
|
|
+ int _errno;
|
|
+ struct mac_device *mac_dev;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ _errno = eth_mac_addr(net_dev, addr);
|
|
+ if (_errno < 0) {
|
|
+ if (netif_msg_drv(priv))
|
|
+ netdev_err(net_dev,
|
|
+ "eth_mac_addr() = %d\n",
|
|
+ _errno);
|
|
+ return _errno;
|
|
+ }
|
|
+
|
|
+ mac_dev = priv->mac_dev;
|
|
+
|
|
+ _errno = mac_dev->change_addr(mac_dev->get_mac_handle(mac_dev),
|
|
+ net_dev->dev_addr);
|
|
+ if (_errno < 0) {
|
|
+ if (netif_msg_drv(priv))
|
|
+ netdev_err(net_dev,
|
|
+ "mac_dev->change_addr() = %d\n",
|
|
+ _errno);
|
|
+ return _errno;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_set_mac_address);
|
|
+
|
|
+void dpa_set_rx_mode(struct net_device *net_dev)
|
|
+{
|
|
+ int _errno;
|
|
+ const struct dpa_priv_s *priv;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ if (!!(net_dev->flags & IFF_PROMISC) != priv->mac_dev->promisc) {
|
|
+ priv->mac_dev->promisc = !priv->mac_dev->promisc;
|
|
+ _errno = priv->mac_dev->set_promisc(
|
|
+ priv->mac_dev->get_mac_handle(priv->mac_dev),
|
|
+ priv->mac_dev->promisc);
|
|
+ if (unlikely(_errno < 0) && netif_msg_drv(priv))
|
|
+ netdev_err(net_dev,
|
|
+ "mac_dev->set_promisc() = %d\n",
|
|
+ _errno);
|
|
+ }
|
|
+
|
|
+ _errno = priv->mac_dev->set_multi(net_dev, priv->mac_dev);
|
|
+ if (unlikely(_errno < 0) && netif_msg_drv(priv))
|
|
+ netdev_err(net_dev, "mac_dev->set_multi() = %d\n", _errno);
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_set_rx_mode);
|
|
+
|
|
+void dpa_set_buffers_layout(struct mac_device *mac_dev,
|
|
+ struct dpa_buffer_layout_s *layout)
|
|
+{
|
|
+ struct fm_port_params params;
|
|
+
|
|
+ /* Rx */
|
|
+ layout[RX].priv_data_size = (uint16_t)DPA_RX_PRIV_DATA_SIZE;
|
|
+ layout[RX].parse_results = true;
|
|
+ layout[RX].hash_results = true;
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ layout[RX].time_stamp = true;
|
|
+#endif
|
|
+ fm_port_get_buff_layout_ext_params(mac_dev->port_dev[RX], ¶ms);
|
|
+ layout[RX].manip_extra_space = params.manip_extra_space;
|
|
+ /* a value of zero for data alignment means "don't care", so align to
|
|
+ * a non-zero value to prevent FMD from using its own default
|
|
+ */
|
|
+ layout[RX].data_align = params.data_align ? : DPA_FD_DATA_ALIGNMENT;
|
|
+
|
|
+ /* Tx */
|
|
+ layout[TX].priv_data_size = DPA_TX_PRIV_DATA_SIZE;
|
|
+ layout[TX].parse_results = true;
|
|
+ layout[TX].hash_results = true;
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ layout[TX].time_stamp = true;
|
|
+#endif
|
|
+ fm_port_get_buff_layout_ext_params(mac_dev->port_dev[TX], ¶ms);
|
|
+ layout[TX].manip_extra_space = params.manip_extra_space;
|
|
+ layout[TX].data_align = params.data_align ? : DPA_FD_DATA_ALIGNMENT;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_set_buffers_layout);
|
|
+
|
|
+int __attribute__((nonnull))
|
|
+dpa_bp_alloc(struct dpa_bp *dpa_bp)
|
|
+{
|
|
+ int err;
|
|
+ struct bman_pool_params bp_params;
|
|
+ struct platform_device *pdev;
|
|
+
|
|
+ if (dpa_bp->size == 0 || dpa_bp->config_count == 0) {
|
|
+ pr_err("Buffer pool is not properly initialized! Missing size or initial number of buffers");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ memset(&bp_params, 0, sizeof(struct bman_pool_params));
|
|
+#ifdef CONFIG_FMAN_PFC
|
|
+ bp_params.flags = BMAN_POOL_FLAG_THRESH;
|
|
+ bp_params.thresholds[0] = bp_params.thresholds[2] =
|
|
+ CONFIG_FSL_DPAA_ETH_REFILL_THRESHOLD;
|
|
+ bp_params.thresholds[1] = bp_params.thresholds[3] =
|
|
+ CONFIG_FSL_DPAA_ETH_MAX_BUF_COUNT;
|
|
+#endif
|
|
+
|
|
+ /* If the pool is already specified, we only create one per bpid */
|
|
+ if (dpa_bpid2pool_use(dpa_bp->bpid))
|
|
+ return 0;
|
|
+
|
|
+ if (dpa_bp->bpid == 0)
|
|
+ bp_params.flags |= BMAN_POOL_FLAG_DYNAMIC_BPID;
|
|
+ else
|
|
+ bp_params.bpid = dpa_bp->bpid;
|
|
+
|
|
+ dpa_bp->pool = bman_new_pool(&bp_params);
|
|
+ if (unlikely(dpa_bp->pool == NULL)) {
|
|
+ pr_err("bman_new_pool() failed\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ dpa_bp->bpid = (uint8_t)bman_get_params(dpa_bp->pool)->bpid;
|
|
+
|
|
+ pdev = platform_device_register_simple("dpaa_eth_bpool",
|
|
+ dpa_bp->bpid, NULL, 0);
|
|
+ if (IS_ERR(pdev)) {
|
|
+ pr_err("platform_device_register_simple() failed\n");
|
|
+ err = PTR_ERR(pdev);
|
|
+ goto pdev_register_failed;
|
|
+ }
|
|
+ {
|
|
+ struct dma_map_ops *ops = get_dma_ops(&pdev->dev);
|
|
+ ops->dma_supported = NULL;
|
|
+ }
|
|
+ err = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40));
|
|
+ if (err) {
|
|
+ pr_err("dma_coerce_mask_and_coherent() failed\n");
|
|
+ goto pdev_mask_failed;
|
|
+ }
|
|
+#ifdef CONFIG_FMAN_ARM
|
|
+ /* force coherency */
|
|
+ pdev->dev.archdata.dma_coherent = true;
|
|
+ arch_setup_dma_ops(&pdev->dev, 0, 0, NULL, true);
|
|
+#endif
|
|
+
|
|
+ dpa_bp->dev = &pdev->dev;
|
|
+
|
|
+ if (dpa_bp->seed_cb) {
|
|
+ err = dpa_bp->seed_cb(dpa_bp);
|
|
+ if (err)
|
|
+ goto pool_seed_failed;
|
|
+ }
|
|
+
|
|
+ dpa_bpid2pool_map(dpa_bp->bpid, dpa_bp);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+pool_seed_failed:
|
|
+pdev_mask_failed:
|
|
+ platform_device_unregister(pdev);
|
|
+pdev_register_failed:
|
|
+ bman_free_pool(dpa_bp->pool);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_bp_alloc);
|
|
+
|
|
+void dpa_bp_drain(struct dpa_bp *bp)
|
|
+{
|
|
+ int ret, num = 8;
|
|
+
|
|
+ do {
|
|
+ struct bm_buffer bmb[8];
|
|
+ int i;
|
|
+
|
|
+ ret = bman_acquire(bp->pool, bmb, num, 0);
|
|
+ if (ret < 0) {
|
|
+ if (num == 8) {
|
|
+ /* we have less than 8 buffers left;
|
|
+ * drain them one by one
|
|
+ */
|
|
+ num = 1;
|
|
+ ret = 1;
|
|
+ continue;
|
|
+ } else {
|
|
+ /* Pool is fully drained */
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < num; i++) {
|
|
+ dma_addr_t addr = bm_buf_addr(&bmb[i]);
|
|
+
|
|
+ dma_unmap_single(bp->dev, addr, bp->size,
|
|
+ DMA_BIDIRECTIONAL);
|
|
+
|
|
+ bp->free_buf_cb(phys_to_virt(addr));
|
|
+ }
|
|
+ } while (ret > 0);
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_bp_drain);
|
|
+
|
|
+static void __cold __attribute__((nonnull))
|
|
+_dpa_bp_free(struct dpa_bp *dpa_bp)
|
|
+{
|
|
+ struct dpa_bp *bp = dpa_bpid2pool(dpa_bp->bpid);
|
|
+
|
|
+ /* the mapping between bpid and dpa_bp is done very late in the
|
|
+ * allocation procedure; if something failed before the mapping, the bp
|
|
+ * was not configured, therefore we don't need the below instructions
|
|
+ */
|
|
+ if (!bp)
|
|
+ return;
|
|
+
|
|
+ if (!atomic_dec_and_test(&bp->refs))
|
|
+ return;
|
|
+
|
|
+ if (bp->free_buf_cb)
|
|
+ dpa_bp_drain(bp);
|
|
+
|
|
+ dpa_bp_array[bp->bpid] = NULL;
|
|
+ bman_free_pool(bp->pool);
|
|
+
|
|
+ if (bp->dev)
|
|
+ platform_device_unregister(to_platform_device(bp->dev));
|
|
+}
|
|
+
|
|
+void __cold __attribute__((nonnull))
|
|
+dpa_bp_free(struct dpa_priv_s *priv)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ if (priv->dpa_bp)
|
|
+ for (i = 0; i < priv->bp_count; i++)
|
|
+ _dpa_bp_free(&priv->dpa_bp[i]);
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_bp_free);
|
|
+
|
|
+struct dpa_bp *dpa_bpid2pool(int bpid)
|
|
+{
|
|
+ return dpa_bp_array[bpid];
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_bpid2pool);
|
|
+
|
|
+void dpa_bpid2pool_map(int bpid, struct dpa_bp *dpa_bp)
|
|
+{
|
|
+ dpa_bp_array[bpid] = dpa_bp;
|
|
+ atomic_set(&dpa_bp->refs, 1);
|
|
+}
|
|
+
|
|
+bool dpa_bpid2pool_use(int bpid)
|
|
+{
|
|
+ if (dpa_bpid2pool(bpid)) {
|
|
+ atomic_inc(&dpa_bp_array[bpid]->refs);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_USE_NDO_SELECT_QUEUE
|
|
+u16 dpa_select_queue(struct net_device *net_dev, struct sk_buff *skb,
|
|
+ struct net_device *sb_dev,
|
|
+ select_queue_fallback_t fallback)
|
|
+{
|
|
+ return dpa_get_queue_mapping(skb);
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_select_queue);
|
|
+#endif
|
|
+
|
|
+struct dpa_fq *dpa_fq_alloc(struct device *dev,
|
|
+ u32 fq_start,
|
|
+ u32 fq_count,
|
|
+ struct list_head *list,
|
|
+ enum dpa_fq_type fq_type)
|
|
+{
|
|
+ int i;
|
|
+ struct dpa_fq *dpa_fq;
|
|
+
|
|
+ dpa_fq = devm_kzalloc(dev, sizeof(*dpa_fq) * fq_count, GFP_KERNEL);
|
|
+ if (dpa_fq == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ for (i = 0; i < fq_count; i++) {
|
|
+ dpa_fq[i].fq_type = fq_type;
|
|
+ if (fq_type == FQ_TYPE_RX_PCD_HI_PRIO)
|
|
+ dpa_fq[i].fqid = fq_start ?
|
|
+ DPAA_ETH_FQ_DELTA + fq_start + i : 0;
|
|
+ else
|
|
+ dpa_fq[i].fqid = fq_start ? fq_start + i : 0;
|
|
+
|
|
+ list_add_tail(&dpa_fq[i].list, list);
|
|
+ }
|
|
+
|
|
+#ifdef CONFIG_FMAN_PFC
|
|
+ if (fq_type == FQ_TYPE_TX)
|
|
+ for (i = 0; i < fq_count; i++)
|
|
+ dpa_fq[i].wq = i / dpa_num_cpus;
|
|
+ else
|
|
+#endif
|
|
+ for (i = 0; i < fq_count; i++)
|
|
+ _dpa_assign_wq(dpa_fq + i);
|
|
+
|
|
+ return dpa_fq;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_fq_alloc);
|
|
+
|
|
+/* Probing of FQs for MACful ports */
|
|
+int dpa_fq_probe_mac(struct device *dev, struct list_head *list,
|
|
+ struct fm_port_fqs *port_fqs,
|
|
+ bool alloc_tx_conf_fqs,
|
|
+ enum port_type ptype)
|
|
+{
|
|
+ struct fqid_cell *fqids = NULL;
|
|
+ const void *fqids_off = NULL;
|
|
+ struct dpa_fq *dpa_fq = NULL;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ int num_ranges;
|
|
+ int i, lenp;
|
|
+
|
|
+ if (ptype == TX && alloc_tx_conf_fqs) {
|
|
+ if (!dpa_fq_alloc(dev, tx_confirm_fqids->start,
|
|
+ tx_confirm_fqids->count, list,
|
|
+ FQ_TYPE_TX_CONF_MQ))
|
|
+ goto fq_alloc_failed;
|
|
+ }
|
|
+
|
|
+ fqids_off = of_get_property(np, fsl_qman_frame_queues[ptype], &lenp);
|
|
+ if (fqids_off == NULL) {
|
|
+ /* No dts definition, so use the defaults. */
|
|
+ fqids = default_fqids[ptype];
|
|
+ num_ranges = 3;
|
|
+ } else {
|
|
+ num_ranges = lenp / sizeof(*fqids);
|
|
+
|
|
+ fqids = devm_kzalloc(dev, sizeof(*fqids) * num_ranges,
|
|
+ GFP_KERNEL);
|
|
+ if (fqids == NULL)
|
|
+ goto fqids_alloc_failed;
|
|
+
|
|
+ /* convert to CPU endianess */
|
|
+ for (i = 0; i < num_ranges; i++) {
|
|
+ fqids[i].start = be32_to_cpup(fqids_off +
|
|
+ i * sizeof(*fqids));
|
|
+ fqids[i].count = be32_to_cpup(fqids_off +
|
|
+ i * sizeof(*fqids) + sizeof(__be32));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < num_ranges; i++) {
|
|
+ switch (i) {
|
|
+ case 0:
|
|
+ /* The first queue is the error queue */
|
|
+ if (fqids[i].count != 1)
|
|
+ goto invalid_error_queue;
|
|
+
|
|
+ dpa_fq = dpa_fq_alloc(dev, fqids[i].start,
|
|
+ fqids[i].count, list,
|
|
+ ptype == RX ?
|
|
+ FQ_TYPE_RX_ERROR :
|
|
+ FQ_TYPE_TX_ERROR);
|
|
+ if (dpa_fq == NULL)
|
|
+ goto fq_alloc_failed;
|
|
+
|
|
+ if (ptype == RX)
|
|
+ port_fqs->rx_errq = &dpa_fq[0];
|
|
+ else
|
|
+ port_fqs->tx_errq = &dpa_fq[0];
|
|
+ break;
|
|
+ case 1:
|
|
+ /* the second queue is the default queue */
|
|
+ if (fqids[i].count != 1)
|
|
+ goto invalid_default_queue;
|
|
+
|
|
+ dpa_fq = dpa_fq_alloc(dev, fqids[i].start,
|
|
+ fqids[i].count, list,
|
|
+ ptype == RX ?
|
|
+ FQ_TYPE_RX_DEFAULT :
|
|
+ FQ_TYPE_TX_CONFIRM);
|
|
+ if (dpa_fq == NULL)
|
|
+ goto fq_alloc_failed;
|
|
+
|
|
+ if (ptype == RX)
|
|
+ port_fqs->rx_defq = &dpa_fq[0];
|
|
+ else
|
|
+ port_fqs->tx_defq = &dpa_fq[0];
|
|
+ break;
|
|
+ default:
|
|
+ /* all subsequent queues are either RX* PCD or Tx */
|
|
+ if (ptype == RX) {
|
|
+ if (!dpa_fq_alloc(dev, fqids[i].start,
|
|
+ fqids[i].count, list,
|
|
+ FQ_TYPE_RX_PCD) ||
|
|
+ !dpa_fq_alloc(dev, fqids[i].start,
|
|
+ fqids[i].count, list,
|
|
+ FQ_TYPE_RX_PCD_HI_PRIO))
|
|
+ goto fq_alloc_failed;
|
|
+ } else {
|
|
+ if (!dpa_fq_alloc(dev, fqids[i].start,
|
|
+ fqids[i].count, list,
|
|
+ FQ_TYPE_TX))
|
|
+ goto fq_alloc_failed;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fq_alloc_failed:
|
|
+fqids_alloc_failed:
|
|
+ dev_err(dev, "Cannot allocate memory for frame queues\n");
|
|
+ return -ENOMEM;
|
|
+
|
|
+invalid_default_queue:
|
|
+invalid_error_queue:
|
|
+ dev_err(dev, "Too many default or error queues\n");
|
|
+ return -EINVAL;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_fq_probe_mac);
|
|
+
|
|
+static u32 rx_pool_channel;
|
|
+static DEFINE_SPINLOCK(rx_pool_channel_init);
|
|
+
|
|
+int dpa_get_channel(void)
|
|
+{
|
|
+ spin_lock(&rx_pool_channel_init);
|
|
+ if (!rx_pool_channel) {
|
|
+ u32 pool;
|
|
+ int ret = qman_alloc_pool(&pool);
|
|
+ if (!ret)
|
|
+ rx_pool_channel = pool;
|
|
+ }
|
|
+ spin_unlock(&rx_pool_channel_init);
|
|
+ if (!rx_pool_channel)
|
|
+ return -ENOMEM;
|
|
+ return rx_pool_channel;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_get_channel);
|
|
+
|
|
+void dpa_release_channel(void)
|
|
+{
|
|
+ qman_release_pool(rx_pool_channel);
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_release_channel);
|
|
+
|
|
+void dpaa_eth_add_channel(u16 channel)
|
|
+{
|
|
+ const cpumask_t *cpus = qman_affine_cpus();
|
|
+ u32 pool = QM_SDQCR_CHANNELS_POOL_CONV(channel);
|
|
+ int cpu;
|
|
+ struct qman_portal *portal;
|
|
+
|
|
+ for_each_cpu(cpu, cpus) {
|
|
+ portal = (struct qman_portal *)qman_get_affine_portal(cpu);
|
|
+ qman_p_static_dequeue_add(portal, pool);
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL(dpaa_eth_add_channel);
|
|
+
|
|
+/**
|
|
+ * Congestion group state change notification callback.
|
|
+ * Stops the device's egress queues while they are congested and
|
|
+ * wakes them upon exiting congested state.
|
|
+ * Also updates some CGR-related stats.
|
|
+ */
|
|
+static void dpaa_eth_cgscn(struct qman_portal *qm, struct qman_cgr *cgr,
|
|
+
|
|
+ int congested)
|
|
+{
|
|
+ struct dpa_priv_s *priv = (struct dpa_priv_s *)container_of(cgr,
|
|
+ struct dpa_priv_s, cgr_data.cgr);
|
|
+
|
|
+ if (congested) {
|
|
+ priv->cgr_data.congestion_start_jiffies = jiffies;
|
|
+ netif_tx_stop_all_queues(priv->net_dev);
|
|
+ priv->cgr_data.cgr_congested_count++;
|
|
+ } else {
|
|
+ priv->cgr_data.congested_jiffies +=
|
|
+ (jiffies - priv->cgr_data.congestion_start_jiffies);
|
|
+ netif_tx_wake_all_queues(priv->net_dev);
|
|
+ }
|
|
+}
|
|
+
|
|
+int dpaa_eth_cgr_init(struct dpa_priv_s *priv)
|
|
+{
|
|
+ struct qm_mcc_initcgr initcgr;
|
|
+ u32 cs_th;
|
|
+ int err;
|
|
+
|
|
+ err = qman_alloc_cgrid(&priv->cgr_data.cgr.cgrid);
|
|
+ if (err < 0) {
|
|
+ pr_err("Error %d allocating CGR ID\n", err);
|
|
+ goto out_error;
|
|
+ }
|
|
+ priv->cgr_data.cgr.cb = dpaa_eth_cgscn;
|
|
+
|
|
+ /* Enable Congestion State Change Notifications and CS taildrop */
|
|
+ initcgr.we_mask = QM_CGR_WE_CSCN_EN | QM_CGR_WE_CS_THRES;
|
|
+ initcgr.cgr.cscn_en = QM_CGR_EN;
|
|
+
|
|
+ /* Set different thresholds based on the MAC speed.
|
|
+ * TODO: this may turn suboptimal if the MAC is reconfigured at a speed
|
|
+ * lower than its max, e.g. if a dTSEC later negotiates a 100Mbps link.
|
|
+ * In such cases, we ought to reconfigure the threshold, too.
|
|
+ */
|
|
+ if (priv->mac_dev->if_support & SUPPORTED_10000baseT_Full)
|
|
+ cs_th = CONFIG_FSL_DPAA_CS_THRESHOLD_10G;
|
|
+ else
|
|
+ cs_th = CONFIG_FSL_DPAA_CS_THRESHOLD_1G;
|
|
+ qm_cgr_cs_thres_set64(&initcgr.cgr.cs_thres, cs_th, 1);
|
|
+
|
|
+ initcgr.we_mask |= QM_CGR_WE_CSTD_EN;
|
|
+ initcgr.cgr.cstd_en = QM_CGR_EN;
|
|
+
|
|
+ err = qman_create_cgr(&priv->cgr_data.cgr, QMAN_CGR_FLAG_USE_INIT,
|
|
+ &initcgr);
|
|
+ if (err < 0) {
|
|
+ pr_err("Error %d creating CGR with ID %d\n", err,
|
|
+ priv->cgr_data.cgr.cgrid);
|
|
+ qman_release_cgrid(priv->cgr_data.cgr.cgrid);
|
|
+ goto out_error;
|
|
+ }
|
|
+ pr_debug("Created CGR %d for netdev with hwaddr %pM on QMan channel %d\n",
|
|
+ priv->cgr_data.cgr.cgrid, priv->mac_dev->addr,
|
|
+ priv->cgr_data.cgr.chan);
|
|
+
|
|
+out_error:
|
|
+ return err;
|
|
+}
|
|
+EXPORT_SYMBOL(dpaa_eth_cgr_init);
|
|
+
|
|
+static inline void dpa_setup_ingress(const struct dpa_priv_s *priv,
|
|
+ struct dpa_fq *fq,
|
|
+ const struct qman_fq *template)
|
|
+{
|
|
+ fq->fq_base = *template;
|
|
+ fq->net_dev = priv->net_dev;
|
|
+
|
|
+ fq->flags = QMAN_FQ_FLAG_NO_ENQUEUE;
|
|
+ fq->channel = priv->channel;
|
|
+}
|
|
+
|
|
+static inline void dpa_setup_egress(const struct dpa_priv_s *priv,
|
|
+ struct dpa_fq *fq,
|
|
+ struct fm_port *port,
|
|
+ const struct qman_fq *template)
|
|
+{
|
|
+ fq->fq_base = *template;
|
|
+ fq->net_dev = priv->net_dev;
|
|
+
|
|
+ if (port) {
|
|
+ fq->flags = QMAN_FQ_FLAG_TO_DCPORTAL;
|
|
+ fq->channel = (uint16_t)fm_get_tx_port_channel(port);
|
|
+ } else {
|
|
+ fq->flags = QMAN_FQ_FLAG_NO_MODIFY;
|
|
+ }
|
|
+}
|
|
+
|
|
+void dpa_fq_setup(struct dpa_priv_s *priv, const struct dpa_fq_cbs_t *fq_cbs,
|
|
+ struct fm_port *tx_port)
|
|
+{
|
|
+ struct dpa_fq *fq;
|
|
+ uint16_t portals[NR_CPUS];
|
|
+ int cpu, portal_cnt = 0, num_portals = 0;
|
|
+ uint32_t pcd_fqid, pcd_fqid_hi_prio;
|
|
+ const cpumask_t *affine_cpus = qman_affine_cpus();
|
|
+ int egress_cnt = 0, conf_cnt = 0;
|
|
+
|
|
+ /* Prepare for PCD FQs init */
|
|
+ for_each_cpu(cpu, affine_cpus)
|
|
+ portals[num_portals++] = qman_affine_channel(cpu);
|
|
+ if (num_portals == 0)
|
|
+ dev_err(priv->net_dev->dev.parent,
|
|
+ "No Qman software (affine) channels found");
|
|
+
|
|
+ pcd_fqid = (priv->mac_dev) ?
|
|
+ DPAA_ETH_PCD_FQ_BASE(priv->mac_dev->res->start) : 0;
|
|
+ pcd_fqid_hi_prio = (priv->mac_dev) ?
|
|
+ DPAA_ETH_PCD_FQ_HI_PRIO_BASE(priv->mac_dev->res->start) : 0;
|
|
+
|
|
+ /* Initialize each FQ in the list */
|
|
+ list_for_each_entry(fq, &priv->dpa_fq_list, list) {
|
|
+ switch (fq->fq_type) {
|
|
+ case FQ_TYPE_RX_DEFAULT:
|
|
+ BUG_ON(!priv->mac_dev);
|
|
+ dpa_setup_ingress(priv, fq, &fq_cbs->rx_defq);
|
|
+ break;
|
|
+ case FQ_TYPE_RX_ERROR:
|
|
+ BUG_ON(!priv->mac_dev);
|
|
+ dpa_setup_ingress(priv, fq, &fq_cbs->rx_errq);
|
|
+ break;
|
|
+ case FQ_TYPE_RX_PCD:
|
|
+ /* For MACless we can't have dynamic Rx queues */
|
|
+ BUG_ON(!priv->mac_dev && !fq->fqid);
|
|
+ dpa_setup_ingress(priv, fq, &fq_cbs->rx_defq);
|
|
+ if (!fq->fqid)
|
|
+ fq->fqid = pcd_fqid++;
|
|
+ fq->channel = portals[portal_cnt];
|
|
+ portal_cnt = (portal_cnt + 1) % num_portals;
|
|
+ break;
|
|
+ case FQ_TYPE_RX_PCD_HI_PRIO:
|
|
+ /* For MACless we can't have dynamic Hi Pri Rx queues */
|
|
+ BUG_ON(!priv->mac_dev && !fq->fqid);
|
|
+ dpa_setup_ingress(priv, fq, &fq_cbs->rx_defq);
|
|
+ if (!fq->fqid)
|
|
+ fq->fqid = pcd_fqid_hi_prio++;
|
|
+ fq->channel = portals[portal_cnt];
|
|
+ portal_cnt = (portal_cnt + 1) % num_portals;
|
|
+ break;
|
|
+ case FQ_TYPE_TX:
|
|
+ dpa_setup_egress(priv, fq, tx_port,
|
|
+ &fq_cbs->egress_ern);
|
|
+ /* If we have more Tx queues than the number of cores,
|
|
+ * just ignore the extra ones.
|
|
+ */
|
|
+ if (egress_cnt < DPAA_ETH_TX_QUEUES)
|
|
+ priv->egress_fqs[egress_cnt++] = &fq->fq_base;
|
|
+ break;
|
|
+ case FQ_TYPE_TX_CONFIRM:
|
|
+ BUG_ON(!priv->mac_dev);
|
|
+ dpa_setup_ingress(priv, fq, &fq_cbs->tx_defq);
|
|
+ break;
|
|
+ case FQ_TYPE_TX_CONF_MQ:
|
|
+ BUG_ON(!priv->mac_dev);
|
|
+ dpa_setup_ingress(priv, fq, &fq_cbs->tx_defq);
|
|
+ priv->conf_fqs[conf_cnt++] = &fq->fq_base;
|
|
+ break;
|
|
+ case FQ_TYPE_TX_ERROR:
|
|
+ BUG_ON(!priv->mac_dev);
|
|
+ dpa_setup_ingress(priv, fq, &fq_cbs->tx_errq);
|
|
+ break;
|
|
+ default:
|
|
+ dev_warn(priv->net_dev->dev.parent,
|
|
+ "Unknown FQ type detected!\n");
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* The number of Tx queues may be smaller than the number of cores, if
|
|
+ * the Tx queue range is specified in the device tree instead of being
|
|
+ * dynamically allocated.
|
|
+ * Make sure all CPUs receive a corresponding Tx queue.
|
|
+ */
|
|
+ while (egress_cnt < DPAA_ETH_TX_QUEUES) {
|
|
+ list_for_each_entry(fq, &priv->dpa_fq_list, list) {
|
|
+ if (fq->fq_type != FQ_TYPE_TX)
|
|
+ continue;
|
|
+ priv->egress_fqs[egress_cnt++] = &fq->fq_base;
|
|
+ if (egress_cnt == DPAA_ETH_TX_QUEUES)
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_fq_setup);
|
|
+
|
|
+int dpa_fq_init(struct dpa_fq *dpa_fq, bool td_enable)
|
|
+{
|
|
+ int _errno;
|
|
+ const struct dpa_priv_s *priv;
|
|
+ struct device *dev;
|
|
+ struct qman_fq *fq;
|
|
+ struct qm_mcc_initfq initfq;
|
|
+ struct qman_fq *confq;
|
|
+ int queue_id;
|
|
+
|
|
+ priv = netdev_priv(dpa_fq->net_dev);
|
|
+ dev = dpa_fq->net_dev->dev.parent;
|
|
+
|
|
+ if (dpa_fq->fqid == 0)
|
|
+ dpa_fq->flags |= QMAN_FQ_FLAG_DYNAMIC_FQID;
|
|
+
|
|
+ dpa_fq->init = !(dpa_fq->flags & QMAN_FQ_FLAG_NO_MODIFY);
|
|
+
|
|
+ _errno = qman_create_fq(dpa_fq->fqid, dpa_fq->flags, &dpa_fq->fq_base);
|
|
+ if (_errno) {
|
|
+ dev_err(dev, "qman_create_fq() failed\n");
|
|
+ return _errno;
|
|
+ }
|
|
+ fq = &dpa_fq->fq_base;
|
|
+
|
|
+ if (dpa_fq->init) {
|
|
+ memset(&initfq, 0, sizeof(initfq));
|
|
+
|
|
+ initfq.we_mask = QM_INITFQ_WE_FQCTRL;
|
|
+ /* FIXME: why would we want to keep an empty FQ in cache? */
|
|
+ initfq.fqd.fq_ctrl = QM_FQCTRL_PREFERINCACHE;
|
|
+
|
|
+ /* Try to reduce the number of portal interrupts for
|
|
+ * Tx Confirmation FQs.
|
|
+ */
|
|
+ if (dpa_fq->fq_type == FQ_TYPE_TX_CONFIRM)
|
|
+ initfq.fqd.fq_ctrl |= QM_FQCTRL_HOLDACTIVE;
|
|
+
|
|
+ /* FQ placement */
|
|
+ initfq.we_mask |= QM_INITFQ_WE_DESTWQ;
|
|
+
|
|
+ initfq.fqd.dest.channel = dpa_fq->channel;
|
|
+ initfq.fqd.dest.wq = dpa_fq->wq;
|
|
+
|
|
+ /* Put all egress queues in a congestion group of their own.
|
|
+ * Sensu stricto, the Tx confirmation queues are Rx FQs,
|
|
+ * rather than Tx - but they nonetheless account for the
|
|
+ * memory footprint on behalf of egress traffic. We therefore
|
|
+ * place them in the netdev's CGR, along with the Tx FQs.
|
|
+ */
|
|
+ if (dpa_fq->fq_type == FQ_TYPE_TX ||
|
|
+ dpa_fq->fq_type == FQ_TYPE_TX_CONFIRM ||
|
|
+ dpa_fq->fq_type == FQ_TYPE_TX_CONF_MQ) {
|
|
+ initfq.we_mask |= QM_INITFQ_WE_CGID;
|
|
+ initfq.fqd.fq_ctrl |= QM_FQCTRL_CGE;
|
|
+ initfq.fqd.cgid = (uint8_t)priv->cgr_data.cgr.cgrid;
|
|
+ /* Set a fixed overhead accounting, in an attempt to
|
|
+ * reduce the impact of fixed-size skb shells and the
|
|
+ * driver's needed headroom on system memory. This is
|
|
+ * especially the case when the egress traffic is
|
|
+ * composed of small datagrams.
|
|
+ * Unfortunately, QMan's OAL value is capped to an
|
|
+ * insufficient value, but even that is better than
|
|
+ * no overhead accounting at all.
|
|
+ */
|
|
+ initfq.we_mask |= QM_INITFQ_WE_OAC;
|
|
+ initfq.fqd.oac_init.oac = QM_OAC_CG;
|
|
+ initfq.fqd.oac_init.oal =
|
|
+ (signed char)(min(sizeof(struct sk_buff) +
|
|
+ priv->tx_headroom, (size_t)FSL_QMAN_MAX_OAL));
|
|
+ }
|
|
+
|
|
+ if (td_enable) {
|
|
+ initfq.we_mask |= QM_INITFQ_WE_TDTHRESH;
|
|
+ qm_fqd_taildrop_set(&initfq.fqd.td,
|
|
+ DPA_FQ_TD, 1);
|
|
+ initfq.fqd.fq_ctrl = QM_FQCTRL_TDE;
|
|
+ }
|
|
+
|
|
+ /* Configure the Tx confirmation queue, now that we know
|
|
+ * which Tx queue it pairs with.
|
|
+ */
|
|
+ if (dpa_fq->fq_type == FQ_TYPE_TX) {
|
|
+ queue_id = _dpa_tx_fq_to_id(priv, &dpa_fq->fq_base);
|
|
+ if (queue_id >= 0) {
|
|
+ confq = priv->conf_fqs[queue_id];
|
|
+ if (confq) {
|
|
+ initfq.we_mask |= QM_INITFQ_WE_CONTEXTA;
|
|
+ /* ContextA: OVOM=1 (use contextA2 bits instead of ICAD)
|
|
+ * A2V=1 (contextA A2 field is valid)
|
|
+ * A0V=1 (contextA A0 field is valid)
|
|
+ * B0V=1 (contextB field is valid)
|
|
+ * ContextA A2: EBD=1 (deallocate buffers inside FMan)
|
|
+ * ContextB B0(ASPID): 0 (absolute Virtual Storage ID)
|
|
+ */
|
|
+ initfq.fqd.context_a.hi = 0x1e000000;
|
|
+ initfq.fqd.context_a.lo = 0x80000000;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Put all *private* ingress queues in our "ingress CGR". */
|
|
+ if (priv->use_ingress_cgr &&
|
|
+ (dpa_fq->fq_type == FQ_TYPE_RX_DEFAULT ||
|
|
+ dpa_fq->fq_type == FQ_TYPE_RX_ERROR ||
|
|
+ dpa_fq->fq_type == FQ_TYPE_RX_PCD ||
|
|
+ dpa_fq->fq_type == FQ_TYPE_RX_PCD_HI_PRIO)) {
|
|
+ initfq.we_mask |= QM_INITFQ_WE_CGID;
|
|
+ initfq.fqd.fq_ctrl |= QM_FQCTRL_CGE;
|
|
+ initfq.fqd.cgid = (uint8_t)priv->ingress_cgr.cgrid;
|
|
+ /* Set a fixed overhead accounting, just like for the
|
|
+ * egress CGR.
|
|
+ */
|
|
+ initfq.we_mask |= QM_INITFQ_WE_OAC;
|
|
+ initfq.fqd.oac_init.oac = QM_OAC_CG;
|
|
+ initfq.fqd.oac_init.oal =
|
|
+ (signed char)(min(sizeof(struct sk_buff) +
|
|
+ priv->tx_headroom, (size_t)FSL_QMAN_MAX_OAL));
|
|
+ }
|
|
+
|
|
+ /* Initialization common to all ingress queues */
|
|
+ if (dpa_fq->flags & QMAN_FQ_FLAG_NO_ENQUEUE) {
|
|
+ initfq.we_mask |= QM_INITFQ_WE_CONTEXTA;
|
|
+ initfq.fqd.fq_ctrl |=
|
|
+ QM_FQCTRL_CTXASTASHING | QM_FQCTRL_AVOIDBLOCK;
|
|
+ initfq.fqd.context_a.stashing.exclusive =
|
|
+ QM_STASHING_EXCL_DATA | QM_STASHING_EXCL_CTX |
|
|
+ QM_STASHING_EXCL_ANNOTATION;
|
|
+ initfq.fqd.context_a.stashing.data_cl = 2;
|
|
+ initfq.fqd.context_a.stashing.annotation_cl = 1;
|
|
+ initfq.fqd.context_a.stashing.context_cl =
|
|
+ DIV_ROUND_UP(sizeof(struct qman_fq), 64);
|
|
+ }
|
|
+
|
|
+ _errno = qman_init_fq(fq, QMAN_INITFQ_FLAG_SCHED, &initfq);
|
|
+ if (_errno < 0) {
|
|
+ if (DPA_RX_PCD_HI_PRIO_FQ_INIT_FAIL(dpa_fq, _errno)) {
|
|
+ dpa_fq->init = 0;
|
|
+ } else {
|
|
+ dev_err(dev, "qman_init_fq(%u) = %d\n",
|
|
+ qman_fq_fqid(fq), _errno);
|
|
+ qman_destroy_fq(fq, 0);
|
|
+ }
|
|
+ return _errno;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dpa_fq->fqid = qman_fq_fqid(fq);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_fq_init);
|
|
+
|
|
+int __cold __attribute__((nonnull))
|
|
+_dpa_fq_free(struct device *dev, struct qman_fq *fq)
|
|
+{
|
|
+ int _errno, __errno;
|
|
+ struct dpa_fq *dpa_fq;
|
|
+ const struct dpa_priv_s *priv;
|
|
+
|
|
+ _errno = 0;
|
|
+
|
|
+ dpa_fq = container_of(fq, struct dpa_fq, fq_base);
|
|
+ priv = netdev_priv(dpa_fq->net_dev);
|
|
+
|
|
+ if (dpa_fq->init) {
|
|
+ _errno = qman_retire_fq(fq, NULL);
|
|
+ if (unlikely(_errno < 0) && netif_msg_drv(priv))
|
|
+ dev_err(dev, "qman_retire_fq(%u) = %d\n",
|
|
+ qman_fq_fqid(fq), _errno);
|
|
+
|
|
+ __errno = qman_oos_fq(fq);
|
|
+ if (unlikely(__errno < 0) && netif_msg_drv(priv)) {
|
|
+ dev_err(dev, "qman_oos_fq(%u) = %d\n",
|
|
+ qman_fq_fqid(fq), __errno);
|
|
+ if (_errno >= 0)
|
|
+ _errno = __errno;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ qman_destroy_fq(fq, 0);
|
|
+ list_del(&dpa_fq->list);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+EXPORT_SYMBOL(_dpa_fq_free);
|
|
+
|
|
+int __cold __attribute__((nonnull))
|
|
+dpa_fq_free(struct device *dev, struct list_head *list)
|
|
+{
|
|
+ int _errno, __errno;
|
|
+ struct dpa_fq *dpa_fq, *tmp;
|
|
+
|
|
+ _errno = 0;
|
|
+ list_for_each_entry_safe(dpa_fq, tmp, list, list) {
|
|
+ __errno = _dpa_fq_free(dev, (struct qman_fq *)dpa_fq);
|
|
+ if (unlikely(__errno < 0) && _errno >= 0)
|
|
+ _errno = __errno;
|
|
+ }
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_fq_free);
|
|
+
|
|
+int dpa_fqs_init(struct device *dev, struct list_head *list, bool td_enable)
|
|
+{
|
|
+ int _errno, __errno;
|
|
+ struct dpa_fq *dpa_fq, *tmp;
|
|
+ static bool print_msg __read_mostly;
|
|
+
|
|
+ _errno = 0;
|
|
+ print_msg = true;
|
|
+ list_for_each_entry_safe(dpa_fq, tmp, list, list) {
|
|
+ __errno = dpa_fq_init(dpa_fq, td_enable);
|
|
+ if (unlikely(__errno < 0) && _errno >= 0) {
|
|
+ if (DPA_RX_PCD_HI_PRIO_FQ_INIT_FAIL(dpa_fq, __errno)) {
|
|
+ if (print_msg) {
|
|
+ dev_warn(dev,
|
|
+ "Skip RX PCD High Priority FQs initialization\n");
|
|
+ print_msg = false;
|
|
+ }
|
|
+ if (_dpa_fq_free(dev, (struct qman_fq *)dpa_fq))
|
|
+ dev_warn(dev,
|
|
+ "Error freeing frame queues\n");
|
|
+ } else {
|
|
+ _errno = __errno;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_fqs_init);
|
|
+static void
|
|
+dpaa_eth_init_tx_port(struct fm_port *port, struct dpa_fq *errq,
|
|
+ struct dpa_fq *defq, struct dpa_buffer_layout_s *buf_layout)
|
|
+{
|
|
+ struct fm_port_params tx_port_param;
|
|
+ bool frag_enabled = false;
|
|
+
|
|
+ memset(&tx_port_param, 0, sizeof(tx_port_param));
|
|
+ dpaa_eth_init_port(tx, port, tx_port_param, errq->fqid, defq->fqid,
|
|
+ buf_layout, frag_enabled);
|
|
+}
|
|
+
|
|
+static void
|
|
+dpaa_eth_init_rx_port(struct fm_port *port, struct dpa_bp *bp, size_t count,
|
|
+ struct dpa_fq *errq, struct dpa_fq *defq,
|
|
+ struct dpa_buffer_layout_s *buf_layout)
|
|
+{
|
|
+ struct fm_port_params rx_port_param;
|
|
+ int i;
|
|
+ bool frag_enabled = false;
|
|
+
|
|
+ memset(&rx_port_param, 0, sizeof(rx_port_param));
|
|
+ count = min(ARRAY_SIZE(rx_port_param.pool_param), count);
|
|
+ rx_port_param.num_pools = (uint8_t)count;
|
|
+ for (i = 0; i < count; i++) {
|
|
+ if (i >= rx_port_param.num_pools)
|
|
+ break;
|
|
+ rx_port_param.pool_param[i].id = bp[i].bpid;
|
|
+ rx_port_param.pool_param[i].size = (uint16_t)bp[i].size;
|
|
+ }
|
|
+
|
|
+ dpaa_eth_init_port(rx, port, rx_port_param, errq->fqid, defq->fqid,
|
|
+ buf_layout, frag_enabled);
|
|
+}
|
|
+
|
|
+#if defined(CONFIG_FSL_SDK_FMAN_TEST)
|
|
+/* Defined as weak, to be implemented by fman pcd tester. */
|
|
+int dpa_alloc_pcd_fqids(struct device *, uint32_t, uint8_t, uint32_t *)
|
|
+__attribute__((weak));
|
|
+
|
|
+int dpa_free_pcd_fqids(struct device *, uint32_t) __attribute__((weak));
|
|
+#else
|
|
+int dpa_alloc_pcd_fqids(struct device *, uint32_t, uint8_t, uint32_t *);
|
|
+
|
|
+int dpa_free_pcd_fqids(struct device *, uint32_t);
|
|
+
|
|
+#endif /* CONFIG_FSL_SDK_FMAN_TEST */
|
|
+
|
|
+
|
|
+int dpa_alloc_pcd_fqids(struct device *dev, uint32_t num,
|
|
+ uint8_t alignment, uint32_t *base_fqid)
|
|
+{
|
|
+ dev_crit(dev, "callback not implemented!\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int dpa_free_pcd_fqids(struct device *dev, uint32_t base_fqid)
|
|
+{
|
|
+
|
|
+ dev_crit(dev, "callback not implemented!\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void dpaa_eth_init_ports(struct mac_device *mac_dev,
|
|
+ struct dpa_bp *bp, size_t count,
|
|
+ struct fm_port_fqs *port_fqs,
|
|
+ struct dpa_buffer_layout_s *buf_layout,
|
|
+ struct device *dev)
|
|
+{
|
|
+ struct fm_port_pcd_param rx_port_pcd_param;
|
|
+ struct fm_port *rxport = mac_dev->port_dev[RX];
|
|
+ struct fm_port *txport = mac_dev->port_dev[TX];
|
|
+
|
|
+ dpaa_eth_init_tx_port(txport, port_fqs->tx_errq,
|
|
+ port_fqs->tx_defq, &buf_layout[TX]);
|
|
+ dpaa_eth_init_rx_port(rxport, bp, count, port_fqs->rx_errq,
|
|
+ port_fqs->rx_defq, &buf_layout[RX]);
|
|
+
|
|
+ rx_port_pcd_param.cba = dpa_alloc_pcd_fqids;
|
|
+ rx_port_pcd_param.cbf = dpa_free_pcd_fqids;
|
|
+ rx_port_pcd_param.dev = dev;
|
|
+ fm_port_pcd_bind(rxport, &rx_port_pcd_param);
|
|
+}
|
|
+EXPORT_SYMBOL(dpaa_eth_init_ports);
|
|
+
|
|
+void dpa_release_sgt(struct qm_sg_entry *sgt)
|
|
+{
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ struct bm_buffer bmb[DPA_BUFF_RELEASE_MAX];
|
|
+ uint8_t i = 0, j;
|
|
+
|
|
+ memset(bmb, 0, DPA_BUFF_RELEASE_MAX * sizeof(struct bm_buffer));
|
|
+
|
|
+ do {
|
|
+ dpa_bp = dpa_bpid2pool(qm_sg_entry_get_bpid(&sgt[i]));
|
|
+ DPA_BUG_ON(!dpa_bp);
|
|
+
|
|
+ j = 0;
|
|
+ do {
|
|
+ DPA_BUG_ON(qm_sg_entry_get_ext(&sgt[i]));
|
|
+ bm_buffer_set64(&bmb[j], qm_sg_addr(&sgt[i]));
|
|
+
|
|
+ j++; i++;
|
|
+ } while (j < ARRAY_SIZE(bmb) &&
|
|
+ !qm_sg_entry_get_final(&sgt[i-1]) &&
|
|
+ qm_sg_entry_get_bpid(&sgt[i-1]) ==
|
|
+ qm_sg_entry_get_bpid(&sgt[i]));
|
|
+
|
|
+ while (bman_release(dpa_bp->pool, bmb, j, 0))
|
|
+ cpu_relax();
|
|
+ } while (!qm_sg_entry_get_final(&sgt[i-1]));
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_release_sgt);
|
|
+
|
|
+void __attribute__((nonnull))
|
|
+dpa_fd_release(const struct net_device *net_dev, const struct qm_fd *fd)
|
|
+{
|
|
+ struct qm_sg_entry *sgt;
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ struct bm_buffer bmb;
|
|
+ dma_addr_t addr;
|
|
+ void *vaddr;
|
|
+
|
|
+ bmb.opaque = 0;
|
|
+ bm_buffer_set64(&bmb, qm_fd_addr(fd));
|
|
+
|
|
+ dpa_bp = dpa_bpid2pool(fd->bpid);
|
|
+ DPA_BUG_ON(!dpa_bp);
|
|
+
|
|
+ if (fd->format == qm_fd_sg) {
|
|
+ vaddr = phys_to_virt(qm_fd_addr(fd));
|
|
+ sgt = vaddr + dpa_fd_offset(fd);
|
|
+
|
|
+ dma_unmap_single(dpa_bp->dev, qm_fd_addr(fd), dpa_bp->size,
|
|
+ DMA_BIDIRECTIONAL);
|
|
+
|
|
+ dpa_release_sgt(sgt);
|
|
+ addr = dma_map_single(dpa_bp->dev, vaddr, dpa_bp->size,
|
|
+ DMA_BIDIRECTIONAL);
|
|
+ if (unlikely(dma_mapping_error(dpa_bp->dev, addr))) {
|
|
+ dev_err(dpa_bp->dev, "DMA mapping failed");
|
|
+ return;
|
|
+ }
|
|
+ bm_buffer_set64(&bmb, addr);
|
|
+ }
|
|
+
|
|
+ while (bman_release(dpa_bp->pool, &bmb, 1, 0))
|
|
+ cpu_relax();
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_fd_release);
|
|
+
|
|
+void count_ern(struct dpa_percpu_priv_s *percpu_priv,
|
|
+ const struct qm_mr_entry *msg)
|
|
+{
|
|
+ switch (msg->ern.rc & QM_MR_RC_MASK) {
|
|
+ case QM_MR_RC_CGR_TAILDROP:
|
|
+ percpu_priv->ern_cnt.cg_tdrop++;
|
|
+ break;
|
|
+ case QM_MR_RC_WRED:
|
|
+ percpu_priv->ern_cnt.wred++;
|
|
+ break;
|
|
+ case QM_MR_RC_ERROR:
|
|
+ percpu_priv->ern_cnt.err_cond++;
|
|
+ break;
|
|
+ case QM_MR_RC_ORPWINDOW_EARLY:
|
|
+ percpu_priv->ern_cnt.early_window++;
|
|
+ break;
|
|
+ case QM_MR_RC_ORPWINDOW_LATE:
|
|
+ percpu_priv->ern_cnt.late_window++;
|
|
+ break;
|
|
+ case QM_MR_RC_FQ_TAILDROP:
|
|
+ percpu_priv->ern_cnt.fq_tdrop++;
|
|
+ break;
|
|
+ case QM_MR_RC_ORPWINDOW_RETIRED:
|
|
+ percpu_priv->ern_cnt.fq_retired++;
|
|
+ break;
|
|
+ case QM_MR_RC_ORP_ZERO:
|
|
+ percpu_priv->ern_cnt.orp_zero++;
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL(count_ern);
|
|
+
|
|
+/**
|
|
+ * Turn on HW checksum computation for this outgoing frame.
|
|
+ * If the current protocol is not something we support in this regard
|
|
+ * (or if the stack has already computed the SW checksum), we do nothing.
|
|
+ *
|
|
+ * Returns 0 if all goes well (or HW csum doesn't apply), and a negative value
|
|
+ * otherwise.
|
|
+ *
|
|
+ * Note that this function may modify the fd->cmd field and the skb data buffer
|
|
+ * (the Parse Results area).
|
|
+ */
|
|
+int dpa_enable_tx_csum(struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, struct qm_fd *fd, char *parse_results)
|
|
+{
|
|
+ fm_prs_result_t *parse_result;
|
|
+ struct iphdr *iph;
|
|
+ struct ipv6hdr *ipv6h = NULL;
|
|
+ u8 l4_proto;
|
|
+ u16 ethertype = ntohs(skb->protocol);
|
|
+ int retval = 0;
|
|
+
|
|
+ if (skb->ip_summed != CHECKSUM_PARTIAL)
|
|
+ return 0;
|
|
+
|
|
+ /* Note: L3 csum seems to be already computed in sw, but we can't choose
|
|
+ * L4 alone from the FM configuration anyway.
|
|
+ */
|
|
+
|
|
+ /* Fill in some fields of the Parse Results array, so the FMan
|
|
+ * can find them as if they came from the FMan Parser.
|
|
+ */
|
|
+ parse_result = (fm_prs_result_t *)parse_results;
|
|
+
|
|
+ /* If we're dealing with VLAN, get the real Ethernet type */
|
|
+ if (ethertype == ETH_P_8021Q) {
|
|
+ /* We can't always assume the MAC header is set correctly
|
|
+ * by the stack, so reset to beginning of skb->data
|
|
+ */
|
|
+ skb_reset_mac_header(skb);
|
|
+ ethertype = ntohs(vlan_eth_hdr(skb)->h_vlan_encapsulated_proto);
|
|
+ }
|
|
+
|
|
+ /* Fill in the relevant L3 parse result fields
|
|
+ * and read the L4 protocol type
|
|
+ */
|
|
+ switch (ethertype) {
|
|
+ case ETH_P_IP:
|
|
+ parse_result->l3r = cpu_to_be16(FM_L3_PARSE_RESULT_IPV4);
|
|
+ iph = ip_hdr(skb);
|
|
+ DPA_BUG_ON(iph == NULL);
|
|
+ l4_proto = iph->protocol;
|
|
+ break;
|
|
+ case ETH_P_IPV6:
|
|
+ parse_result->l3r = cpu_to_be16(FM_L3_PARSE_RESULT_IPV6);
|
|
+ ipv6h = ipv6_hdr(skb);
|
|
+ DPA_BUG_ON(ipv6h == NULL);
|
|
+ l4_proto = ipv6h->nexthdr;
|
|
+ break;
|
|
+ default:
|
|
+ /* We shouldn't even be here */
|
|
+ if (netif_msg_tx_err(priv) && net_ratelimit())
|
|
+ netdev_alert(priv->net_dev,
|
|
+ "Can't compute HW csum for L3 proto 0x%x\n",
|
|
+ ntohs(skb->protocol));
|
|
+ retval = -EIO;
|
|
+ goto return_error;
|
|
+ }
|
|
+
|
|
+ /* Fill in the relevant L4 parse result fields */
|
|
+ switch (l4_proto) {
|
|
+ case IPPROTO_UDP:
|
|
+ parse_result->l4r = FM_L4_PARSE_RESULT_UDP;
|
|
+ break;
|
|
+ case IPPROTO_TCP:
|
|
+ parse_result->l4r = FM_L4_PARSE_RESULT_TCP;
|
|
+ break;
|
|
+ default:
|
|
+ /* This can as well be a BUG() */
|
|
+ if (netif_msg_tx_err(priv) && net_ratelimit())
|
|
+ netdev_alert(priv->net_dev,
|
|
+ "Can't compute HW csum for L4 proto 0x%x\n",
|
|
+ l4_proto);
|
|
+ retval = -EIO;
|
|
+ goto return_error;
|
|
+ }
|
|
+
|
|
+ /* At index 0 is IPOffset_1 as defined in the Parse Results */
|
|
+ parse_result->ip_off[0] = (uint8_t)skb_network_offset(skb);
|
|
+ parse_result->l4_off = (uint8_t)skb_transport_offset(skb);
|
|
+
|
|
+ /* Enable L3 (and L4, if TCP or UDP) HW checksum. */
|
|
+ fd->cmd |= FM_FD_CMD_RPD | FM_FD_CMD_DTC;
|
|
+
|
|
+ /* On P1023 and similar platforms fd->cmd interpretation could
|
|
+ * be disabled by setting CONTEXT_A bit ICMD; currently this bit
|
|
+ * is not set so we do not need to check; in the future, if/when
|
|
+ * using context_a we need to check this bit
|
|
+ */
|
|
+
|
|
+return_error:
|
|
+ return retval;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_enable_tx_csum);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_CEETM
|
|
+void dpa_enable_ceetm(struct net_device *dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(dev);
|
|
+ priv->ceetm_en = true;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_enable_ceetm);
|
|
+
|
|
+void dpa_disable_ceetm(struct net_device *dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(dev);
|
|
+ priv->ceetm_en = false;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_disable_ceetm);
|
|
+#endif
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_common.h
|
|
@@ -0,0 +1,226 @@
|
|
+/* Copyright 2008-2013 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifndef __DPAA_ETH_COMMON_H
|
|
+#define __DPAA_ETH_COMMON_H
|
|
+
|
|
+#include <linux/etherdevice.h> /* struct net_device */
|
|
+#include <linux/fsl_bman.h> /* struct bm_buffer */
|
|
+#include <linux/of_platform.h> /* struct platform_device */
|
|
+#include <linux/net_tstamp.h> /* struct hwtstamp_config */
|
|
+
|
|
+#include "dpaa_eth.h"
|
|
+#include "lnxwrp_fsl_fman.h"
|
|
+
|
|
+#define dpaa_eth_init_port(type, port, param, errq_id, defq_id, buf_layout,\
|
|
+ frag_enabled) \
|
|
+{ \
|
|
+ param.errq = errq_id; \
|
|
+ param.defq = defq_id; \
|
|
+ param.priv_data_size = buf_layout->priv_data_size; \
|
|
+ param.parse_results = buf_layout->parse_results; \
|
|
+ param.hash_results = buf_layout->hash_results; \
|
|
+ param.frag_enable = frag_enabled; \
|
|
+ param.time_stamp = buf_layout->time_stamp; \
|
|
+ param.manip_extra_space = buf_layout->manip_extra_space; \
|
|
+ param.data_align = buf_layout->data_align; \
|
|
+ fm_set_##type##_port_params(port, ¶m); \
|
|
+}
|
|
+
|
|
+#define DPA_SGT_MAX_ENTRIES 16 /* maximum number of entries in SG Table */
|
|
+
|
|
+#define DPA_SGT_ENTRIES_THRESHOLD DPA_SGT_MAX_ENTRIES
|
|
+
|
|
+#define DPA_BUFF_RELEASE_MAX 8 /* maximum number of buffers released at once */
|
|
+
|
|
+#define DPA_RX_PCD_HI_PRIO_FQ_INIT_FAIL(dpa_fq, _errno) \
|
|
+ (((dpa_fq)->fq_type == FQ_TYPE_RX_PCD_HI_PRIO) && \
|
|
+ (_errno == -EIO))
|
|
+/* return codes for the dpaa-eth hooks */
|
|
+enum dpaa_eth_hook_result {
|
|
+ /* fd/skb was retained by the hook.
|
|
+ *
|
|
+ * On the Rx path, this means the Ethernet driver will _not_
|
|
+ * deliver the skb to the stack. Instead, the hook implementation
|
|
+ * is expected to properly dispose of the skb.
|
|
+ *
|
|
+ * On the Tx path, the Ethernet driver's dpa_tx() function will
|
|
+ * immediately return NETDEV_TX_OK. The hook implementation is expected
|
|
+ * to free the skb. *DO*NOT* release it to BMan, or enqueue it to FMan,
|
|
+ * unless you know exactly what you're doing!
|
|
+ *
|
|
+ * On the confirmation/error paths, the Ethernet driver will _not_
|
|
+ * perform any fd cleanup, nor update the interface statistics.
|
|
+ */
|
|
+ DPAA_ETH_STOLEN,
|
|
+ /* fd/skb was returned to the Ethernet driver for regular processing.
|
|
+ * The hook is not allowed to, for instance, reallocate the skb (as if
|
|
+ * by linearizing, copying, cloning or reallocating the headroom).
|
|
+ */
|
|
+ DPAA_ETH_CONTINUE
|
|
+};
|
|
+
|
|
+typedef enum dpaa_eth_hook_result (*dpaa_eth_ingress_hook_t)(
|
|
+ struct sk_buff *skb, struct net_device *net_dev, u32 fqid);
|
|
+typedef enum dpaa_eth_hook_result (*dpaa_eth_egress_hook_t)(
|
|
+ struct sk_buff *skb, struct net_device *net_dev);
|
|
+typedef enum dpaa_eth_hook_result (*dpaa_eth_confirm_hook_t)(
|
|
+ struct net_device *net_dev, const struct qm_fd *fd, u32 fqid);
|
|
+
|
|
+/* used in napi related functions */
|
|
+extern u16 qman_portal_max;
|
|
+
|
|
+/* from dpa_ethtool.c */
|
|
+extern const struct ethtool_ops dpa_ethtool_ops;
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_HOOKS
|
|
+/* Various hooks used for unit-testing and/or fastpath optimizations.
|
|
+ * Currently only one set of such hooks is supported.
|
|
+ */
|
|
+struct dpaa_eth_hooks_s {
|
|
+ /* Invoked on the Tx private path, immediately after receiving the skb
|
|
+ * from the stack.
|
|
+ */
|
|
+ dpaa_eth_egress_hook_t tx;
|
|
+
|
|
+ /* Invoked on the Rx private path, right before passing the skb
|
|
+ * up the stack. At that point, the packet's protocol id has already
|
|
+ * been set. The skb's data pointer is now at the L3 header, and
|
|
+ * skb->mac_header points to the L2 header. skb->len has been adjusted
|
|
+ * to be the length of L3+payload (i.e., the length of the
|
|
+ * original frame minus the L2 header len).
|
|
+ * For more details on what the skb looks like, see eth_type_trans().
|
|
+ */
|
|
+ dpaa_eth_ingress_hook_t rx_default;
|
|
+
|
|
+ /* Driver hook for the Rx error private path. */
|
|
+ dpaa_eth_confirm_hook_t rx_error;
|
|
+ /* Driver hook for the Tx confirmation private path. */
|
|
+ dpaa_eth_confirm_hook_t tx_confirm;
|
|
+ /* Driver hook for the Tx error private path. */
|
|
+ dpaa_eth_confirm_hook_t tx_error;
|
|
+};
|
|
+
|
|
+void fsl_dpaa_eth_set_hooks(struct dpaa_eth_hooks_s *hooks);
|
|
+
|
|
+extern struct dpaa_eth_hooks_s dpaa_eth_hooks;
|
|
+#endif
|
|
+
|
|
+int dpa_netdev_init(struct net_device *net_dev,
|
|
+ const uint8_t *mac_addr,
|
|
+ uint16_t tx_timeout);
|
|
+int __cold dpa_start(struct net_device *net_dev);
|
|
+int __cold dpa_stop(struct net_device *net_dev);
|
|
+void __cold dpa_timeout(struct net_device *net_dev);
|
|
+void __cold
|
|
+dpa_get_stats64(struct net_device *net_dev,
|
|
+ struct rtnl_link_stats64 *stats);
|
|
+int dpa_change_mtu(struct net_device *net_dev, int new_mtu);
|
|
+int dpa_ndo_init(struct net_device *net_dev);
|
|
+int dpa_set_features(struct net_device *dev, netdev_features_t features);
|
|
+netdev_features_t dpa_fix_features(struct net_device *dev,
|
|
+ netdev_features_t features);
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+u64 dpa_get_timestamp_ns(const struct dpa_priv_s *priv,
|
|
+ enum port_type rx_tx, const void *data);
|
|
+/* Updates the skb shared hw timestamp from the hardware timestamp */
|
|
+int dpa_get_ts(const struct dpa_priv_s *priv, enum port_type rx_tx,
|
|
+ struct skb_shared_hwtstamps *shhwtstamps, const void *data);
|
|
+#endif /* CONFIG_FSL_DPAA_TS */
|
|
+int dpa_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
|
|
+int __cold dpa_remove(struct platform_device *of_dev);
|
|
+struct mac_device * __cold __must_check
|
|
+__attribute__((nonnull)) dpa_mac_probe(struct platform_device *_of_dev);
|
|
+int dpa_set_mac_address(struct net_device *net_dev, void *addr);
|
|
+void dpa_set_rx_mode(struct net_device *net_dev);
|
|
+void dpa_set_buffers_layout(struct mac_device *mac_dev,
|
|
+ struct dpa_buffer_layout_s *layout);
|
|
+int __attribute__((nonnull))
|
|
+dpa_bp_alloc(struct dpa_bp *dpa_bp);
|
|
+void __cold __attribute__((nonnull))
|
|
+dpa_bp_free(struct dpa_priv_s *priv);
|
|
+struct dpa_bp *dpa_bpid2pool(int bpid);
|
|
+void dpa_bpid2pool_map(int bpid, struct dpa_bp *dpa_bp);
|
|
+bool dpa_bpid2pool_use(int bpid);
|
|
+void dpa_bp_drain(struct dpa_bp *bp);
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_USE_NDO_SELECT_QUEUE
|
|
+u16 dpa_select_queue(struct net_device *net_dev, struct sk_buff *skb,
|
|
+ struct net_device *sb_dev,
|
|
+ select_queue_fallback_t fallback);
|
|
+#endif
|
|
+struct dpa_fq *dpa_fq_alloc(struct device *dev,
|
|
+ u32 fq_start,
|
|
+ u32 fq_count,
|
|
+ struct list_head *list,
|
|
+ enum dpa_fq_type fq_type);
|
|
+int dpa_fq_probe_mac(struct device *dev, struct list_head *list,
|
|
+ struct fm_port_fqs *port_fqs,
|
|
+ bool tx_conf_fqs_per_core,
|
|
+ enum port_type ptype);
|
|
+int dpa_get_channel(void);
|
|
+void dpa_release_channel(void);
|
|
+void dpaa_eth_add_channel(u16 channel);
|
|
+int dpaa_eth_cgr_init(struct dpa_priv_s *priv);
|
|
+void dpa_fq_setup(struct dpa_priv_s *priv, const struct dpa_fq_cbs_t *fq_cbs,
|
|
+ struct fm_port *tx_port);
|
|
+int dpa_fq_init(struct dpa_fq *dpa_fq, bool td_enable);
|
|
+int dpa_fqs_init(struct device *dev, struct list_head *list, bool td_enable);
|
|
+int __cold __attribute__((nonnull))
|
|
+dpa_fq_free(struct device *dev, struct list_head *list);
|
|
+void dpaa_eth_init_ports(struct mac_device *mac_dev,
|
|
+ struct dpa_bp *bp, size_t count,
|
|
+ struct fm_port_fqs *port_fqs,
|
|
+ struct dpa_buffer_layout_s *buf_layout,
|
|
+ struct device *dev);
|
|
+void dpa_release_sgt(struct qm_sg_entry *sgt);
|
|
+void __attribute__((nonnull))
|
|
+dpa_fd_release(const struct net_device *net_dev, const struct qm_fd *fd);
|
|
+void count_ern(struct dpa_percpu_priv_s *percpu_priv,
|
|
+ const struct qm_mr_entry *msg);
|
|
+int dpa_enable_tx_csum(struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, struct qm_fd *fd, char *parse_results);
|
|
+#ifdef CONFIG_FSL_DPAA_CEETM
|
|
+void dpa_enable_ceetm(struct net_device *dev);
|
|
+void dpa_disable_ceetm(struct net_device *dev);
|
|
+#endif
|
|
+struct proxy_device {
|
|
+ struct mac_device *mac_dev;
|
|
+};
|
|
+
|
|
+/* mac device control functions exposed by proxy interface*/
|
|
+int dpa_proxy_start(struct net_device *net_dev);
|
|
+int dpa_proxy_stop(struct proxy_device *proxy_dev, struct net_device *net_dev);
|
|
+int dpa_proxy_set_mac_address(struct proxy_device *proxy_dev,
|
|
+ struct net_device *net_dev);
|
|
+int dpa_proxy_set_rx_mode(struct proxy_device *proxy_dev,
|
|
+ struct net_device *net_dev);
|
|
+
|
|
+#endif /* __DPAA_ETH_COMMON_H */
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_proxy.c
|
|
@@ -0,0 +1,381 @@
|
|
+/* Copyright 2008-2013 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": %s:%hu:%s() " fmt, \
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__
|
|
+#else
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": " fmt
|
|
+#endif
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include "dpaa_eth.h"
|
|
+#include "dpaa_eth_common.h"
|
|
+#include "dpaa_eth_base.h"
|
|
+#include "lnxwrp_fsl_fman.h" /* fm_get_rx_extra_headroom(), fm_get_max_frm() */
|
|
+#include "mac.h"
|
|
+
|
|
+#define DPA_DESCRIPTION "FSL DPAA Proxy initialization driver"
|
|
+
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
+
|
|
+MODULE_DESCRIPTION(DPA_DESCRIPTION);
|
|
+
|
|
+static int __cold dpa_eth_proxy_remove(struct platform_device *of_dev);
|
|
+#ifdef CONFIG_PM
|
|
+
|
|
+static int proxy_suspend(struct device *dev)
|
|
+{
|
|
+ struct proxy_device *proxy_dev = dev_get_drvdata(dev);
|
|
+ struct mac_device *mac_dev = proxy_dev->mac_dev;
|
|
+ int err = 0;
|
|
+
|
|
+ err = fm_port_suspend(mac_dev->port_dev[RX]);
|
|
+ if (err)
|
|
+ goto port_suspend_failed;
|
|
+
|
|
+ err = fm_port_suspend(mac_dev->port_dev[TX]);
|
|
+ if (err)
|
|
+ err = fm_port_resume(mac_dev->port_dev[RX]);
|
|
+
|
|
+port_suspend_failed:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int proxy_resume(struct device *dev)
|
|
+{
|
|
+ struct proxy_device *proxy_dev = dev_get_drvdata(dev);
|
|
+ struct mac_device *mac_dev = proxy_dev->mac_dev;
|
|
+ int err = 0;
|
|
+
|
|
+ err = fm_port_resume(mac_dev->port_dev[TX]);
|
|
+ if (err)
|
|
+ goto port_resume_failed;
|
|
+
|
|
+ err = fm_port_resume(mac_dev->port_dev[RX]);
|
|
+ if (err)
|
|
+ err = fm_port_suspend(mac_dev->port_dev[TX]);
|
|
+
|
|
+port_resume_failed:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops proxy_pm_ops = {
|
|
+ .suspend = proxy_suspend,
|
|
+ .resume = proxy_resume,
|
|
+};
|
|
+
|
|
+#define PROXY_PM_OPS (&proxy_pm_ops)
|
|
+
|
|
+#else /* CONFIG_PM */
|
|
+
|
|
+#define PROXY_PM_OPS NULL
|
|
+
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+static int dpaa_eth_proxy_probe(struct platform_device *_of_dev)
|
|
+{
|
|
+ int err = 0, i;
|
|
+ struct device *dev;
|
|
+ struct device_node *dpa_node;
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ struct list_head proxy_fq_list;
|
|
+ size_t count;
|
|
+ struct fm_port_fqs port_fqs;
|
|
+ struct dpa_buffer_layout_s *buf_layout = NULL;
|
|
+ struct mac_device *mac_dev;
|
|
+ struct proxy_device *proxy_dev;
|
|
+
|
|
+ dev = &_of_dev->dev;
|
|
+
|
|
+ dpa_node = dev->of_node;
|
|
+
|
|
+ if (!of_device_is_available(dpa_node))
|
|
+ return -ENODEV;
|
|
+
|
|
+ /* Get the buffer pools assigned to this interface */
|
|
+ dpa_bp = dpa_bp_probe(_of_dev, &count);
|
|
+ if (IS_ERR(dpa_bp))
|
|
+ return PTR_ERR(dpa_bp);
|
|
+
|
|
+ mac_dev = dpa_mac_probe(_of_dev);
|
|
+ if (IS_ERR(mac_dev))
|
|
+ return PTR_ERR(mac_dev);
|
|
+
|
|
+ proxy_dev = devm_kzalloc(dev, sizeof(*proxy_dev), GFP_KERNEL);
|
|
+ if (!proxy_dev) {
|
|
+ dev_err(dev, "devm_kzalloc() failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ proxy_dev->mac_dev = mac_dev;
|
|
+ dev_set_drvdata(dev, proxy_dev);
|
|
+
|
|
+ /* We have physical ports, so we need to establish
|
|
+ * the buffer layout.
|
|
+ */
|
|
+ buf_layout = devm_kzalloc(dev, 2 * sizeof(*buf_layout),
|
|
+ GFP_KERNEL);
|
|
+ if (!buf_layout) {
|
|
+ dev_err(dev, "devm_kzalloc() failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ dpa_set_buffers_layout(mac_dev, buf_layout);
|
|
+
|
|
+ INIT_LIST_HEAD(&proxy_fq_list);
|
|
+
|
|
+ memset(&port_fqs, 0, sizeof(port_fqs));
|
|
+
|
|
+ err = dpa_fq_probe_mac(dev, &proxy_fq_list, &port_fqs, true, RX);
|
|
+ if (!err)
|
|
+ err = dpa_fq_probe_mac(dev, &proxy_fq_list, &port_fqs, true,
|
|
+ TX);
|
|
+ if (err < 0) {
|
|
+ devm_kfree(dev, buf_layout);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* Proxy initializer - Just configures the MAC on behalf of
|
|
+ * another partition.
|
|
+ */
|
|
+ dpaa_eth_init_ports(mac_dev, dpa_bp, count, &port_fqs,
|
|
+ buf_layout, dev);
|
|
+
|
|
+ /* Proxy interfaces need to be started, and the allocated
|
|
+ * memory freed
|
|
+ */
|
|
+ devm_kfree(dev, buf_layout);
|
|
+ devm_kfree(dev, dpa_bp);
|
|
+
|
|
+ /* Free FQ structures */
|
|
+ devm_kfree(dev, port_fqs.rx_defq);
|
|
+ devm_kfree(dev, port_fqs.rx_errq);
|
|
+ devm_kfree(dev, port_fqs.tx_defq);
|
|
+ devm_kfree(dev, port_fqs.tx_errq);
|
|
+
|
|
+ for_each_port_device(i, mac_dev->port_dev) {
|
|
+ err = fm_port_enable(mac_dev->port_dev[i]);
|
|
+ if (err)
|
|
+ goto port_enable_fail;
|
|
+ }
|
|
+
|
|
+ dev_info(dev, "probed MAC device with MAC address: %02hx:%02hx:%02hx:%02hx:%02hx:%02hx\n",
|
|
+ mac_dev->addr[0], mac_dev->addr[1], mac_dev->addr[2],
|
|
+ mac_dev->addr[3], mac_dev->addr[4], mac_dev->addr[5]);
|
|
+
|
|
+ return 0; /* Proxy interface initialization ended */
|
|
+
|
|
+port_enable_fail:
|
|
+ for_each_port_device(i, mac_dev->port_dev)
|
|
+ fm_port_disable(mac_dev->port_dev[i]);
|
|
+ dpa_eth_proxy_remove(_of_dev);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int dpa_proxy_set_mac_address(struct proxy_device *proxy_dev,
|
|
+ struct net_device *net_dev)
|
|
+{
|
|
+ struct mac_device *mac_dev;
|
|
+ int _errno;
|
|
+
|
|
+ mac_dev = proxy_dev->mac_dev;
|
|
+
|
|
+ _errno = mac_dev->change_addr(mac_dev->get_mac_handle(mac_dev),
|
|
+ net_dev->dev_addr);
|
|
+ if (_errno < 0)
|
|
+ return _errno;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_proxy_set_mac_address);
|
|
+
|
|
+int dpa_proxy_set_rx_mode(struct proxy_device *proxy_dev,
|
|
+ struct net_device *net_dev)
|
|
+{
|
|
+ struct mac_device *mac_dev = proxy_dev->mac_dev;
|
|
+ int _errno;
|
|
+
|
|
+ if (!!(net_dev->flags & IFF_PROMISC) != mac_dev->promisc) {
|
|
+ mac_dev->promisc = !mac_dev->promisc;
|
|
+ _errno = mac_dev->set_promisc(mac_dev->get_mac_handle(mac_dev),
|
|
+ mac_dev->promisc);
|
|
+ if (unlikely(_errno < 0))
|
|
+ netdev_err(net_dev, "mac_dev->set_promisc() = %d\n",
|
|
+ _errno);
|
|
+ }
|
|
+
|
|
+ _errno = mac_dev->set_multi(net_dev, mac_dev);
|
|
+ if (unlikely(_errno < 0))
|
|
+ return _errno;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_proxy_set_rx_mode);
|
|
+
|
|
+int dpa_proxy_start(struct net_device *net_dev)
|
|
+{
|
|
+ struct mac_device *mac_dev;
|
|
+ const struct dpa_priv_s *priv;
|
|
+ struct proxy_device *proxy_dev;
|
|
+ int _errno;
|
|
+ int i;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ proxy_dev = (struct proxy_device *)priv->peer;
|
|
+ mac_dev = proxy_dev->mac_dev;
|
|
+
|
|
+ _errno = mac_dev->init_phy(net_dev, mac_dev);
|
|
+ if (_errno < 0) {
|
|
+ if (netif_msg_drv(priv))
|
|
+ netdev_err(net_dev, "init_phy() = %d\n",
|
|
+ _errno);
|
|
+ return _errno;
|
|
+ }
|
|
+
|
|
+ for_each_port_device(i, mac_dev->port_dev) {
|
|
+ _errno = fm_port_enable(mac_dev->port_dev[i]);
|
|
+ if (_errno)
|
|
+ goto port_enable_fail;
|
|
+ }
|
|
+
|
|
+ _errno = mac_dev->start(mac_dev);
|
|
+ if (_errno < 0) {
|
|
+ if (netif_msg_drv(priv))
|
|
+ netdev_err(net_dev, "mac_dev->start() = %d\n",
|
|
+ _errno);
|
|
+ goto port_enable_fail;
|
|
+ }
|
|
+
|
|
+ return _errno;
|
|
+
|
|
+port_enable_fail:
|
|
+ for_each_port_device(i, mac_dev->port_dev)
|
|
+ fm_port_disable(mac_dev->port_dev[i]);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_proxy_start);
|
|
+
|
|
+int dpa_proxy_stop(struct proxy_device *proxy_dev, struct net_device *net_dev)
|
|
+{
|
|
+ struct mac_device *mac_dev = proxy_dev->mac_dev;
|
|
+ const struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ int _errno, i, err;
|
|
+
|
|
+ _errno = mac_dev->stop(mac_dev);
|
|
+ if (_errno < 0) {
|
|
+ if (netif_msg_drv(priv))
|
|
+ netdev_err(net_dev, "mac_dev->stop() = %d\n",
|
|
+ _errno);
|
|
+ return _errno;
|
|
+ }
|
|
+
|
|
+ for_each_port_device(i, mac_dev->port_dev) {
|
|
+ err = fm_port_disable(mac_dev->port_dev[i]);
|
|
+ _errno = err ? err : _errno;
|
|
+ }
|
|
+
|
|
+ if (mac_dev->phy_dev)
|
|
+ phy_disconnect(mac_dev->phy_dev);
|
|
+ mac_dev->phy_dev = NULL;
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_proxy_stop);
|
|
+
|
|
+static int __cold dpa_eth_proxy_remove(struct platform_device *of_dev)
|
|
+{
|
|
+ struct device *dev = &of_dev->dev;
|
|
+ struct proxy_device *proxy_dev = dev_get_drvdata(dev);
|
|
+
|
|
+ kfree(proxy_dev);
|
|
+
|
|
+ dev_set_drvdata(dev, NULL);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id dpa_proxy_match[] = {
|
|
+ {
|
|
+ .compatible = "fsl,dpa-ethernet-init"
|
|
+ },
|
|
+ {}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, dpa_proxy_match);
|
|
+
|
|
+static struct platform_driver dpa_proxy_driver = {
|
|
+ .driver = {
|
|
+ .name = KBUILD_MODNAME "-proxy",
|
|
+ .of_match_table = dpa_proxy_match,
|
|
+ .owner = THIS_MODULE,
|
|
+ .pm = PROXY_PM_OPS,
|
|
+ },
|
|
+ .probe = dpaa_eth_proxy_probe,
|
|
+ .remove = dpa_eth_proxy_remove
|
|
+};
|
|
+
|
|
+static int __init __cold dpa_proxy_load(void)
|
|
+{
|
|
+ int _errno;
|
|
+
|
|
+ pr_info(DPA_DESCRIPTION "\n");
|
|
+
|
|
+ /* Initialize dpaa_eth mirror values */
|
|
+ dpa_rx_extra_headroom = fm_get_rx_extra_headroom();
|
|
+ dpa_max_frm = fm_get_max_frm();
|
|
+
|
|
+ _errno = platform_driver_register(&dpa_proxy_driver);
|
|
+ if (unlikely(_errno < 0)) {
|
|
+ pr_err(KBUILD_MODNAME
|
|
+ ": %s:%hu:%s(): platform_driver_register() = %d\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__, _errno);
|
|
+ }
|
|
+
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+module_init(dpa_proxy_load);
|
|
+
|
|
+static void __exit __cold dpa_proxy_unload(void)
|
|
+{
|
|
+ platform_driver_unregister(&dpa_proxy_driver);
|
|
+
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+}
|
|
+module_exit(dpa_proxy_unload);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_sg.c
|
|
@@ -0,0 +1,1113 @@
|
|
+/* Copyright 2012 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": %s:%hu:%s() " fmt, \
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__
|
|
+#else
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": " fmt
|
|
+#endif
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/highmem.h>
|
|
+#include <linux/fsl_bman.h>
|
|
+
|
|
+#include "dpaa_eth.h"
|
|
+#include "dpaa_eth_common.h"
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+#include "dpaa_1588.h"
|
|
+#endif
|
|
+#ifdef CONFIG_FSL_DPAA_CEETM
|
|
+#include "dpaa_eth_ceetm.h"
|
|
+#endif
|
|
+
|
|
+/* DMA map and add a page frag back into the bpool.
|
|
+ * @vaddr fragment must have been allocated with netdev_alloc_frag(),
|
|
+ * specifically for fitting into @dpa_bp.
|
|
+ */
|
|
+static void dpa_bp_recycle_frag(struct dpa_bp *dpa_bp, unsigned long vaddr,
|
|
+ int *count_ptr)
|
|
+{
|
|
+ struct bm_buffer bmb;
|
|
+ dma_addr_t addr;
|
|
+
|
|
+ bmb.opaque = 0;
|
|
+
|
|
+ addr = dma_map_single(dpa_bp->dev, (void *)vaddr, dpa_bp->size,
|
|
+ DMA_BIDIRECTIONAL);
|
|
+ if (unlikely(dma_mapping_error(dpa_bp->dev, addr))) {
|
|
+ dev_err(dpa_bp->dev, "DMA mapping failed");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ bm_buffer_set64(&bmb, addr);
|
|
+
|
|
+ while (bman_release(dpa_bp->pool, &bmb, 1, 0))
|
|
+ cpu_relax();
|
|
+
|
|
+ (*count_ptr)++;
|
|
+}
|
|
+
|
|
+static int _dpa_bp_add_8_bufs(const struct dpa_bp *dpa_bp)
|
|
+{
|
|
+ struct bm_buffer bmb[8];
|
|
+ void *new_buf;
|
|
+ dma_addr_t addr;
|
|
+ uint8_t i;
|
|
+ struct device *dev = dpa_bp->dev;
|
|
+ struct sk_buff *skb, **skbh;
|
|
+
|
|
+ memset(bmb, 0, sizeof(struct bm_buffer) * 8);
|
|
+
|
|
+ for (i = 0; i < 8; i++) {
|
|
+ /* We'll prepend the skb back-pointer; can't use the DPA
|
|
+ * priv space, because FMan will overwrite it (from offset 0)
|
|
+ * if it ends up being the second, third, etc. fragment
|
|
+ * in a S/G frame.
|
|
+ *
|
|
+ * We only need enough space to store a pointer, but allocate
|
|
+ * an entire cacheline for performance reasons.
|
|
+ */
|
|
+#ifndef CONFIG_PPC
|
|
+ if (unlikely(dpaa_errata_a010022))
|
|
+ new_buf = page_address(alloc_page(GFP_ATOMIC));
|
|
+ else
|
|
+#endif
|
|
+ new_buf = netdev_alloc_frag(SMP_CACHE_BYTES + DPA_BP_RAW_SIZE);
|
|
+
|
|
+ if (unlikely(!new_buf))
|
|
+ goto netdev_alloc_failed;
|
|
+ new_buf = PTR_ALIGN(new_buf + SMP_CACHE_BYTES, SMP_CACHE_BYTES);
|
|
+
|
|
+ skb = build_skb(new_buf, DPA_SKB_SIZE(dpa_bp->size) +
|
|
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)));
|
|
+ if (unlikely(!skb)) {
|
|
+ put_page(virt_to_head_page(new_buf));
|
|
+ goto build_skb_failed;
|
|
+ }
|
|
+ DPA_WRITE_SKB_PTR(skb, skbh, new_buf, -1);
|
|
+
|
|
+ addr = dma_map_single(dev, new_buf,
|
|
+ dpa_bp->size, DMA_BIDIRECTIONAL);
|
|
+ if (unlikely(dma_mapping_error(dev, addr)))
|
|
+ goto dma_map_failed;
|
|
+
|
|
+ bm_buffer_set64(&bmb[i], addr);
|
|
+ }
|
|
+
|
|
+release_bufs:
|
|
+ /* Release the buffers. In case bman is busy, keep trying
|
|
+ * until successful. bman_release() is guaranteed to succeed
|
|
+ * in a reasonable amount of time
|
|
+ */
|
|
+ while (unlikely(bman_release(dpa_bp->pool, bmb, i, 0)))
|
|
+ cpu_relax();
|
|
+ return i;
|
|
+
|
|
+dma_map_failed:
|
|
+ kfree_skb(skb);
|
|
+
|
|
+build_skb_failed:
|
|
+netdev_alloc_failed:
|
|
+ net_err_ratelimited("dpa_bp_add_8_bufs() failed\n");
|
|
+ WARN_ONCE(1, "Memory allocation failure on Rx\n");
|
|
+
|
|
+ bm_buffer_set64(&bmb[i], 0);
|
|
+ /* Avoid releasing a completely null buffer; bman_release() requires
|
|
+ * at least one buffer.
|
|
+ */
|
|
+ if (likely(i))
|
|
+ goto release_bufs;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Cold path wrapper over _dpa_bp_add_8_bufs(). */
|
|
+static void dpa_bp_add_8_bufs(const struct dpa_bp *dpa_bp, int cpu)
|
|
+{
|
|
+ int *count_ptr = per_cpu_ptr(dpa_bp->percpu_count, cpu);
|
|
+ *count_ptr += _dpa_bp_add_8_bufs(dpa_bp);
|
|
+}
|
|
+
|
|
+int dpa_bp_priv_seed(struct dpa_bp *dpa_bp)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ /* Give each CPU an allotment of "config_count" buffers */
|
|
+ for_each_possible_cpu(i) {
|
|
+ int j;
|
|
+
|
|
+ /* Although we access another CPU's counters here
|
|
+ * we do it at boot time so it is safe
|
|
+ */
|
|
+ for (j = 0; j < dpa_bp->config_count; j += 8)
|
|
+ dpa_bp_add_8_bufs(dpa_bp, i);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_bp_priv_seed);
|
|
+
|
|
+/* Add buffers/(pages) for Rx processing whenever bpool count falls below
|
|
+ * REFILL_THRESHOLD.
|
|
+ */
|
|
+int dpaa_eth_refill_bpools(struct dpa_bp *dpa_bp, int *countptr)
|
|
+{
|
|
+ int count = *countptr;
|
|
+ int new_bufs;
|
|
+
|
|
+ if (unlikely(count < CONFIG_FSL_DPAA_ETH_REFILL_THRESHOLD)) {
|
|
+ do {
|
|
+ new_bufs = _dpa_bp_add_8_bufs(dpa_bp);
|
|
+ if (unlikely(!new_bufs)) {
|
|
+ /* Avoid looping forever if we've temporarily
|
|
+ * run out of memory. We'll try again at the
|
|
+ * next NAPI cycle.
|
|
+ */
|
|
+ break;
|
|
+ }
|
|
+ count += new_bufs;
|
|
+ } while (count < CONFIG_FSL_DPAA_ETH_MAX_BUF_COUNT);
|
|
+
|
|
+ *countptr = count;
|
|
+ if (unlikely(count < CONFIG_FSL_DPAA_ETH_MAX_BUF_COUNT))
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(dpaa_eth_refill_bpools);
|
|
+
|
|
+/* Cleanup function for outgoing frame descriptors that were built on Tx path,
|
|
+ * either contiguous frames or scatter/gather ones.
|
|
+ * Skb freeing is not handled here.
|
|
+ *
|
|
+ * This function may be called on error paths in the Tx function, so guard
|
|
+ * against cases when not all fd relevant fields were filled in.
|
|
+ *
|
|
+ * Return the skb backpointer, since for S/G frames the buffer containing it
|
|
+ * gets freed here.
|
|
+ */
|
|
+struct sk_buff *_dpa_cleanup_tx_fd(const struct dpa_priv_s *priv,
|
|
+ const struct qm_fd *fd)
|
|
+{
|
|
+ const struct qm_sg_entry *sgt;
|
|
+ int i;
|
|
+ struct dpa_bp *dpa_bp = priv->dpa_bp;
|
|
+ dma_addr_t addr = qm_fd_addr(fd);
|
|
+ dma_addr_t sg_addr;
|
|
+ struct sk_buff **skbh;
|
|
+ struct sk_buff *skb = NULL;
|
|
+ const enum dma_data_direction dma_dir = DMA_TO_DEVICE;
|
|
+ int nr_frags;
|
|
+ int sg_len;
|
|
+
|
|
+ /* retrieve skb back pointer */
|
|
+ DPA_READ_SKB_PTR(skb, skbh, phys_to_virt(addr), 0);
|
|
+
|
|
+ if (unlikely(fd->format == qm_fd_sg)) {
|
|
+ nr_frags = skb_shinfo(skb)->nr_frags;
|
|
+ dma_unmap_single(dpa_bp->dev, addr, dpa_fd_offset(fd) +
|
|
+ sizeof(struct qm_sg_entry) * (1 + nr_frags),
|
|
+ dma_dir);
|
|
+
|
|
+ /* The sgt buffer has been allocated with netdev_alloc_frag(),
|
|
+ * it's from lowmem.
|
|
+ */
|
|
+ sgt = phys_to_virt(addr + dpa_fd_offset(fd));
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ if (priv->tsu && priv->tsu->valid &&
|
|
+ priv->tsu->hwts_tx_en_ioctl)
|
|
+ dpa_ptp_store_txstamp(priv, skb, (void *)skbh);
|
|
+#endif
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ if (unlikely(priv->ts_tx_en &&
|
|
+ skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) {
|
|
+ struct skb_shared_hwtstamps shhwtstamps;
|
|
+
|
|
+ dpa_get_ts(priv, TX, &shhwtstamps, (void *)skbh);
|
|
+ skb_tstamp_tx(skb, &shhwtstamps);
|
|
+ }
|
|
+#endif /* CONFIG_FSL_DPAA_TS */
|
|
+
|
|
+ /* sgt[0] is from lowmem, was dma_map_single()-ed */
|
|
+ sg_addr = qm_sg_addr(&sgt[0]);
|
|
+ sg_len = qm_sg_entry_get_len(&sgt[0]);
|
|
+ dma_unmap_single(dpa_bp->dev, sg_addr, sg_len, dma_dir);
|
|
+
|
|
+ /* remaining pages were mapped with dma_map_page() */
|
|
+ for (i = 1; i <= nr_frags; i++) {
|
|
+ DPA_BUG_ON(qm_sg_entry_get_ext(&sgt[i]));
|
|
+ sg_addr = qm_sg_addr(&sgt[i]);
|
|
+ sg_len = qm_sg_entry_get_len(&sgt[i]);
|
|
+ dma_unmap_page(dpa_bp->dev, sg_addr, sg_len, dma_dir);
|
|
+ }
|
|
+
|
|
+ /* Free the page frag that we allocated on Tx */
|
|
+ put_page(virt_to_head_page(sgt));
|
|
+ } else {
|
|
+ dma_unmap_single(dpa_bp->dev, addr,
|
|
+ skb_tail_pointer(skb) - (u8 *)skbh, dma_dir);
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ /* get the timestamp for non-SG frames */
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ if (priv->tsu && priv->tsu->valid &&
|
|
+ priv->tsu->hwts_tx_en_ioctl)
|
|
+ dpa_ptp_store_txstamp(priv, skb, (void *)skbh);
|
|
+#endif
|
|
+ if (unlikely(priv->ts_tx_en &&
|
|
+ skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) {
|
|
+ struct skb_shared_hwtstamps shhwtstamps;
|
|
+
|
|
+ dpa_get_ts(priv, TX, &shhwtstamps, (void *)skbh);
|
|
+ skb_tstamp_tx(skb, &shhwtstamps);
|
|
+ }
|
|
+#endif
|
|
+ }
|
|
+
|
|
+ return skb;
|
|
+}
|
|
+EXPORT_SYMBOL(_dpa_cleanup_tx_fd);
|
|
+
|
|
+#ifndef CONFIG_FSL_DPAA_TS
|
|
+bool dpa_skb_is_recyclable(struct sk_buff *skb)
|
|
+{
|
|
+ /* No recycling possible if skb buffer is kmalloc'ed */
|
|
+ if (skb->head_frag == 0)
|
|
+ return false;
|
|
+
|
|
+ /* or if it's an userspace buffer */
|
|
+ if (skb_shinfo(skb)->tx_flags & SKBTX_DEV_ZEROCOPY)
|
|
+ return false;
|
|
+
|
|
+ /* or if it's cloned or shared */
|
|
+ if (skb_shared(skb) || skb_cloned(skb) ||
|
|
+ skb->fclone != SKB_FCLONE_UNAVAILABLE)
|
|
+ return false;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_skb_is_recyclable);
|
|
+
|
|
+bool dpa_buf_is_recyclable(struct sk_buff *skb,
|
|
+ uint32_t min_size,
|
|
+ uint16_t min_offset,
|
|
+ unsigned char **new_buf_start)
|
|
+{
|
|
+ unsigned char *new;
|
|
+
|
|
+ /* In order to recycle a buffer, the following conditions must be met:
|
|
+ * - buffer size no less than the buffer pool size
|
|
+ * - buffer size no higher than an upper limit (to avoid moving too much
|
|
+ * system memory to the buffer pools)
|
|
+ * - buffer address aligned to cacheline bytes
|
|
+ * - offset of data from start of buffer no lower than a minimum value
|
|
+ * - offset of data from start of buffer no higher than a maximum value
|
|
+ */
|
|
+ new = min(skb_end_pointer(skb) - min_size, skb->data - min_offset);
|
|
+
|
|
+ /* left align to the nearest cacheline */
|
|
+ new = (unsigned char *)((unsigned long)new & ~(SMP_CACHE_BYTES - 1));
|
|
+
|
|
+ if (likely(new >= skb->head &&
|
|
+ new >= (skb->data - DPA_MAX_FD_OFFSET) &&
|
|
+ skb_end_pointer(skb) - new <= DPA_RECYCLE_MAX_SIZE)) {
|
|
+ *new_buf_start = new;
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_buf_is_recyclable);
|
|
+#endif
|
|
+
|
|
+/* Build a linear skb around the received buffer.
|
|
+ * We are guaranteed there is enough room at the end of the data buffer to
|
|
+ * accommodate the shared info area of the skb.
|
|
+ */
|
|
+static struct sk_buff *__hot contig_fd_to_skb(const struct dpa_priv_s *priv,
|
|
+ const struct qm_fd *fd, int *use_gro)
|
|
+{
|
|
+ dma_addr_t addr = qm_fd_addr(fd);
|
|
+ ssize_t fd_off = dpa_fd_offset(fd);
|
|
+ void *vaddr;
|
|
+ const fm_prs_result_t *parse_results;
|
|
+ struct sk_buff *skb = NULL, **skbh;
|
|
+
|
|
+ vaddr = phys_to_virt(addr);
|
|
+ DPA_BUG_ON(!IS_ALIGNED((unsigned long)vaddr, SMP_CACHE_BYTES));
|
|
+
|
|
+ /* Retrieve the skb and adjust data and tail pointers, to make sure
|
|
+ * forwarded skbs will have enough space on Tx if extra headers
|
|
+ * are added.
|
|
+ */
|
|
+ DPA_READ_SKB_PTR(skb, skbh, vaddr, -1);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_JUMBO_FRAME
|
|
+ /* When using jumbo Rx buffers, we risk having frames dropped due to
|
|
+ * the socket backlog reaching its maximum allowed size.
|
|
+ * Use the frame length for the skb truesize instead of the buffer
|
|
+ * size, as this is the size of the data that actually gets copied to
|
|
+ * userspace.
|
|
+ * The stack may increase the payload. In this case, it will want to
|
|
+ * warn us that the frame length is larger than the truesize. We
|
|
+ * bypass the warning.
|
|
+ */
|
|
+#ifndef CONFIG_PPC
|
|
+ /* We do not support Jumbo frames on LS1043 and thus we edit
|
|
+ * the skb truesize only when the 4k errata is not present.
|
|
+ */
|
|
+ if (likely(!dpaa_errata_a010022))
|
|
+#endif
|
|
+ skb->truesize = SKB_TRUESIZE(dpa_fd_length(fd));
|
|
+#endif
|
|
+
|
|
+ DPA_BUG_ON(fd_off != priv->rx_headroom);
|
|
+ skb_reserve(skb, fd_off);
|
|
+ skb_put(skb, dpa_fd_length(fd));
|
|
+
|
|
+ /* Peek at the parse results for csum validation */
|
|
+ parse_results = (const fm_prs_result_t *)(vaddr +
|
|
+ DPA_RX_PRIV_DATA_SIZE);
|
|
+ _dpa_process_parse_results(parse_results, fd, skb, use_gro);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ if (priv->tsu && priv->tsu->valid && priv->tsu->hwts_rx_en_ioctl)
|
|
+ dpa_ptp_store_rxstamp(priv, skb, vaddr);
|
|
+#endif
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ if (priv->ts_rx_en)
|
|
+ dpa_get_ts(priv, RX, skb_hwtstamps(skb), vaddr);
|
|
+#endif /* CONFIG_FSL_DPAA_TS */
|
|
+
|
|
+ return skb;
|
|
+}
|
|
+
|
|
+
|
|
+/* Build an skb with the data of the first S/G entry in the linear portion and
|
|
+ * the rest of the frame as skb fragments.
|
|
+ *
|
|
+ * The page fragment holding the S/G Table is recycled here.
|
|
+ */
|
|
+static struct sk_buff *__hot sg_fd_to_skb(const struct dpa_priv_s *priv,
|
|
+ const struct qm_fd *fd, int *use_gro,
|
|
+ int *count_ptr)
|
|
+{
|
|
+ const struct qm_sg_entry *sgt;
|
|
+ dma_addr_t addr = qm_fd_addr(fd);
|
|
+ ssize_t fd_off = dpa_fd_offset(fd);
|
|
+ dma_addr_t sg_addr;
|
|
+ void *vaddr, *sg_vaddr;
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ struct page *page, *head_page;
|
|
+ int frag_offset, frag_len;
|
|
+ int page_offset;
|
|
+ int i;
|
|
+ const fm_prs_result_t *parse_results;
|
|
+ struct sk_buff *skb = NULL, *skb_tmp, **skbh;
|
|
+
|
|
+ vaddr = phys_to_virt(addr);
|
|
+ DPA_BUG_ON(!IS_ALIGNED((unsigned long)vaddr, SMP_CACHE_BYTES));
|
|
+
|
|
+ dpa_bp = priv->dpa_bp;
|
|
+ /* Iterate through the SGT entries and add data buffers to the skb */
|
|
+ sgt = vaddr + fd_off;
|
|
+ for (i = 0; i < DPA_SGT_MAX_ENTRIES; i++) {
|
|
+ /* Extension bit is not supported */
|
|
+ DPA_BUG_ON(qm_sg_entry_get_ext(&sgt[i]));
|
|
+
|
|
+ /* We use a single global Rx pool */
|
|
+ DPA_BUG_ON(dpa_bp !=
|
|
+ dpa_bpid2pool(qm_sg_entry_get_bpid(&sgt[i])));
|
|
+
|
|
+ sg_addr = qm_sg_addr(&sgt[i]);
|
|
+ sg_vaddr = phys_to_virt(sg_addr);
|
|
+ DPA_BUG_ON(!IS_ALIGNED((unsigned long)sg_vaddr,
|
|
+ SMP_CACHE_BYTES));
|
|
+
|
|
+ dma_unmap_single(dpa_bp->dev, sg_addr, dpa_bp->size,
|
|
+ DMA_BIDIRECTIONAL);
|
|
+ if (i == 0) {
|
|
+ DPA_READ_SKB_PTR(skb, skbh, sg_vaddr, -1);
|
|
+ DPA_BUG_ON(skb->head != sg_vaddr);
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ if (priv->tsu && priv->tsu->valid &&
|
|
+ priv->tsu->hwts_rx_en_ioctl)
|
|
+ dpa_ptp_store_rxstamp(priv, skb, vaddr);
|
|
+#endif
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ if (priv->ts_rx_en)
|
|
+ dpa_get_ts(priv, RX, skb_hwtstamps(skb), vaddr);
|
|
+#endif /* CONFIG_FSL_DPAA_TS */
|
|
+
|
|
+ /* In the case of a SG frame, FMan stores the Internal
|
|
+ * Context in the buffer containing the sgt.
|
|
+ * Inspect the parse results before anything else.
|
|
+ */
|
|
+ parse_results = (const fm_prs_result_t *)(vaddr +
|
|
+ DPA_RX_PRIV_DATA_SIZE);
|
|
+ _dpa_process_parse_results(parse_results, fd, skb,
|
|
+ use_gro);
|
|
+
|
|
+ /* Make sure forwarded skbs will have enough space
|
|
+ * on Tx, if extra headers are added.
|
|
+ */
|
|
+ DPA_BUG_ON(fd_off != priv->rx_headroom);
|
|
+ skb_reserve(skb, fd_off);
|
|
+ skb_put(skb, qm_sg_entry_get_len(&sgt[i]));
|
|
+ } else {
|
|
+ /* Not the first S/G entry; all data from buffer will
|
|
+ * be added in an skb fragment; fragment index is offset
|
|
+ * by one since first S/G entry was incorporated in the
|
|
+ * linear part of the skb.
|
|
+ *
|
|
+ * Caution: 'page' may be a tail page.
|
|
+ */
|
|
+ DPA_READ_SKB_PTR(skb_tmp, skbh, sg_vaddr, -1);
|
|
+ page = virt_to_page(sg_vaddr);
|
|
+ head_page = virt_to_head_page(sg_vaddr);
|
|
+
|
|
+ /* Free (only) the skbuff shell because its data buffer
|
|
+ * is already a frag in the main skb.
|
|
+ */
|
|
+ get_page(head_page);
|
|
+ dev_kfree_skb(skb_tmp);
|
|
+
|
|
+ /* Compute offset in (possibly tail) page */
|
|
+ page_offset = ((unsigned long)sg_vaddr &
|
|
+ (PAGE_SIZE - 1)) +
|
|
+ (page_address(page) - page_address(head_page));
|
|
+ /* page_offset only refers to the beginning of sgt[i];
|
|
+ * but the buffer itself may have an internal offset.
|
|
+ */
|
|
+ frag_offset = qm_sg_entry_get_offset(&sgt[i]) +
|
|
+ page_offset;
|
|
+ frag_len = qm_sg_entry_get_len(&sgt[i]);
|
|
+ /* skb_add_rx_frag() does no checking on the page; if
|
|
+ * we pass it a tail page, we'll end up with
|
|
+ * bad page accounting and eventually with segafults.
|
|
+ */
|
|
+ skb_add_rx_frag(skb, i - 1, head_page, frag_offset,
|
|
+ frag_len, dpa_bp->size);
|
|
+ }
|
|
+ /* Update the pool count for the current {cpu x bpool} */
|
|
+ (*count_ptr)--;
|
|
+
|
|
+ if (qm_sg_entry_get_final(&sgt[i]))
|
|
+ break;
|
|
+ }
|
|
+ WARN_ONCE(i == DPA_SGT_MAX_ENTRIES, "No final bit on SGT\n");
|
|
+
|
|
+ /* recycle the SGT fragment */
|
|
+ DPA_BUG_ON(dpa_bp != dpa_bpid2pool(fd->bpid));
|
|
+ dpa_bp_recycle_frag(dpa_bp, (unsigned long)vaddr, count_ptr);
|
|
+ return skb;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+static inline int dpa_skb_loop(const struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb)
|
|
+{
|
|
+ if (unlikely(priv->loop_to < 0))
|
|
+ return 0; /* loop disabled by default */
|
|
+
|
|
+ skb_push(skb, ETH_HLEN); /* compensate for eth_type_trans */
|
|
+ dpa_tx(skb, dpa_loop_netdevs[priv->loop_to]);
|
|
+
|
|
+ return 1; /* Frame Tx on the selected interface */
|
|
+}
|
|
+#endif
|
|
+
|
|
+void __hot _dpa_rx(struct net_device *net_dev,
|
|
+ struct qman_portal *portal,
|
|
+ const struct dpa_priv_s *priv,
|
|
+ struct dpa_percpu_priv_s *percpu_priv,
|
|
+ const struct qm_fd *fd,
|
|
+ u32 fqid,
|
|
+ int *count_ptr)
|
|
+{
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ struct sk_buff *skb;
|
|
+ dma_addr_t addr = qm_fd_addr(fd);
|
|
+ u32 fd_status = fd->status;
|
|
+ unsigned int skb_len;
|
|
+ struct rtnl_link_stats64 *percpu_stats = &percpu_priv->stats;
|
|
+ int use_gro = net_dev->features & NETIF_F_GRO;
|
|
+
|
|
+ if (unlikely(fd_status & FM_FD_STAT_RX_ERRORS) != 0) {
|
|
+ if (netif_msg_hw(priv) && net_ratelimit())
|
|
+ netdev_warn(net_dev, "FD status = 0x%08x\n",
|
|
+ fd_status & FM_FD_STAT_RX_ERRORS);
|
|
+
|
|
+ percpu_stats->rx_errors++;
|
|
+ goto _release_frame;
|
|
+ }
|
|
+
|
|
+ dpa_bp = priv->dpa_bp;
|
|
+ DPA_BUG_ON(dpa_bp != dpa_bpid2pool(fd->bpid));
|
|
+
|
|
+ /* prefetch the first 64 bytes of the frame or the SGT start */
|
|
+ dma_unmap_single(dpa_bp->dev, addr, dpa_bp->size, DMA_BIDIRECTIONAL);
|
|
+ prefetch(phys_to_virt(addr) + dpa_fd_offset(fd));
|
|
+
|
|
+ /* The only FD types that we may receive are contig and S/G */
|
|
+ DPA_BUG_ON((fd->format != qm_fd_contig) && (fd->format != qm_fd_sg));
|
|
+
|
|
+ if (likely(fd->format == qm_fd_contig)) {
|
|
+#ifdef CONFIG_FSL_DPAA_HOOKS
|
|
+ /* Execute the Rx processing hook, if it exists. */
|
|
+ if (dpaa_eth_hooks.rx_default &&
|
|
+ dpaa_eth_hooks.rx_default((void *)fd, net_dev,
|
|
+ fqid) == DPAA_ETH_STOLEN) {
|
|
+ /* won't count the rx bytes in */
|
|
+ return;
|
|
+ }
|
|
+#endif
|
|
+ skb = contig_fd_to_skb(priv, fd, &use_gro);
|
|
+ } else {
|
|
+ skb = sg_fd_to_skb(priv, fd, &use_gro, count_ptr);
|
|
+ percpu_priv->rx_sg++;
|
|
+ }
|
|
+
|
|
+ /* Account for either the contig buffer or the SGT buffer (depending on
|
|
+ * which case we were in) having been removed from the pool.
|
|
+ */
|
|
+ (*count_ptr)--;
|
|
+ skb->protocol = eth_type_trans(skb, net_dev);
|
|
+
|
|
+ /* IP Reassembled frames are allowed to be larger than MTU */
|
|
+ if (unlikely(dpa_check_rx_mtu(skb, net_dev->mtu) &&
|
|
+ !(fd_status & FM_FD_IPR))) {
|
|
+ percpu_stats->rx_dropped++;
|
|
+ goto drop_bad_frame;
|
|
+ }
|
|
+
|
|
+ skb_len = skb->len;
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_DBG_LOOP
|
|
+ if (dpa_skb_loop(priv, skb)) {
|
|
+ percpu_stats->rx_packets++;
|
|
+ percpu_stats->rx_bytes += skb_len;
|
|
+ return;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ if (use_gro) {
|
|
+ gro_result_t gro_result;
|
|
+ const struct qman_portal_config *pc =
|
|
+ qman_p_get_portal_config(portal);
|
|
+ struct dpa_napi_portal *np = &percpu_priv->np[pc->index];
|
|
+
|
|
+ np->p = portal;
|
|
+ gro_result = napi_gro_receive(&np->napi, skb);
|
|
+ /* If frame is dropped by the stack, rx_dropped counter is
|
|
+ * incremented automatically, so no need for us to update it
|
|
+ */
|
|
+ if (unlikely(gro_result == GRO_DROP))
|
|
+ goto packet_dropped;
|
|
+ } else if (unlikely(netif_receive_skb(skb) == NET_RX_DROP))
|
|
+ goto packet_dropped;
|
|
+
|
|
+ percpu_stats->rx_packets++;
|
|
+ percpu_stats->rx_bytes += skb_len;
|
|
+
|
|
+packet_dropped:
|
|
+ return;
|
|
+
|
|
+drop_bad_frame:
|
|
+ dev_kfree_skb(skb);
|
|
+ return;
|
|
+
|
|
+_release_frame:
|
|
+ dpa_fd_release(net_dev, fd);
|
|
+}
|
|
+
|
|
+int __hot skb_to_contig_fd(struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, struct qm_fd *fd,
|
|
+ int *count_ptr, int *offset)
|
|
+{
|
|
+ struct sk_buff **skbh;
|
|
+ dma_addr_t addr;
|
|
+ struct dpa_bp *dpa_bp = priv->dpa_bp;
|
|
+ struct net_device *net_dev = priv->net_dev;
|
|
+ int err;
|
|
+ enum dma_data_direction dma_dir;
|
|
+ unsigned char *buffer_start;
|
|
+ int dma_map_size;
|
|
+
|
|
+#ifndef CONFIG_FSL_DPAA_TS
|
|
+ /* Check recycling conditions; only if timestamp support is not
|
|
+ * enabled, otherwise we need the fd back on tx confirmation
|
|
+ */
|
|
+
|
|
+ /* We can recycle the buffer if:
|
|
+ * - the pool is not full
|
|
+ * - the buffer meets the skb recycling conditions
|
|
+ * - the buffer meets our own (size, offset, align) conditions
|
|
+ */
|
|
+ if (likely((*count_ptr < dpa_bp->target_count) &&
|
|
+ dpa_skb_is_recyclable(skb) &&
|
|
+ dpa_buf_is_recyclable(skb, dpa_bp->size,
|
|
+ priv->tx_headroom, &buffer_start))) {
|
|
+ /* Buffer is recyclable; use the new start address
|
|
+ * and set fd parameters and DMA mapping direction
|
|
+ */
|
|
+ fd->bpid = dpa_bp->bpid;
|
|
+ DPA_BUG_ON(skb->data - buffer_start > DPA_MAX_FD_OFFSET);
|
|
+ fd->offset = (uint16_t)(skb->data - buffer_start);
|
|
+ dma_dir = DMA_BIDIRECTIONAL;
|
|
+ dma_map_size = dpa_bp->size;
|
|
+
|
|
+ DPA_WRITE_SKB_PTR(skb, skbh, buffer_start, -1);
|
|
+ *offset = skb_headroom(skb) - fd->offset;
|
|
+ } else
|
|
+#endif
|
|
+ {
|
|
+ /* Not recyclable.
|
|
+ * We are guaranteed to have at least tx_headroom bytes
|
|
+ * available, so just use that for offset.
|
|
+ */
|
|
+ fd->bpid = 0xff;
|
|
+ buffer_start = skb->data - priv->tx_headroom;
|
|
+ fd->offset = priv->tx_headroom;
|
|
+ dma_dir = DMA_TO_DEVICE;
|
|
+ dma_map_size = skb_tail_pointer(skb) - buffer_start;
|
|
+
|
|
+ /* The buffer will be Tx-confirmed, but the TxConf cb must
|
|
+ * necessarily look at our Tx private data to retrieve the
|
|
+ * skbuff. (In short: can't use DPA_WRITE_SKB_PTR() here.)
|
|
+ */
|
|
+ DPA_WRITE_SKB_PTR(skb, skbh, buffer_start, 0);
|
|
+ }
|
|
+
|
|
+ /* Enable L3/L4 hardware checksum computation.
|
|
+ *
|
|
+ * We must do this before dma_map_single(DMA_TO_DEVICE), because we may
|
|
+ * need to write into the skb.
|
|
+ */
|
|
+ err = dpa_enable_tx_csum(priv, skb, fd,
|
|
+ ((char *)skbh) + DPA_TX_PRIV_DATA_SIZE);
|
|
+ if (unlikely(err < 0)) {
|
|
+ if (netif_msg_tx_err(priv) && net_ratelimit())
|
|
+ netdev_err(net_dev, "HW csum error: %d\n", err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* Fill in the rest of the FD fields */
|
|
+ fd->format = qm_fd_contig;
|
|
+ fd->length20 = skb->len;
|
|
+ fd->cmd |= FM_FD_CMD_FCO;
|
|
+
|
|
+ /* Map the entire buffer size that may be seen by FMan, but no more */
|
|
+ addr = dma_map_single(dpa_bp->dev, skbh, dma_map_size, dma_dir);
|
|
+ if (unlikely(dma_mapping_error(dpa_bp->dev, addr))) {
|
|
+ if (netif_msg_tx_err(priv) && net_ratelimit())
|
|
+ netdev_err(net_dev, "dma_map_single() failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ qm_fd_addr_set64(fd, addr);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(skb_to_contig_fd);
|
|
+
|
|
+#ifndef CONFIG_PPC
|
|
+struct sk_buff *split_skb_at_4k_boundaries(struct sk_buff *skb)
|
|
+{
|
|
+ unsigned int length, nr_frags, moved_len = 0;
|
|
+ u64 page_start;
|
|
+ struct page *page;
|
|
+ skb_frag_t *frag;
|
|
+ int i = 0, j = 0;
|
|
+
|
|
+ /* make sure skb is not shared */
|
|
+ skb = skb_share_check(skb, GFP_ATOMIC);
|
|
+ if (!skb)
|
|
+ return NULL;
|
|
+
|
|
+ nr_frags = skb_shinfo(skb)->nr_frags;
|
|
+ page_start = (u64)skb->data;
|
|
+
|
|
+ /* split the linear part at the first 4k boundary and create one (big)
|
|
+ * fragment with the rest
|
|
+ */
|
|
+ if (HAS_DMA_ISSUE(skb->data, skb_headlen(skb))) {
|
|
+ /* we'll add one more frag, make sure there's room */
|
|
+ if (nr_frags + 1 > DPA_SGT_MAX_ENTRIES)
|
|
+ return NULL;
|
|
+
|
|
+ /* next page boundary */
|
|
+ page_start = (page_start + 0x1000) & ~0xFFF;
|
|
+ page = virt_to_page(page_start);
|
|
+
|
|
+ /* move the rest of fragments to make room for a new one at j */
|
|
+ for (i = nr_frags - 1; i >= j; i--)
|
|
+ skb_shinfo(skb)->frags[i + 1] = skb_shinfo(skb)->frags[i];
|
|
+
|
|
+ /* move length bytes to a paged fragment at j */
|
|
+ length = min((u64)0x1000,
|
|
+ (u64)skb->data + skb_headlen(skb) - page_start);
|
|
+ skb->data_len += length;
|
|
+ moved_len += length;
|
|
+ skb_fill_page_desc(skb, j++, page, 0, length);
|
|
+ get_page(page);
|
|
+ skb_shinfo(skb)->nr_frags = ++nr_frags;
|
|
+ }
|
|
+ /* adjust the tail pointer */
|
|
+ skb->tail -= moved_len;
|
|
+ j = 0;
|
|
+
|
|
+ /* split any paged fragment that crosses a 4K boundary */
|
|
+ while (j < nr_frags) {
|
|
+ frag = &skb_shinfo(skb)->frags[j];
|
|
+
|
|
+ /* if there is a 4K boundary between the fragment's offset and end */
|
|
+ if (HAS_DMA_ISSUE(frag->page_offset, frag->size)) {
|
|
+ /* we'll add one more frag, make sure there's room */
|
|
+ if (nr_frags + 1 > DPA_SGT_MAX_ENTRIES)
|
|
+ return NULL;
|
|
+
|
|
+ /* new page boundary */
|
|
+ page_start = (u64)page_address(skb_frag_page(frag)) +
|
|
+ frag->page_offset + 0x1000;
|
|
+ page_start = (u64)page_start & ~0xFFF;
|
|
+ page = virt_to_page(page_start);
|
|
+
|
|
+ /* move the rest of fragments to make room for a new one at j+1 */
|
|
+ for (i = nr_frags - 1; i > j; i--)
|
|
+ skb_shinfo(skb)->frags[i + 1] =
|
|
+ skb_shinfo(skb)->frags[i];
|
|
+
|
|
+ /* move length bytes to a new paged fragment at j+1 */
|
|
+ length = (u64)page_address(skb_frag_page(frag)) +
|
|
+ frag->page_offset + frag->size - page_start;
|
|
+ frag->size -= length;
|
|
+ skb_fill_page_desc(skb, j + 1, page, 0, length);
|
|
+ get_page(page);
|
|
+ skb_shinfo(skb)->nr_frags = ++nr_frags;
|
|
+ }
|
|
+
|
|
+ /* move to next frag */
|
|
+ j++;
|
|
+ }
|
|
+
|
|
+ return skb;
|
|
+}
|
|
+#endif
|
|
+
|
|
+int __hot skb_to_sg_fd(struct dpa_priv_s *priv,
|
|
+ struct sk_buff *skb, struct qm_fd *fd)
|
|
+{
|
|
+ struct dpa_bp *dpa_bp = priv->dpa_bp;
|
|
+ dma_addr_t addr;
|
|
+ dma_addr_t sg_addr;
|
|
+ struct sk_buff **skbh;
|
|
+ struct net_device *net_dev = priv->net_dev;
|
|
+ int sg_len, sgt_size;
|
|
+ int err;
|
|
+
|
|
+ struct qm_sg_entry *sgt;
|
|
+ void *sgt_buf;
|
|
+ skb_frag_t *frag;
|
|
+ int i = 0, j = 0;
|
|
+ int nr_frags;
|
|
+ const enum dma_data_direction dma_dir = DMA_TO_DEVICE;
|
|
+
|
|
+ nr_frags = skb_shinfo(skb)->nr_frags;
|
|
+ fd->format = qm_fd_sg;
|
|
+
|
|
+ sgt_size = sizeof(struct qm_sg_entry) * (1 + nr_frags);
|
|
+
|
|
+ /* Get a page frag to store the SGTable, or a full page if the errata
|
|
+ * is in place and we need to avoid crossing a 4k boundary.
|
|
+ */
|
|
+#ifndef CONFIG_PPC
|
|
+ if (unlikely(dpaa_errata_a010022))
|
|
+ sgt_buf = page_address(alloc_page(GFP_ATOMIC));
|
|
+ else
|
|
+#endif
|
|
+ sgt_buf = netdev_alloc_frag(priv->tx_headroom + sgt_size);
|
|
+ if (unlikely(!sgt_buf)) {
|
|
+ dev_err(dpa_bp->dev, "netdev_alloc_frag() failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /* it seems that the memory allocator does not zero the allocated mem */
|
|
+ memset(sgt_buf, 0, priv->tx_headroom + sgt_size);
|
|
+
|
|
+ /* Enable L3/L4 hardware checksum computation.
|
|
+ *
|
|
+ * We must do this before dma_map_single(DMA_TO_DEVICE), because we may
|
|
+ * need to write into the skb.
|
|
+ */
|
|
+ err = dpa_enable_tx_csum(priv, skb, fd,
|
|
+ sgt_buf + DPA_TX_PRIV_DATA_SIZE);
|
|
+ if (unlikely(err < 0)) {
|
|
+ if (netif_msg_tx_err(priv) && net_ratelimit())
|
|
+ netdev_err(net_dev, "HW csum error: %d\n", err);
|
|
+ goto csum_failed;
|
|
+ }
|
|
+
|
|
+ /* Assign the data from skb->data to the first SG list entry */
|
|
+ sgt = (struct qm_sg_entry *)(sgt_buf + priv->tx_headroom);
|
|
+ sg_len = skb_headlen(skb);
|
|
+ qm_sg_entry_set_bpid(&sgt[0], 0xff);
|
|
+ qm_sg_entry_set_offset(&sgt[0], 0);
|
|
+ qm_sg_entry_set_len(&sgt[0], sg_len);
|
|
+ qm_sg_entry_set_ext(&sgt[0], 0);
|
|
+ qm_sg_entry_set_final(&sgt[0], 0);
|
|
+
|
|
+ addr = dma_map_single(dpa_bp->dev, skb->data, sg_len, dma_dir);
|
|
+ if (unlikely(dma_mapping_error(dpa_bp->dev, addr))) {
|
|
+ dev_err(dpa_bp->dev, "DMA mapping failed");
|
|
+ err = -EINVAL;
|
|
+ goto sg0_map_failed;
|
|
+ }
|
|
+
|
|
+ qm_sg_entry_set64(&sgt[0], addr);
|
|
+
|
|
+ /* populate the rest of SGT entries */
|
|
+ for (i = 1; i <= nr_frags; i++) {
|
|
+ frag = &skb_shinfo(skb)->frags[i - 1];
|
|
+ qm_sg_entry_set_bpid(&sgt[i], 0xff);
|
|
+ qm_sg_entry_set_offset(&sgt[i], 0);
|
|
+ qm_sg_entry_set_len(&sgt[i], frag->size);
|
|
+ qm_sg_entry_set_ext(&sgt[i], 0);
|
|
+
|
|
+ if (i == nr_frags)
|
|
+ qm_sg_entry_set_final(&sgt[i], 1);
|
|
+ else
|
|
+ qm_sg_entry_set_final(&sgt[i], 0);
|
|
+
|
|
+ DPA_BUG_ON(!skb_frag_page(frag));
|
|
+ addr = skb_frag_dma_map(dpa_bp->dev, frag, 0, frag->size,
|
|
+ dma_dir);
|
|
+ if (unlikely(dma_mapping_error(dpa_bp->dev, addr))) {
|
|
+ dev_err(dpa_bp->dev, "DMA mapping failed");
|
|
+ err = -EINVAL;
|
|
+ goto sg_map_failed;
|
|
+ }
|
|
+
|
|
+ /* keep the offset in the address */
|
|
+ qm_sg_entry_set64(&sgt[i], addr);
|
|
+ }
|
|
+
|
|
+ fd->length20 = skb->len;
|
|
+ fd->offset = priv->tx_headroom;
|
|
+
|
|
+ /* DMA map the SGT page */
|
|
+ DPA_WRITE_SKB_PTR(skb, skbh, sgt_buf, 0);
|
|
+ addr = dma_map_single(dpa_bp->dev, sgt_buf,
|
|
+ priv->tx_headroom + sgt_size,
|
|
+ dma_dir);
|
|
+
|
|
+ if (unlikely(dma_mapping_error(dpa_bp->dev, addr))) {
|
|
+ dev_err(dpa_bp->dev, "DMA mapping failed");
|
|
+ err = -EINVAL;
|
|
+ goto sgt_map_failed;
|
|
+ }
|
|
+
|
|
+ qm_fd_addr_set64(fd, addr);
|
|
+ fd->bpid = 0xff;
|
|
+ fd->cmd |= FM_FD_CMD_FCO;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+sgt_map_failed:
|
|
+sg_map_failed:
|
|
+ for (j = 0; j < i; j++) {
|
|
+ sg_addr = qm_sg_addr(&sgt[j]);
|
|
+ dma_unmap_page(dpa_bp->dev, sg_addr,
|
|
+ qm_sg_entry_get_len(&sgt[j]), dma_dir);
|
|
+ }
|
|
+sg0_map_failed:
|
|
+csum_failed:
|
|
+ put_page(virt_to_head_page(sgt_buf));
|
|
+
|
|
+ return err;
|
|
+}
|
|
+EXPORT_SYMBOL(skb_to_sg_fd);
|
|
+
|
|
+int __hot dpa_tx(struct sk_buff *skb, struct net_device *net_dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv;
|
|
+ const int queue_mapping = dpa_get_queue_mapping(skb);
|
|
+ struct qman_fq *egress_fq, *conf_fq;
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_HOOKS
|
|
+ /* If there is a Tx hook, run it. */
|
|
+ if (dpaa_eth_hooks.tx &&
|
|
+ dpaa_eth_hooks.tx(skb, net_dev) == DPAA_ETH_STOLEN)
|
|
+ /* won't update any Tx stats */
|
|
+ return NETDEV_TX_OK;
|
|
+#endif
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_CEETM
|
|
+ if (priv->ceetm_en)
|
|
+ return ceetm_tx(skb, net_dev);
|
|
+#endif
|
|
+
|
|
+ egress_fq = priv->egress_fqs[queue_mapping];
|
|
+ conf_fq = priv->conf_fqs[queue_mapping];
|
|
+
|
|
+ return dpa_tx_extended(skb, net_dev, egress_fq, conf_fq);
|
|
+}
|
|
+
|
|
+int __hot dpa_tx_extended(struct sk_buff *skb, struct net_device *net_dev,
|
|
+ struct qman_fq *egress_fq, struct qman_fq *conf_fq)
|
|
+{
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct qm_fd fd;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ struct rtnl_link_stats64 *percpu_stats;
|
|
+ int err = 0;
|
|
+ const bool nonlinear = skb_is_nonlinear(skb);
|
|
+ int *countptr, offset = 0;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ /* Non-migratable context, safe to use raw_cpu_ptr */
|
|
+ percpu_priv = raw_cpu_ptr(priv->percpu_priv);
|
|
+ percpu_stats = &percpu_priv->stats;
|
|
+ countptr = raw_cpu_ptr(priv->dpa_bp->percpu_count);
|
|
+
|
|
+ clear_fd(&fd);
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ if (priv->tsu && priv->tsu->valid && priv->tsu->hwts_tx_en_ioctl)
|
|
+ fd.cmd |= FM_FD_CMD_UPD;
|
|
+#endif
|
|
+#ifdef CONFIG_FSL_DPAA_TS
|
|
+ if (unlikely(priv->ts_tx_en &&
|
|
+ skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP))
|
|
+ fd.cmd |= FM_FD_CMD_UPD;
|
|
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
|
|
+#endif /* CONFIG_FSL_DPAA_TS */
|
|
+
|
|
+#ifndef CONFIG_PPC
|
|
+ if (unlikely(dpaa_errata_a010022)) {
|
|
+ skb = split_skb_at_4k_boundaries(skb);
|
|
+ if (!skb)
|
|
+ goto skb_to_fd_failed;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ /* MAX_SKB_FRAGS is larger than our DPA_SGT_MAX_ENTRIES; make sure
|
|
+ * we don't feed FMan with more fragments than it supports.
|
|
+ * Btw, we're using the first sgt entry to store the linear part of
|
|
+ * the skb, so we're one extra frag short.
|
|
+ */
|
|
+ if (nonlinear &&
|
|
+ likely(skb_shinfo(skb)->nr_frags < DPA_SGT_MAX_ENTRIES)) {
|
|
+ /* Just create a S/G fd based on the skb */
|
|
+ err = skb_to_sg_fd(priv, skb, &fd);
|
|
+ percpu_priv->tx_frag_skbuffs++;
|
|
+ } else {
|
|
+ /* Make sure we have enough headroom to accommodate private
|
|
+ * data, parse results, etc. Normally this shouldn't happen if
|
|
+ * we're here via the standard kernel stack.
|
|
+ */
|
|
+ if (unlikely(skb_headroom(skb) < priv->tx_headroom)) {
|
|
+ struct sk_buff *skb_new;
|
|
+
|
|
+ skb_new = skb_realloc_headroom(skb, priv->tx_headroom);
|
|
+ if (unlikely(!skb_new)) {
|
|
+ dev_kfree_skb(skb);
|
|
+ percpu_stats->tx_errors++;
|
|
+ return NETDEV_TX_OK;
|
|
+ }
|
|
+ dev_kfree_skb(skb);
|
|
+ skb = skb_new;
|
|
+ }
|
|
+
|
|
+ /* We're going to store the skb backpointer at the beginning
|
|
+ * of the data buffer, so we need a privately owned skb
|
|
+ */
|
|
+
|
|
+ /* Code borrowed from skb_unshare(). */
|
|
+ if (skb_cloned(skb)) {
|
|
+ struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC);
|
|
+ kfree_skb(skb);
|
|
+ skb = nskb;
|
|
+ /* skb_copy() has now linearized the skbuff. */
|
|
+ } else if (unlikely(nonlinear)) {
|
|
+ /* We are here because the egress skb contains
|
|
+ * more fragments than we support. In this case,
|
|
+ * we have no choice but to linearize it ourselves.
|
|
+ */
|
|
+ err = __skb_linearize(skb);
|
|
+ }
|
|
+ if (unlikely(!skb || err < 0))
|
|
+ /* Common out-of-memory error path */
|
|
+ goto enomem;
|
|
+
|
|
+ err = skb_to_contig_fd(priv, skb, &fd, countptr, &offset);
|
|
+ }
|
|
+ if (unlikely(err < 0))
|
|
+ goto skb_to_fd_failed;
|
|
+
|
|
+ if (fd.bpid != 0xff) {
|
|
+ skb_recycle(skb);
|
|
+ /* skb_recycle() reserves NET_SKB_PAD as skb headroom,
|
|
+ * but we need the skb to look as if returned by build_skb().
|
|
+ * We need to manually adjust the tailptr as well.
|
|
+ */
|
|
+ skb->data = skb->head + offset;
|
|
+ skb_reset_tail_pointer(skb);
|
|
+
|
|
+ (*countptr)++;
|
|
+ percpu_priv->tx_returned++;
|
|
+ }
|
|
+
|
|
+ if (unlikely(dpa_xmit(priv, percpu_stats, &fd, egress_fq, conf_fq) < 0))
|
|
+ goto xmit_failed;
|
|
+
|
|
+ return NETDEV_TX_OK;
|
|
+
|
|
+xmit_failed:
|
|
+ if (fd.bpid != 0xff) {
|
|
+ (*countptr)--;
|
|
+ percpu_priv->tx_returned--;
|
|
+ dpa_fd_release(net_dev, &fd);
|
|
+ percpu_stats->tx_errors++;
|
|
+ return NETDEV_TX_OK;
|
|
+ }
|
|
+ _dpa_cleanup_tx_fd(priv, &fd);
|
|
+skb_to_fd_failed:
|
|
+enomem:
|
|
+ percpu_stats->tx_errors++;
|
|
+ dev_kfree_skb(skb);
|
|
+ return NETDEV_TX_OK;
|
|
+}
|
|
+EXPORT_SYMBOL(dpa_tx_extended);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_sysfs.c
|
|
@@ -0,0 +1,278 @@
|
|
+/* Copyright 2008-2012 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/of_net.h>
|
|
+#include "dpaa_eth.h"
|
|
+#include "mac.h" /* struct mac_device */
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+#include "dpaa_1588.h"
|
|
+#endif
|
|
+
|
|
+static ssize_t dpaa_eth_show_addr(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+
|
|
+ if (mac_dev)
|
|
+ return sprintf(buf, "%llx",
|
|
+ (unsigned long long)mac_dev->res->start);
|
|
+ else
|
|
+ return sprintf(buf, "none");
|
|
+}
|
|
+
|
|
+static ssize_t dpaa_eth_show_type(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+ ssize_t res = 0;
|
|
+
|
|
+ if (priv)
|
|
+ res = sprintf(buf, "%s", priv->if_type);
|
|
+
|
|
+ return res;
|
|
+}
|
|
+
|
|
+static ssize_t dpaa_eth_show_fqids(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+ ssize_t bytes = 0;
|
|
+ int i = 0;
|
|
+ char *str;
|
|
+ struct dpa_fq *fq;
|
|
+ struct dpa_fq *tmp;
|
|
+ struct dpa_fq *prev = NULL;
|
|
+ u32 first_fqid = 0;
|
|
+ u32 last_fqid = 0;
|
|
+ char *prevstr = NULL;
|
|
+
|
|
+ list_for_each_entry_safe(fq, tmp, &priv->dpa_fq_list, list) {
|
|
+ switch (fq->fq_type) {
|
|
+ case FQ_TYPE_RX_DEFAULT:
|
|
+ str = "Rx default";
|
|
+ break;
|
|
+ case FQ_TYPE_RX_ERROR:
|
|
+ str = "Rx error";
|
|
+ break;
|
|
+ case FQ_TYPE_RX_PCD:
|
|
+ str = "Rx PCD";
|
|
+ break;
|
|
+ case FQ_TYPE_TX_CONFIRM:
|
|
+ str = "Tx default confirmation";
|
|
+ break;
|
|
+ case FQ_TYPE_TX_CONF_MQ:
|
|
+ str = "Tx confirmation (mq)";
|
|
+ break;
|
|
+ case FQ_TYPE_TX_ERROR:
|
|
+ str = "Tx error";
|
|
+ break;
|
|
+ case FQ_TYPE_TX:
|
|
+ str = "Tx";
|
|
+ break;
|
|
+ case FQ_TYPE_RX_PCD_HI_PRIO:
|
|
+ str ="Rx PCD High Priority";
|
|
+ break;
|
|
+ default:
|
|
+ str = "Unknown";
|
|
+ }
|
|
+
|
|
+ if (prev && (abs(fq->fqid - prev->fqid) != 1 ||
|
|
+ str != prevstr)) {
|
|
+ if (last_fqid == first_fqid)
|
|
+ bytes += sprintf(buf + bytes,
|
|
+ "%s: %d\n", prevstr, prev->fqid);
|
|
+ else
|
|
+ bytes += sprintf(buf + bytes,
|
|
+ "%s: %d - %d\n", prevstr,
|
|
+ first_fqid, last_fqid);
|
|
+ }
|
|
+
|
|
+ if (prev && abs(fq->fqid - prev->fqid) == 1 && str == prevstr)
|
|
+ last_fqid = fq->fqid;
|
|
+ else
|
|
+ first_fqid = last_fqid = fq->fqid;
|
|
+
|
|
+ prev = fq;
|
|
+ prevstr = str;
|
|
+ i++;
|
|
+ }
|
|
+
|
|
+ if (prev) {
|
|
+ if (last_fqid == first_fqid)
|
|
+ bytes += sprintf(buf + bytes, "%s: %d\n", prevstr,
|
|
+ prev->fqid);
|
|
+ else
|
|
+ bytes += sprintf(buf + bytes, "%s: %d - %d\n", prevstr,
|
|
+ first_fqid, last_fqid);
|
|
+ }
|
|
+
|
|
+ return bytes;
|
|
+}
|
|
+
|
|
+static ssize_t dpaa_eth_show_bpids(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ ssize_t bytes = 0;
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+ struct dpa_bp *dpa_bp = priv->dpa_bp;
|
|
+ int i = 0;
|
|
+
|
|
+ for (i = 0; i < priv->bp_count; i++)
|
|
+ bytes += snprintf(buf + bytes, PAGE_SIZE, "%u\n",
|
|
+ dpa_bp[i].bpid);
|
|
+
|
|
+ return bytes;
|
|
+}
|
|
+
|
|
+static ssize_t dpaa_eth_show_mac_regs(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+ int n = 0;
|
|
+
|
|
+ if (mac_dev)
|
|
+ n = fm_mac_dump_regs(mac_dev, buf, n);
|
|
+ else
|
|
+ return sprintf(buf, "no mac registers\n");
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static ssize_t dpaa_eth_show_mac_rx_stats(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+ int n = 0;
|
|
+
|
|
+ if (mac_dev)
|
|
+ n = fm_mac_dump_rx_stats(mac_dev, buf, n);
|
|
+ else
|
|
+ return sprintf(buf, "no mac rx stats\n");
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static ssize_t dpaa_eth_show_mac_tx_stats(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+ int n = 0;
|
|
+
|
|
+ if (mac_dev)
|
|
+ n = fm_mac_dump_tx_stats(mac_dev, buf, n);
|
|
+ else
|
|
+ return sprintf(buf, "no mac tx stats\n");
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+static ssize_t dpaa_eth_show_ptp_1588(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+
|
|
+ if (priv->tsu && priv->tsu->valid)
|
|
+ return sprintf(buf, "1\n");
|
|
+ else
|
|
+ return sprintf(buf, "0\n");
|
|
+}
|
|
+
|
|
+static ssize_t dpaa_eth_set_ptp_1588(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(to_net_dev(dev));
|
|
+ unsigned int num;
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (kstrtouint(buf, 0, &num) < 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ local_irq_save(flags);
|
|
+
|
|
+ if (num) {
|
|
+ if (priv->tsu)
|
|
+ priv->tsu->valid = TRUE;
|
|
+ } else {
|
|
+ if (priv->tsu)
|
|
+ priv->tsu->valid = FALSE;
|
|
+ }
|
|
+
|
|
+ local_irq_restore(flags);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static struct device_attribute dpaa_eth_attrs[] = {
|
|
+ __ATTR(device_addr, S_IRUGO, dpaa_eth_show_addr, NULL),
|
|
+ __ATTR(device_type, S_IRUGO, dpaa_eth_show_type, NULL),
|
|
+ __ATTR(fqids, S_IRUGO, dpaa_eth_show_fqids, NULL),
|
|
+ __ATTR(bpids, S_IRUGO, dpaa_eth_show_bpids, NULL),
|
|
+ __ATTR(mac_regs, S_IRUGO, dpaa_eth_show_mac_regs, NULL),
|
|
+ __ATTR(mac_rx_stats, S_IRUGO, dpaa_eth_show_mac_rx_stats, NULL),
|
|
+ __ATTR(mac_tx_stats, S_IRUGO, dpaa_eth_show_mac_tx_stats, NULL),
|
|
+#ifdef CONFIG_FSL_DPAA_1588
|
|
+ __ATTR(ptp_1588, S_IRUGO | S_IWUSR, dpaa_eth_show_ptp_1588,
|
|
+ dpaa_eth_set_ptp_1588),
|
|
+#endif
|
|
+};
|
|
+
|
|
+void dpaa_eth_sysfs_init(struct device *dev)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(dpaa_eth_attrs); i++)
|
|
+ if (device_create_file(dev, &dpaa_eth_attrs[i])) {
|
|
+ dev_err(dev, "Error creating sysfs file\n");
|
|
+ while (i > 0)
|
|
+ device_remove_file(dev, &dpaa_eth_attrs[--i]);
|
|
+ return;
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL(dpaa_eth_sysfs_init);
|
|
+
|
|
+void dpaa_eth_sysfs_remove(struct device *dev)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(dpaa_eth_attrs); i++)
|
|
+ device_remove_file(dev, &dpaa_eth_attrs[i]);
|
|
+}
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_eth_trace.h
|
|
@@ -0,0 +1,144 @@
|
|
+/* Copyright 2013 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#undef TRACE_SYSTEM
|
|
+#define TRACE_SYSTEM dpaa_eth
|
|
+
|
|
+#if !defined(_DPAA_ETH_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
|
+#define _DPAA_ETH_TRACE_H
|
|
+
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include "dpaa_eth.h"
|
|
+#include <linux/tracepoint.h>
|
|
+
|
|
+#define fd_format_name(format) { qm_fd_##format, #format }
|
|
+#define fd_format_list \
|
|
+ fd_format_name(contig), \
|
|
+ fd_format_name(sg)
|
|
+#define TR_FMT "[%s] fqid=%d, fd: addr=0x%llx, format=%s, off=%u, len=%u," \
|
|
+ " status=0x%08x"
|
|
+
|
|
+/* This is used to declare a class of events.
|
|
+ * individual events of this type will be defined below.
|
|
+ */
|
|
+
|
|
+/* Store details about a frame descriptor and the FQ on which it was
|
|
+ * transmitted/received.
|
|
+ */
|
|
+DECLARE_EVENT_CLASS(dpaa_eth_fd,
|
|
+ /* Trace function prototype */
|
|
+ TP_PROTO(struct net_device *netdev,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_fd *fd),
|
|
+
|
|
+ /* Repeat argument list here */
|
|
+ TP_ARGS(netdev, fq, fd),
|
|
+
|
|
+ /* A structure containing the relevant information we want to record.
|
|
+ * Declare name and type for each normal element, name, type and size
|
|
+ * for arrays. Use __string for variable length strings.
|
|
+ */
|
|
+ TP_STRUCT__entry(
|
|
+ __field(u32, fqid)
|
|
+ __field(u64, fd_addr)
|
|
+ __field(u8, fd_format)
|
|
+ __field(u16, fd_offset)
|
|
+ __field(u32, fd_length)
|
|
+ __field(u32, fd_status)
|
|
+ __string(name, netdev->name)
|
|
+ ),
|
|
+
|
|
+ /* The function that assigns values to the above declared fields */
|
|
+ TP_fast_assign(
|
|
+ __entry->fqid = fq->fqid;
|
|
+ __entry->fd_addr = qm_fd_addr_get64(fd);
|
|
+ __entry->fd_format = fd->format;
|
|
+ __entry->fd_offset = dpa_fd_offset(fd);
|
|
+ __entry->fd_length = dpa_fd_length(fd);
|
|
+ __entry->fd_status = fd->status;
|
|
+ __assign_str(name, netdev->name);
|
|
+ ),
|
|
+
|
|
+ /* This is what gets printed when the trace event is triggered */
|
|
+ /* TODO: print the status using __print_flags() */
|
|
+ TP_printk(TR_FMT,
|
|
+ __get_str(name), __entry->fqid, __entry->fd_addr,
|
|
+ __print_symbolic(__entry->fd_format, fd_format_list),
|
|
+ __entry->fd_offset, __entry->fd_length, __entry->fd_status)
|
|
+);
|
|
+
|
|
+/* Now declare events of the above type. Format is:
|
|
+ * DEFINE_EVENT(class, name, proto, args), with proto and args same as for class
|
|
+ */
|
|
+
|
|
+/* Tx (egress) fd */
|
|
+DEFINE_EVENT(dpaa_eth_fd, dpa_tx_fd,
|
|
+
|
|
+ TP_PROTO(struct net_device *netdev,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_fd *fd),
|
|
+
|
|
+ TP_ARGS(netdev, fq, fd)
|
|
+);
|
|
+
|
|
+/* Rx fd */
|
|
+DEFINE_EVENT(dpaa_eth_fd, dpa_rx_fd,
|
|
+
|
|
+ TP_PROTO(struct net_device *netdev,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_fd *fd),
|
|
+
|
|
+ TP_ARGS(netdev, fq, fd)
|
|
+);
|
|
+
|
|
+/* Tx confirmation fd */
|
|
+DEFINE_EVENT(dpaa_eth_fd, dpa_tx_conf_fd,
|
|
+
|
|
+ TP_PROTO(struct net_device *netdev,
|
|
+ struct qman_fq *fq,
|
|
+ const struct qm_fd *fd),
|
|
+
|
|
+ TP_ARGS(netdev, fq, fd)
|
|
+);
|
|
+
|
|
+/* If only one event of a certain type needs to be declared, use TRACE_EVENT().
|
|
+ * The syntax is the same as for DECLARE_EVENT_CLASS().
|
|
+ */
|
|
+
|
|
+#endif /* _DPAA_ETH_TRACE_H */
|
|
+
|
|
+/* This must be outside ifdef _DPAA_ETH_TRACE_H */
|
|
+#undef TRACE_INCLUDE_PATH
|
|
+#define TRACE_INCLUDE_PATH .
|
|
+#undef TRACE_INCLUDE_FILE
|
|
+#define TRACE_INCLUDE_FILE dpaa_eth_trace
|
|
+#include <trace/define_trace.h>
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_ethtool.c
|
|
@@ -0,0 +1,544 @@
|
|
+/* Copyright 2008-2012 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": %s:%hu:%s() " fmt, \
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__
|
|
+#else
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": " fmt
|
|
+#endif
|
|
+
|
|
+#include <linux/string.h>
|
|
+
|
|
+#include "dpaa_eth.h"
|
|
+#include "mac.h" /* struct mac_device */
|
|
+#include "dpaa_eth_common.h"
|
|
+
|
|
+static const char dpa_stats_percpu[][ETH_GSTRING_LEN] = {
|
|
+ "interrupts",
|
|
+ "rx packets",
|
|
+ "tx packets",
|
|
+ "tx recycled",
|
|
+ "tx confirm",
|
|
+ "tx S/G",
|
|
+ "rx S/G",
|
|
+ "tx error",
|
|
+ "rx error",
|
|
+ "bp count"
|
|
+};
|
|
+
|
|
+static char dpa_stats_global[][ETH_GSTRING_LEN] = {
|
|
+ /* dpa rx errors */
|
|
+ "rx dma error",
|
|
+ "rx frame physical error",
|
|
+ "rx frame size error",
|
|
+ "rx header error",
|
|
+ "rx csum error",
|
|
+
|
|
+ /* demultiplexing errors */
|
|
+ "qman cg_tdrop",
|
|
+ "qman wred",
|
|
+ "qman error cond",
|
|
+ "qman early window",
|
|
+ "qman late window",
|
|
+ "qman fq tdrop",
|
|
+ "qman fq retired",
|
|
+ "qman orp disabled",
|
|
+
|
|
+ /* congestion related stats */
|
|
+ "congestion time (ms)",
|
|
+ "entered congestion",
|
|
+ "congested (0/1)"
|
|
+};
|
|
+
|
|
+#define DPA_STATS_PERCPU_LEN ARRAY_SIZE(dpa_stats_percpu)
|
|
+#define DPA_STATS_GLOBAL_LEN ARRAY_SIZE(dpa_stats_global)
|
|
+
|
|
+static int __cold dpa_get_settings(struct net_device *net_dev,
|
|
+ struct ethtool_cmd *et_cmd)
|
|
+{
|
|
+ int _errno;
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ if (priv->mac_dev == NULL) {
|
|
+ netdev_info(net_dev, "This is a MAC-less interface\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ if (unlikely(priv->mac_dev->phy_dev == NULL)) {
|
|
+ netdev_dbg(net_dev, "phy device not initialized\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ _errno = phy_ethtool_gset(priv->mac_dev->phy_dev, et_cmd);
|
|
+ if (unlikely(_errno < 0))
|
|
+ netdev_err(net_dev, "phy_ethtool_gset() = %d\n", _errno);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static int __cold dpa_set_settings(struct net_device *net_dev,
|
|
+ struct ethtool_cmd *et_cmd)
|
|
+{
|
|
+ int _errno;
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ if (priv->mac_dev == NULL) {
|
|
+ netdev_info(net_dev, "This is a MAC-less interface\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ if (unlikely(priv->mac_dev->phy_dev == NULL)) {
|
|
+ netdev_err(net_dev, "phy device not initialized\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ _errno = phy_ethtool_sset(priv->mac_dev->phy_dev, et_cmd);
|
|
+ if (unlikely(_errno < 0))
|
|
+ netdev_err(net_dev, "phy_ethtool_sset() = %d\n", _errno);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static void __cold dpa_get_drvinfo(struct net_device *net_dev,
|
|
+ struct ethtool_drvinfo *drvinfo)
|
|
+{
|
|
+ int _errno;
|
|
+
|
|
+ strncpy(drvinfo->driver, KBUILD_MODNAME,
|
|
+ sizeof(drvinfo->driver) - 1)[sizeof(drvinfo->driver)-1] = 0;
|
|
+ _errno = snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version),
|
|
+ "%X", 0);
|
|
+
|
|
+ if (unlikely(_errno >= sizeof(drvinfo->fw_version))) {
|
|
+ /* Truncated output */
|
|
+ netdev_notice(net_dev, "snprintf() = %d\n", _errno);
|
|
+ } else if (unlikely(_errno < 0)) {
|
|
+ netdev_warn(net_dev, "snprintf() = %d\n", _errno);
|
|
+ memset(drvinfo->fw_version, 0, sizeof(drvinfo->fw_version));
|
|
+ }
|
|
+ strncpy(drvinfo->bus_info, dev_name(net_dev->dev.parent->parent),
|
|
+ sizeof(drvinfo->bus_info)-1)[sizeof(drvinfo->bus_info)-1] = 0;
|
|
+}
|
|
+
|
|
+static uint32_t __cold dpa_get_msglevel(struct net_device *net_dev)
|
|
+{
|
|
+ return ((struct dpa_priv_s *)netdev_priv(net_dev))->msg_enable;
|
|
+}
|
|
+
|
|
+static void __cold dpa_set_msglevel(struct net_device *net_dev,
|
|
+ uint32_t msg_enable)
|
|
+{
|
|
+ ((struct dpa_priv_s *)netdev_priv(net_dev))->msg_enable = msg_enable;
|
|
+}
|
|
+
|
|
+static int __cold dpa_nway_reset(struct net_device *net_dev)
|
|
+{
|
|
+ int _errno;
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+
|
|
+ if (priv->mac_dev == NULL) {
|
|
+ netdev_info(net_dev, "This is a MAC-less interface\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ if (unlikely(priv->mac_dev->phy_dev == NULL)) {
|
|
+ netdev_err(net_dev, "phy device not initialized\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ _errno = 0;
|
|
+ if (priv->mac_dev->phy_dev->autoneg) {
|
|
+ _errno = phy_start_aneg(priv->mac_dev->phy_dev);
|
|
+ if (unlikely(_errno < 0))
|
|
+ netdev_err(net_dev, "phy_start_aneg() = %d\n",
|
|
+ _errno);
|
|
+ }
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static void __cold dpa_get_pauseparam(struct net_device *net_dev,
|
|
+ struct ethtool_pauseparam *epause)
|
|
+{
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct mac_device *mac_dev;
|
|
+ struct phy_device *phy_dev;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ mac_dev = priv->mac_dev;
|
|
+
|
|
+ if (mac_dev == NULL) {
|
|
+ netdev_info(net_dev, "This is a MAC-less interface\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ phy_dev = mac_dev->phy_dev;
|
|
+ if (unlikely(phy_dev == NULL)) {
|
|
+ netdev_err(net_dev, "phy device not initialized\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ epause->autoneg = mac_dev->autoneg_pause;
|
|
+ epause->rx_pause = mac_dev->rx_pause_active;
|
|
+ epause->tx_pause = mac_dev->tx_pause_active;
|
|
+}
|
|
+
|
|
+static int __cold dpa_set_pauseparam(struct net_device *net_dev,
|
|
+ struct ethtool_pauseparam *epause)
|
|
+{
|
|
+ struct dpa_priv_s *priv;
|
|
+ struct mac_device *mac_dev;
|
|
+ struct phy_device *phy_dev;
|
|
+ int _errno;
|
|
+ u32 newadv, oldadv;
|
|
+ bool rx_pause, tx_pause;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ mac_dev = priv->mac_dev;
|
|
+
|
|
+ if (mac_dev == NULL) {
|
|
+ netdev_info(net_dev, "This is a MAC-less interface\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ phy_dev = mac_dev->phy_dev;
|
|
+ if (unlikely(phy_dev == NULL)) {
|
|
+ netdev_err(net_dev, "phy device not initialized\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (!(phy_dev->supported & SUPPORTED_Pause) ||
|
|
+ (!(phy_dev->supported & SUPPORTED_Asym_Pause) &&
|
|
+ (epause->rx_pause != epause->tx_pause)))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* The MAC should know how to handle PAUSE frame autonegotiation before
|
|
+ * adjust_link is triggered by a forced renegotiation of sym/asym PAUSE
|
|
+ * settings.
|
|
+ */
|
|
+ mac_dev->autoneg_pause = !!epause->autoneg;
|
|
+ mac_dev->rx_pause_req = !!epause->rx_pause;
|
|
+ mac_dev->tx_pause_req = !!epause->tx_pause;
|
|
+
|
|
+ /* Determine the sym/asym advertised PAUSE capabilities from the desired
|
|
+ * rx/tx pause settings.
|
|
+ */
|
|
+ newadv = 0;
|
|
+ if (epause->rx_pause)
|
|
+ newadv = ADVERTISED_Pause | ADVERTISED_Asym_Pause;
|
|
+ if (epause->tx_pause)
|
|
+ newadv |= ADVERTISED_Asym_Pause;
|
|
+
|
|
+ oldadv = phy_dev->advertising &
|
|
+ (ADVERTISED_Pause | ADVERTISED_Asym_Pause);
|
|
+
|
|
+ /* If there are differences between the old and the new advertised
|
|
+ * values, restart PHY autonegotiation and advertise the new values.
|
|
+ */
|
|
+ if (oldadv != newadv) {
|
|
+ phy_dev->advertising &= ~(ADVERTISED_Pause
|
|
+ | ADVERTISED_Asym_Pause);
|
|
+ phy_dev->advertising |= newadv;
|
|
+ if (phy_dev->autoneg) {
|
|
+ _errno = phy_start_aneg(phy_dev);
|
|
+ if (unlikely(_errno < 0))
|
|
+ netdev_err(net_dev, "phy_start_aneg() = %d\n",
|
|
+ _errno);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ get_pause_cfg(mac_dev, &rx_pause, &tx_pause);
|
|
+ _errno = set_mac_active_pause(mac_dev, rx_pause, tx_pause);
|
|
+ if (unlikely(_errno < 0))
|
|
+ netdev_err(net_dev, "set_mac_active_pause() = %d\n", _errno);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static void dpa_get_wol(struct net_device *net_dev, struct ethtool_wolinfo *wol)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+
|
|
+ wol->supported = 0;
|
|
+ wol->wolopts = 0;
|
|
+
|
|
+ if (!priv->wol || !device_can_wakeup(net_dev->dev.parent))
|
|
+ return;
|
|
+
|
|
+ if (priv->wol & DPAA_WOL_MAGIC) {
|
|
+ wol->supported = WAKE_MAGIC;
|
|
+ wol->wolopts = WAKE_MAGIC;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int dpa_set_wol(struct net_device *net_dev, struct ethtool_wolinfo *wol)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+
|
|
+ if (priv->mac_dev == NULL) {
|
|
+ netdev_info(net_dev, "This is a MAC-less interface\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (unlikely(priv->mac_dev->phy_dev == NULL)) {
|
|
+ netdev_dbg(net_dev, "phy device not initialized\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (!device_can_wakeup(net_dev->dev.parent) ||
|
|
+ (wol->wolopts & ~WAKE_MAGIC))
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ priv->wol = 0;
|
|
+
|
|
+ if (wol->wolopts & WAKE_MAGIC) {
|
|
+ priv->wol = DPAA_WOL_MAGIC;
|
|
+ device_set_wakeup_enable(net_dev->dev.parent, 1);
|
|
+ } else {
|
|
+ device_set_wakeup_enable(net_dev->dev.parent, 0);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int dpa_get_eee(struct net_device *net_dev, struct ethtool_eee *et_eee)
|
|
+{
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ if (priv->mac_dev == NULL) {
|
|
+ netdev_info(net_dev, "This is a MAC-less interface\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (unlikely(priv->mac_dev->phy_dev == NULL)) {
|
|
+ netdev_err(net_dev, "phy device not initialized\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ return phy_ethtool_get_eee(priv->mac_dev->phy_dev, et_eee);
|
|
+}
|
|
+
|
|
+static int dpa_set_eee(struct net_device *net_dev, struct ethtool_eee *et_eee)
|
|
+{
|
|
+ struct dpa_priv_s *priv;
|
|
+
|
|
+ priv = netdev_priv(net_dev);
|
|
+ if (priv->mac_dev == NULL) {
|
|
+ netdev_info(net_dev, "This is a MAC-less interface\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (unlikely(priv->mac_dev->phy_dev == NULL)) {
|
|
+ netdev_err(net_dev, "phy device not initialized\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ return phy_ethtool_set_eee(priv->mac_dev->phy_dev, et_eee);
|
|
+}
|
|
+
|
|
+static int dpa_get_sset_count(struct net_device *net_dev, int type)
|
|
+{
|
|
+ unsigned int total_stats, num_stats;
|
|
+
|
|
+ num_stats = num_online_cpus() + 1;
|
|
+ total_stats = num_stats * DPA_STATS_PERCPU_LEN + DPA_STATS_GLOBAL_LEN;
|
|
+
|
|
+ switch (type) {
|
|
+ case ETH_SS_STATS:
|
|
+ return total_stats;
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void copy_stats(struct dpa_percpu_priv_s *percpu_priv, int num_cpus,
|
|
+ int crr_cpu, u64 bp_count, u64 *data)
|
|
+{
|
|
+ int num_stat_values = num_cpus + 1;
|
|
+ int crr_stat = 0;
|
|
+
|
|
+ /* update current CPU's stats and also add them to the total values */
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->in_interrupt;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->in_interrupt;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->stats.rx_packets;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->stats.rx_packets;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->stats.tx_packets;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->stats.tx_packets;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->tx_returned;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->tx_returned;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->tx_confirm;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->tx_confirm;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->tx_frag_skbuffs;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->tx_frag_skbuffs;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->rx_sg;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->rx_sg;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->stats.tx_errors;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->stats.tx_errors;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = percpu_priv->stats.rx_errors;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += percpu_priv->stats.rx_errors;
|
|
+
|
|
+ data[crr_stat * num_stat_values + crr_cpu] = bp_count;
|
|
+ data[crr_stat++ * num_stat_values + num_cpus] += bp_count;
|
|
+}
|
|
+
|
|
+static void dpa_get_ethtool_stats(struct net_device *net_dev,
|
|
+ struct ethtool_stats *stats, u64 *data)
|
|
+{
|
|
+ u64 bp_count, cg_time, cg_num, cg_status;
|
|
+ struct dpa_percpu_priv_s *percpu_priv;
|
|
+ struct qm_mcr_querycgr query_cgr;
|
|
+ struct dpa_rx_errors rx_errors;
|
|
+ struct dpa_ern_cnt ern_cnt;
|
|
+ struct dpa_priv_s *priv;
|
|
+ unsigned int num_cpus, offset;
|
|
+ struct dpa_bp *dpa_bp;
|
|
+ int total_stats, i;
|
|
+
|
|
+ total_stats = dpa_get_sset_count(net_dev, ETH_SS_STATS);
|
|
+ priv = netdev_priv(net_dev);
|
|
+ dpa_bp = priv->dpa_bp;
|
|
+ num_cpus = num_online_cpus();
|
|
+ bp_count = 0;
|
|
+
|
|
+ memset(&rx_errors, 0, sizeof(struct dpa_rx_errors));
|
|
+ memset(&ern_cnt, 0, sizeof(struct dpa_ern_cnt));
|
|
+ memset(data, 0, total_stats * sizeof(u64));
|
|
+
|
|
+ for_each_online_cpu(i) {
|
|
+ percpu_priv = per_cpu_ptr(priv->percpu_priv, i);
|
|
+
|
|
+ if (dpa_bp->percpu_count)
|
|
+ bp_count = *(per_cpu_ptr(dpa_bp->percpu_count, i));
|
|
+
|
|
+ rx_errors.dme += percpu_priv->rx_errors.dme;
|
|
+ rx_errors.fpe += percpu_priv->rx_errors.fpe;
|
|
+ rx_errors.fse += percpu_priv->rx_errors.fse;
|
|
+ rx_errors.phe += percpu_priv->rx_errors.phe;
|
|
+ rx_errors.cse += percpu_priv->rx_errors.cse;
|
|
+
|
|
+ ern_cnt.cg_tdrop += percpu_priv->ern_cnt.cg_tdrop;
|
|
+ ern_cnt.wred += percpu_priv->ern_cnt.wred;
|
|
+ ern_cnt.err_cond += percpu_priv->ern_cnt.err_cond;
|
|
+ ern_cnt.early_window += percpu_priv->ern_cnt.early_window;
|
|
+ ern_cnt.late_window += percpu_priv->ern_cnt.late_window;
|
|
+ ern_cnt.fq_tdrop += percpu_priv->ern_cnt.fq_tdrop;
|
|
+ ern_cnt.fq_retired += percpu_priv->ern_cnt.fq_retired;
|
|
+ ern_cnt.orp_zero += percpu_priv->ern_cnt.orp_zero;
|
|
+
|
|
+ copy_stats(percpu_priv, num_cpus, i, bp_count, data);
|
|
+ }
|
|
+
|
|
+ offset = (num_cpus + 1) * DPA_STATS_PERCPU_LEN;
|
|
+ memcpy(data + offset, &rx_errors, sizeof(struct dpa_rx_errors));
|
|
+
|
|
+ offset += sizeof(struct dpa_rx_errors) / sizeof(u64);
|
|
+ memcpy(data + offset, &ern_cnt, sizeof(struct dpa_ern_cnt));
|
|
+
|
|
+ /* gather congestion related counters */
|
|
+ cg_num = 0;
|
|
+ cg_status = 0;
|
|
+ cg_time = jiffies_to_msecs(priv->cgr_data.congested_jiffies);
|
|
+ if (qman_query_cgr(&priv->cgr_data.cgr, &query_cgr) == 0) {
|
|
+ cg_num = priv->cgr_data.cgr_congested_count;
|
|
+ cg_status = query_cgr.cgr.cs;
|
|
+
|
|
+ /* reset congestion stats (like QMan API does */
|
|
+ priv->cgr_data.congested_jiffies = 0;
|
|
+ priv->cgr_data.cgr_congested_count = 0;
|
|
+ }
|
|
+
|
|
+ offset += sizeof(struct dpa_ern_cnt) / sizeof(u64);
|
|
+ data[offset++] = cg_time;
|
|
+ data[offset++] = cg_num;
|
|
+ data[offset++] = cg_status;
|
|
+}
|
|
+
|
|
+static void dpa_get_strings(struct net_device *net_dev, u32 stringset, u8 *data)
|
|
+{
|
|
+ unsigned int i, j, num_cpus, size;
|
|
+ char stat_string_cpu[ETH_GSTRING_LEN];
|
|
+ u8 *strings;
|
|
+
|
|
+ strings = data;
|
|
+ num_cpus = num_online_cpus();
|
|
+ size = DPA_STATS_GLOBAL_LEN * ETH_GSTRING_LEN;
|
|
+
|
|
+ for (i = 0; i < DPA_STATS_PERCPU_LEN; i++) {
|
|
+ for (j = 0; j < num_cpus; j++) {
|
|
+ snprintf(stat_string_cpu, ETH_GSTRING_LEN, "%s [CPU %d]", dpa_stats_percpu[i], j);
|
|
+ memcpy(strings, stat_string_cpu, ETH_GSTRING_LEN);
|
|
+ strings += ETH_GSTRING_LEN;
|
|
+ }
|
|
+ snprintf(stat_string_cpu, ETH_GSTRING_LEN, "%s [TOTAL]", dpa_stats_percpu[i]);
|
|
+ memcpy(strings, stat_string_cpu, ETH_GSTRING_LEN);
|
|
+ strings += ETH_GSTRING_LEN;
|
|
+ }
|
|
+ memcpy(strings, dpa_stats_global, size);
|
|
+}
|
|
+
|
|
+const struct ethtool_ops dpa_ethtool_ops = {
|
|
+ .get_settings = dpa_get_settings,
|
|
+ .set_settings = dpa_set_settings,
|
|
+ .get_drvinfo = dpa_get_drvinfo,
|
|
+ .get_msglevel = dpa_get_msglevel,
|
|
+ .set_msglevel = dpa_set_msglevel,
|
|
+ .nway_reset = dpa_nway_reset,
|
|
+ .get_pauseparam = dpa_get_pauseparam,
|
|
+ .set_pauseparam = dpa_set_pauseparam,
|
|
+ .self_test = NULL, /* TODO invoke the cold-boot unit-test? */
|
|
+ .get_link = ethtool_op_get_link,
|
|
+ .get_eee = dpa_get_eee,
|
|
+ .set_eee = dpa_set_eee,
|
|
+ .get_sset_count = dpa_get_sset_count,
|
|
+ .get_ethtool_stats = dpa_get_ethtool_stats,
|
|
+ .get_strings = dpa_get_strings,
|
|
+#ifdef CONFIG_PM
|
|
+ .get_wol = dpa_get_wol,
|
|
+ .set_wol = dpa_set_wol,
|
|
+#endif
|
|
+};
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/dpaa_ptp.c
|
|
@@ -0,0 +1,290 @@
|
|
+/*
|
|
+ * DPAA Ethernet Driver -- PTP 1588 clock using the dTSEC
|
|
+ *
|
|
+ * Author: Yangbo Lu <yangbo.lu@freescale.com>
|
|
+ *
|
|
+ * Copyright 2014 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify it
|
|
+ * under the terms of the GNU General Public License as published by the
|
|
+ * Free Software Foundation; either version 2 of the License, or (at your
|
|
+ * option) any later version.
|
|
+*/
|
|
+
|
|
+#include <linux/device.h>
|
|
+#include <linux/hrtimer.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/timex.h>
|
|
+#include <linux/io.h>
|
|
+
|
|
+#include <linux/ptp_clock_kernel.h>
|
|
+
|
|
+#include "dpaa_eth.h"
|
|
+#include "mac.h"
|
|
+
|
|
+struct ptp_clock *clock;
|
|
+
|
|
+static struct mac_device *mac_dev;
|
|
+static u32 freqCompensation;
|
|
+
|
|
+/* Bit definitions for the TMR_CTRL register */
|
|
+#define ALM1P (1<<31) /* Alarm1 output polarity */
|
|
+#define ALM2P (1<<30) /* Alarm2 output polarity */
|
|
+#define FS (1<<28) /* FIPER start indication */
|
|
+#define PP1L (1<<27) /* Fiper1 pulse loopback mode enabled. */
|
|
+#define PP2L (1<<26) /* Fiper2 pulse loopback mode enabled. */
|
|
+#define TCLK_PERIOD_SHIFT (16) /* 1588 timer reference clock period. */
|
|
+#define TCLK_PERIOD_MASK (0x3ff)
|
|
+#define RTPE (1<<15) /* Record Tx Timestamp to PAL Enable. */
|
|
+#define FRD (1<<14) /* FIPER Realignment Disable */
|
|
+#define ESFDP (1<<11) /* External Tx/Rx SFD Polarity. */
|
|
+#define ESFDE (1<<10) /* External Tx/Rx SFD Enable. */
|
|
+#define ETEP2 (1<<9) /* External trigger 2 edge polarity */
|
|
+#define ETEP1 (1<<8) /* External trigger 1 edge polarity */
|
|
+#define COPH (1<<7) /* Generated clock output phase. */
|
|
+#define CIPH (1<<6) /* External oscillator input clock phase */
|
|
+#define TMSR (1<<5) /* Timer soft reset. */
|
|
+#define BYP (1<<3) /* Bypass drift compensated clock */
|
|
+#define TE (1<<2) /* 1588 timer enable. */
|
|
+#define CKSEL_SHIFT (0) /* 1588 Timer reference clock source */
|
|
+#define CKSEL_MASK (0x3)
|
|
+
|
|
+/* Bit definitions for the TMR_TEVENT register */
|
|
+#define ETS2 (1<<25) /* External trigger 2 timestamp sampled */
|
|
+#define ETS1 (1<<24) /* External trigger 1 timestamp sampled */
|
|
+#define ALM2 (1<<17) /* Current time = alarm time register 2 */
|
|
+#define ALM1 (1<<16) /* Current time = alarm time register 1 */
|
|
+#define PP1 (1<<7) /* periodic pulse generated on FIPER1 */
|
|
+#define PP2 (1<<6) /* periodic pulse generated on FIPER2 */
|
|
+#define PP3 (1<<5) /* periodic pulse generated on FIPER3 */
|
|
+
|
|
+/* Bit definitions for the TMR_TEMASK register */
|
|
+#define ETS2EN (1<<25) /* External trigger 2 timestamp enable */
|
|
+#define ETS1EN (1<<24) /* External trigger 1 timestamp enable */
|
|
+#define ALM2EN (1<<17) /* Timer ALM2 event enable */
|
|
+#define ALM1EN (1<<16) /* Timer ALM1 event enable */
|
|
+#define PP1EN (1<<7) /* Periodic pulse event 1 enable */
|
|
+#define PP2EN (1<<6) /* Periodic pulse event 2 enable */
|
|
+
|
|
+/* Bit definitions for the TMR_PEVENT register */
|
|
+#define TXP2 (1<<9) /* PTP transmitted timestamp im TXTS2 */
|
|
+#define TXP1 (1<<8) /* PTP transmitted timestamp in TXTS1 */
|
|
+#define RXP (1<<0) /* PTP frame has been received */
|
|
+
|
|
+/* Bit definitions for the TMR_PEMASK register */
|
|
+#define TXP2EN (1<<9) /* Transmit PTP packet event 2 enable */
|
|
+#define TXP1EN (1<<8) /* Transmit PTP packet event 1 enable */
|
|
+#define RXPEN (1<<0) /* Receive PTP packet event enable */
|
|
+
|
|
+/* Bit definitions for the TMR_STAT register */
|
|
+#define STAT_VEC_SHIFT (0) /* Timer general purpose status vector */
|
|
+#define STAT_VEC_MASK (0x3f)
|
|
+
|
|
+/* Bit definitions for the TMR_PRSC register */
|
|
+#define PRSC_OCK_SHIFT (0) /* Output clock division/prescale factor. */
|
|
+#define PRSC_OCK_MASK (0xffff)
|
|
+
|
|
+
|
|
+#define N_EXT_TS 2
|
|
+
|
|
+static void set_alarm(void)
|
|
+{
|
|
+ u64 ns;
|
|
+
|
|
+ if (mac_dev->fm_rtc_get_cnt)
|
|
+ mac_dev->fm_rtc_get_cnt(mac_dev->fm_dev, &ns);
|
|
+ ns += 1500000000ULL;
|
|
+ ns = div_u64(ns, 1000000000UL) * 1000000000ULL;
|
|
+ ns -= DPA_PTP_NOMINAL_FREQ_PERIOD_NS;
|
|
+ if (mac_dev->fm_rtc_set_alarm)
|
|
+ mac_dev->fm_rtc_set_alarm(mac_dev->fm_dev, 0, ns);
|
|
+}
|
|
+
|
|
+static void set_fipers(void)
|
|
+{
|
|
+ u64 fiper;
|
|
+
|
|
+ if (mac_dev->fm_rtc_disable)
|
|
+ mac_dev->fm_rtc_disable(mac_dev->fm_dev);
|
|
+
|
|
+ set_alarm();
|
|
+ fiper = 1000000000ULL - DPA_PTP_NOMINAL_FREQ_PERIOD_NS;
|
|
+ if (mac_dev->fm_rtc_set_fiper)
|
|
+ mac_dev->fm_rtc_set_fiper(mac_dev->fm_dev, 0, fiper);
|
|
+
|
|
+ if (mac_dev->fm_rtc_enable)
|
|
+ mac_dev->fm_rtc_enable(mac_dev->fm_dev);
|
|
+}
|
|
+
|
|
+/* PTP clock operations */
|
|
+
|
|
+static int ptp_dpa_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
|
|
+{
|
|
+ u64 adj;
|
|
+ u32 diff, tmr_add;
|
|
+ int neg_adj = 0;
|
|
+
|
|
+ if (ppb < 0) {
|
|
+ neg_adj = 1;
|
|
+ ppb = -ppb;
|
|
+ }
|
|
+
|
|
+ tmr_add = freqCompensation;
|
|
+ adj = tmr_add;
|
|
+ adj *= ppb;
|
|
+ diff = div_u64(adj, 1000000000ULL);
|
|
+
|
|
+ tmr_add = neg_adj ? tmr_add - diff : tmr_add + diff;
|
|
+
|
|
+ if (mac_dev->fm_rtc_set_drift)
|
|
+ mac_dev->fm_rtc_set_drift(mac_dev->fm_dev, tmr_add);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ptp_dpa_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
|
+{
|
|
+ s64 now;
|
|
+
|
|
+ if (mac_dev->fm_rtc_get_cnt)
|
|
+ mac_dev->fm_rtc_get_cnt(mac_dev->fm_dev, &now);
|
|
+
|
|
+ now += delta;
|
|
+
|
|
+ if (mac_dev->fm_rtc_set_cnt)
|
|
+ mac_dev->fm_rtc_set_cnt(mac_dev->fm_dev, now);
|
|
+ set_fipers();
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ptp_dpa_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
|
|
+{
|
|
+ u64 ns;
|
|
+ u32 remainder;
|
|
+
|
|
+ if (mac_dev->fm_rtc_get_cnt)
|
|
+ mac_dev->fm_rtc_get_cnt(mac_dev->fm_dev, &ns);
|
|
+
|
|
+ ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
|
|
+ ts->tv_nsec = remainder;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ptp_dpa_settime(struct ptp_clock_info *ptp,
|
|
+ const struct timespec64 *ts)
|
|
+{
|
|
+ u64 ns;
|
|
+
|
|
+ ns = ts->tv_sec * 1000000000ULL;
|
|
+ ns += ts->tv_nsec;
|
|
+
|
|
+ if (mac_dev->fm_rtc_set_cnt)
|
|
+ mac_dev->fm_rtc_set_cnt(mac_dev->fm_dev, ns);
|
|
+ set_fipers();
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ptp_dpa_enable(struct ptp_clock_info *ptp,
|
|
+ struct ptp_clock_request *rq, int on)
|
|
+{
|
|
+ u32 bit;
|
|
+
|
|
+ switch (rq->type) {
|
|
+ case PTP_CLK_REQ_EXTTS:
|
|
+ switch (rq->extts.index) {
|
|
+ case 0:
|
|
+ bit = ETS1EN;
|
|
+ break;
|
|
+ case 1:
|
|
+ bit = ETS2EN;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (on) {
|
|
+ if (mac_dev->fm_rtc_enable_interrupt)
|
|
+ mac_dev->fm_rtc_enable_interrupt(
|
|
+ mac_dev->fm_dev, bit);
|
|
+ } else {
|
|
+ if (mac_dev->fm_rtc_disable_interrupt)
|
|
+ mac_dev->fm_rtc_disable_interrupt(
|
|
+ mac_dev->fm_dev, bit);
|
|
+ }
|
|
+ return 0;
|
|
+
|
|
+ case PTP_CLK_REQ_PPS:
|
|
+ if (on) {
|
|
+ if (mac_dev->fm_rtc_enable_interrupt)
|
|
+ mac_dev->fm_rtc_enable_interrupt(
|
|
+ mac_dev->fm_dev, PP1EN);
|
|
+ } else {
|
|
+ if (mac_dev->fm_rtc_disable_interrupt)
|
|
+ mac_dev->fm_rtc_disable_interrupt(
|
|
+ mac_dev->fm_dev, PP1EN);
|
|
+ }
|
|
+ return 0;
|
|
+
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return -EOPNOTSUPP;
|
|
+}
|
|
+
|
|
+static struct ptp_clock_info ptp_dpa_caps = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .name = "dpaa clock",
|
|
+ .max_adj = 512000,
|
|
+ .n_alarm = 0,
|
|
+ .n_ext_ts = N_EXT_TS,
|
|
+ .n_per_out = 0,
|
|
+ .pps = 1,
|
|
+ .adjfreq = ptp_dpa_adjfreq,
|
|
+ .adjtime = ptp_dpa_adjtime,
|
|
+ .gettime64 = ptp_dpa_gettime,
|
|
+ .settime64 = ptp_dpa_settime,
|
|
+ .enable = ptp_dpa_enable,
|
|
+};
|
|
+
|
|
+static int __init __cold dpa_ptp_load(void)
|
|
+{
|
|
+ struct device *ptp_dev;
|
|
+ struct timespec64 now;
|
|
+ int dpa_phc_index;
|
|
+ int err;
|
|
+
|
|
+ if (!(ptp_priv.of_dev && ptp_priv.mac_dev))
|
|
+ return -ENODEV;
|
|
+
|
|
+ ptp_dev = &ptp_priv.of_dev->dev;
|
|
+ mac_dev = ptp_priv.mac_dev;
|
|
+
|
|
+ if (mac_dev->fm_rtc_get_drift)
|
|
+ mac_dev->fm_rtc_get_drift(mac_dev->fm_dev, &freqCompensation);
|
|
+
|
|
+ getnstimeofday64(&now);
|
|
+ ptp_dpa_settime(&ptp_dpa_caps, &now);
|
|
+
|
|
+ clock = ptp_clock_register(&ptp_dpa_caps, ptp_dev);
|
|
+ if (IS_ERR(clock)) {
|
|
+ err = PTR_ERR(clock);
|
|
+ return err;
|
|
+ }
|
|
+ dpa_phc_index = ptp_clock_index(clock);
|
|
+ return 0;
|
|
+}
|
|
+module_init(dpa_ptp_load);
|
|
+
|
|
+static void __exit __cold dpa_ptp_unload(void)
|
|
+{
|
|
+ if (mac_dev->fm_rtc_disable_interrupt)
|
|
+ mac_dev->fm_rtc_disable_interrupt(mac_dev->fm_dev, 0xffffffff);
|
|
+ ptp_clock_unregister(clock);
|
|
+}
|
|
+module_exit(dpa_ptp_unload);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/mac-api.c
|
|
@@ -0,0 +1,909 @@
|
|
+/* Copyright 2008-2012 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": %s:%hu:%s() " fmt, \
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__
|
|
+#else
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": " fmt
|
|
+#endif
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/of_mdio.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/netdevice.h>
|
|
+
|
|
+#include "dpaa_eth.h"
|
|
+#include "mac.h"
|
|
+#include "lnxwrp_fsl_fman.h"
|
|
+
|
|
+#include "error_ext.h" /* GET_ERROR_TYPE, E_OK */
|
|
+
|
|
+#include "fsl_fman_dtsec.h"
|
|
+#include "fsl_fman_tgec.h"
|
|
+#include "fsl_fman_memac.h"
|
|
+#include "../sdk_fman/src/wrapper/lnxwrp_sysfs_fm.h"
|
|
+
|
|
+#define MAC_DESCRIPTION "FSL FMan MAC API based driver"
|
|
+
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
+
|
|
+MODULE_AUTHOR("Emil Medve <Emilian.Medve@Freescale.com>");
|
|
+
|
|
+MODULE_DESCRIPTION(MAC_DESCRIPTION);
|
|
+
|
|
+struct mac_priv_s {
|
|
+ struct fm_mac_dev *fm_mac;
|
|
+};
|
|
+
|
|
+const char *mac_driver_description __initconst = MAC_DESCRIPTION;
|
|
+const size_t mac_sizeof_priv[] = {
|
|
+ [DTSEC] = sizeof(struct mac_priv_s),
|
|
+ [XGMAC] = sizeof(struct mac_priv_s),
|
|
+ [MEMAC] = sizeof(struct mac_priv_s)
|
|
+};
|
|
+
|
|
+static const enet_mode_t _100[] = {
|
|
+ [PHY_INTERFACE_MODE_MII] = e_ENET_MODE_MII_100,
|
|
+ [PHY_INTERFACE_MODE_RMII] = e_ENET_MODE_RMII_100
|
|
+};
|
|
+
|
|
+static const enet_mode_t _1000[] = {
|
|
+ [PHY_INTERFACE_MODE_GMII] = e_ENET_MODE_GMII_1000,
|
|
+ [PHY_INTERFACE_MODE_SGMII] = e_ENET_MODE_SGMII_1000,
|
|
+ [PHY_INTERFACE_MODE_QSGMII] = e_ENET_MODE_QSGMII_1000,
|
|
+ [PHY_INTERFACE_MODE_TBI] = e_ENET_MODE_TBI_1000,
|
|
+ [PHY_INTERFACE_MODE_RGMII] = e_ENET_MODE_RGMII_1000,
|
|
+ [PHY_INTERFACE_MODE_RGMII_ID] = e_ENET_MODE_RGMII_1000,
|
|
+ [PHY_INTERFACE_MODE_RGMII_RXID] = e_ENET_MODE_RGMII_1000,
|
|
+ [PHY_INTERFACE_MODE_RGMII_TXID] = e_ENET_MODE_RGMII_1000,
|
|
+ [PHY_INTERFACE_MODE_RTBI] = e_ENET_MODE_RTBI_1000
|
|
+};
|
|
+
|
|
+static enet_mode_t __cold __attribute__((nonnull))
|
|
+macdev2enetinterface(const struct mac_device *mac_dev)
|
|
+{
|
|
+ switch (mac_dev->max_speed) {
|
|
+ case SPEED_100:
|
|
+ return _100[mac_dev->phy_if];
|
|
+ case SPEED_1000:
|
|
+ return _1000[mac_dev->phy_if];
|
|
+ case SPEED_2500:
|
|
+ return e_ENET_MODE_SGMII_2500;
|
|
+ case SPEED_10000:
|
|
+ return e_ENET_MODE_XGMII_10000;
|
|
+ default:
|
|
+ return e_ENET_MODE_MII_100;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void mac_exception(handle_t _mac_dev, e_FmMacExceptions exception)
|
|
+{
|
|
+ struct mac_device *mac_dev;
|
|
+
|
|
+ mac_dev = (struct mac_device *)_mac_dev;
|
|
+
|
|
+ if (e_FM_MAC_EX_10G_RX_FIFO_OVFL == exception) {
|
|
+ /* don't flag RX FIFO after the first */
|
|
+ fm_mac_set_exception(mac_dev->get_mac_handle(mac_dev),
|
|
+ e_FM_MAC_EX_10G_RX_FIFO_OVFL, false);
|
|
+ dev_err(mac_dev->dev, "10G MAC got RX FIFO Error = %x\n",
|
|
+ exception);
|
|
+ }
|
|
+
|
|
+ dev_dbg(mac_dev->dev, "%s:%s() -> %d\n", KBUILD_BASENAME".c", __func__,
|
|
+ exception);
|
|
+}
|
|
+
|
|
+static int __cold init(struct mac_device *mac_dev)
|
|
+{
|
|
+ int _errno;
|
|
+ struct mac_priv_s *priv;
|
|
+ t_FmMacParams param;
|
|
+ uint32_t version;
|
|
+
|
|
+ priv = macdev_priv(mac_dev);
|
|
+
|
|
+ param.baseAddr = (typeof(param.baseAddr))(uintptr_t)devm_ioremap(
|
|
+ mac_dev->dev, mac_dev->res->start, 0x2000);
|
|
+ param.enetMode = macdev2enetinterface(mac_dev);
|
|
+ memcpy(¶m.addr, mac_dev->addr, min(sizeof(param.addr),
|
|
+ sizeof(mac_dev->addr)));
|
|
+ param.macId = mac_dev->cell_index;
|
|
+ param.h_Fm = (handle_t)mac_dev->fm;
|
|
+ param.mdioIrq = NO_IRQ;
|
|
+ param.f_Exception = mac_exception;
|
|
+ param.f_Event = mac_exception;
|
|
+ param.h_App = mac_dev;
|
|
+
|
|
+ priv->fm_mac = fm_mac_config(¶m);
|
|
+ if (unlikely(priv->fm_mac == NULL)) {
|
|
+ _errno = -EINVAL;
|
|
+ goto _return;
|
|
+ }
|
|
+
|
|
+ fm_mac_set_handle(mac_dev->fm_dev, priv->fm_mac,
|
|
+ (macdev2enetinterface(mac_dev) != e_ENET_MODE_XGMII_10000) ?
|
|
+ param.macId : param.macId + FM_MAX_NUM_OF_1G_MACS);
|
|
+
|
|
+ _errno = fm_mac_config_max_frame_length(priv->fm_mac,
|
|
+ fm_get_max_frm());
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+
|
|
+ if (macdev2enetinterface(mac_dev) != e_ENET_MODE_XGMII_10000) {
|
|
+ /* 10G always works with pad and CRC */
|
|
+ _errno = fm_mac_config_pad_and_crc(priv->fm_mac, true);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+
|
|
+ _errno = fm_mac_config_half_duplex(priv->fm_mac,
|
|
+ mac_dev->half_duplex);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+ } else {
|
|
+ _errno = fm_mac_config_reset_on_init(priv->fm_mac, true);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+ }
|
|
+
|
|
+ _errno = fm_mac_init(priv->fm_mac);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+
|
|
+#ifndef CONFIG_FMAN_MIB_CNT_OVF_IRQ_EN
|
|
+ /* For 1G MAC, disable by default the MIB counters overflow interrupt */
|
|
+ if (macdev2enetinterface(mac_dev) != e_ENET_MODE_XGMII_10000) {
|
|
+ _errno = fm_mac_set_exception(mac_dev->get_mac_handle(mac_dev),
|
|
+ e_FM_MAC_EX_1G_RX_MIB_CNT_OVFL, FALSE);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+ }
|
|
+#endif /* !CONFIG_FMAN_MIB_CNT_OVF_IRQ_EN */
|
|
+
|
|
+ /* For 10G MAC, disable Tx ECC exception */
|
|
+ if (macdev2enetinterface(mac_dev) == e_ENET_MODE_XGMII_10000) {
|
|
+ _errno = fm_mac_set_exception(mac_dev->get_mac_handle(mac_dev),
|
|
+ e_FM_MAC_EX_10G_1TX_ECC_ER, FALSE);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+ }
|
|
+
|
|
+ _errno = fm_mac_get_version(priv->fm_mac, &version);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+
|
|
+ dev_info(mac_dev->dev, "FMan %s version: 0x%08x\n",
|
|
+ ((macdev2enetinterface(mac_dev) != e_ENET_MODE_XGMII_10000) ?
|
|
+ "dTSEC" : "XGEC"), version);
|
|
+
|
|
+ goto _return;
|
|
+
|
|
+
|
|
+_return_fm_mac_free:
|
|
+ fm_mac_free(mac_dev->get_mac_handle(mac_dev));
|
|
+
|
|
+_return:
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static int __cold memac_init(struct mac_device *mac_dev)
|
|
+{
|
|
+ int _errno;
|
|
+ struct mac_priv_s *priv;
|
|
+ t_FmMacParams param;
|
|
+
|
|
+ priv = macdev_priv(mac_dev);
|
|
+
|
|
+ param.baseAddr = (typeof(param.baseAddr))(uintptr_t)devm_ioremap(
|
|
+ mac_dev->dev, mac_dev->res->start, 0x2000);
|
|
+ param.enetMode = macdev2enetinterface(mac_dev);
|
|
+ memcpy(¶m.addr, mac_dev->addr, sizeof(mac_dev->addr));
|
|
+ param.macId = mac_dev->cell_index;
|
|
+ param.h_Fm = (handle_t)mac_dev->fm;
|
|
+ param.mdioIrq = NO_IRQ;
|
|
+ param.f_Exception = mac_exception;
|
|
+ param.f_Event = mac_exception;
|
|
+ param.h_App = mac_dev;
|
|
+
|
|
+ priv->fm_mac = fm_mac_config(¶m);
|
|
+ if (unlikely(priv->fm_mac == NULL)) {
|
|
+ _errno = -EINVAL;
|
|
+ goto _return;
|
|
+ }
|
|
+
|
|
+ fm_mac_set_handle(mac_dev->fm_dev, priv->fm_mac,
|
|
+ (macdev2enetinterface(mac_dev) != e_ENET_MODE_XGMII_10000) ?
|
|
+ param.macId : param.macId + FM_MAX_NUM_OF_1G_MACS);
|
|
+
|
|
+ _errno = fm_mac_config_max_frame_length(priv->fm_mac, fm_get_max_frm());
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+
|
|
+ _errno = fm_mac_config_reset_on_init(priv->fm_mac, true);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+
|
|
+ _errno = fm_mac_init(priv->fm_mac);
|
|
+ if (unlikely(_errno < 0))
|
|
+ goto _return_fm_mac_free;
|
|
+
|
|
+ dev_info(mac_dev->dev, "FMan MEMAC\n");
|
|
+
|
|
+ goto _return;
|
|
+
|
|
+_return_fm_mac_free:
|
|
+ fm_mac_free(priv->fm_mac);
|
|
+
|
|
+_return:
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static int __cold start(struct mac_device *mac_dev)
|
|
+{
|
|
+ int _errno;
|
|
+ struct phy_device *phy_dev = mac_dev->phy_dev;
|
|
+
|
|
+ _errno = fm_mac_enable(mac_dev->get_mac_handle(mac_dev));
|
|
+
|
|
+ if (!_errno && phy_dev)
|
|
+ phy_start(phy_dev);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static int __cold stop(struct mac_device *mac_dev)
|
|
+{
|
|
+ if (mac_dev->phy_dev)
|
|
+ phy_stop(mac_dev->phy_dev);
|
|
+
|
|
+ return fm_mac_disable(mac_dev->get_mac_handle(mac_dev));
|
|
+}
|
|
+
|
|
+static int __cold set_multi(struct net_device *net_dev,
|
|
+ struct mac_device *mac_dev)
|
|
+{
|
|
+ struct mac_priv_s *mac_priv;
|
|
+ struct mac_address *old_addr, *tmp;
|
|
+ struct netdev_hw_addr *ha;
|
|
+ int _errno;
|
|
+
|
|
+ mac_priv = macdev_priv(mac_dev);
|
|
+
|
|
+ /* Clear previous address list */
|
|
+ list_for_each_entry_safe(old_addr, tmp, &mac_dev->mc_addr_list, list) {
|
|
+ _errno = fm_mac_remove_hash_mac_addr(mac_priv->fm_mac,
|
|
+ (t_EnetAddr *)old_addr->addr);
|
|
+ if (_errno < 0)
|
|
+ return _errno;
|
|
+
|
|
+ list_del(&old_addr->list);
|
|
+ kfree(old_addr);
|
|
+ }
|
|
+
|
|
+ /* Add all the addresses from the new list */
|
|
+ netdev_for_each_mc_addr(ha, net_dev) {
|
|
+ _errno = fm_mac_add_hash_mac_addr(mac_priv->fm_mac,
|
|
+ (t_EnetAddr *)ha->addr);
|
|
+ if (_errno < 0)
|
|
+ return _errno;
|
|
+
|
|
+ tmp = kmalloc(sizeof(struct mac_address), GFP_ATOMIC);
|
|
+ if (!tmp) {
|
|
+ dev_err(mac_dev->dev, "Out of memory\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ memcpy(tmp->addr, ha->addr, ETH_ALEN);
|
|
+ list_add(&tmp->list, &mac_dev->mc_addr_list);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Avoid redundant calls to FMD, if the MAC driver already contains the desired
|
|
+ * active PAUSE settings. Otherwise, the new active settings should be reflected
|
|
+ * in FMan.
|
|
+ */
|
|
+int set_mac_active_pause(struct mac_device *mac_dev, bool rx, bool tx)
|
|
+{
|
|
+ struct fm_mac_dev *fm_mac_dev = mac_dev->get_mac_handle(mac_dev);
|
|
+ int _errno = 0;
|
|
+
|
|
+ if (unlikely(rx != mac_dev->rx_pause_active)) {
|
|
+ _errno = fm_mac_set_rx_pause_frames(fm_mac_dev, rx);
|
|
+ if (likely(_errno == 0))
|
|
+ mac_dev->rx_pause_active = rx;
|
|
+ }
|
|
+
|
|
+ if (unlikely(tx != mac_dev->tx_pause_active)) {
|
|
+ _errno = fm_mac_set_tx_pause_frames(fm_mac_dev, tx);
|
|
+ if (likely(_errno == 0))
|
|
+ mac_dev->tx_pause_active = tx;
|
|
+ }
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+EXPORT_SYMBOL(set_mac_active_pause);
|
|
+
|
|
+/* Determine the MAC RX/TX PAUSE frames settings based on PHY
|
|
+ * autonegotiation or values set by eththool.
|
|
+ */
|
|
+void get_pause_cfg(struct mac_device *mac_dev, bool *rx_pause, bool *tx_pause)
|
|
+{
|
|
+ struct phy_device *phy_dev = mac_dev->phy_dev;
|
|
+ u16 lcl_adv, rmt_adv;
|
|
+ u8 flowctrl;
|
|
+
|
|
+ *rx_pause = *tx_pause = false;
|
|
+
|
|
+ if (!phy_dev->duplex)
|
|
+ return;
|
|
+
|
|
+ /* If PAUSE autonegotiation is disabled, the TX/RX PAUSE settings
|
|
+ * are those set by ethtool.
|
|
+ */
|
|
+ if (!mac_dev->autoneg_pause) {
|
|
+ *rx_pause = mac_dev->rx_pause_req;
|
|
+ *tx_pause = mac_dev->tx_pause_req;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Else if PAUSE autonegotiation is enabled, the TX/RX PAUSE
|
|
+ * settings depend on the result of the link negotiation.
|
|
+ */
|
|
+
|
|
+ /* get local capabilities */
|
|
+ lcl_adv = 0;
|
|
+ if (phy_dev->advertising & ADVERTISED_Pause)
|
|
+ lcl_adv |= ADVERTISE_PAUSE_CAP;
|
|
+ if (phy_dev->advertising & ADVERTISED_Asym_Pause)
|
|
+ lcl_adv |= ADVERTISE_PAUSE_ASYM;
|
|
+
|
|
+ /* get link partner capabilities */
|
|
+ rmt_adv = 0;
|
|
+ if (phy_dev->pause)
|
|
+ rmt_adv |= LPA_PAUSE_CAP;
|
|
+ if (phy_dev->asym_pause)
|
|
+ rmt_adv |= LPA_PAUSE_ASYM;
|
|
+
|
|
+ /* Calculate TX/RX settings based on local and peer advertised
|
|
+ * symmetric/asymmetric PAUSE capabilities.
|
|
+ */
|
|
+ flowctrl = mii_resolve_flowctrl_fdx(lcl_adv, rmt_adv);
|
|
+ if (flowctrl & FLOW_CTRL_RX)
|
|
+ *rx_pause = true;
|
|
+ if (flowctrl & FLOW_CTRL_TX)
|
|
+ *tx_pause = true;
|
|
+}
|
|
+EXPORT_SYMBOL(get_pause_cfg);
|
|
+
|
|
+static void adjust_link_void(struct net_device *net_dev)
|
|
+{
|
|
+}
|
|
+
|
|
+static void adjust_link(struct net_device *net_dev)
|
|
+{
|
|
+ struct dpa_priv_s *priv = netdev_priv(net_dev);
|
|
+ struct mac_device *mac_dev = priv->mac_dev;
|
|
+ struct phy_device *phy_dev = mac_dev->phy_dev;
|
|
+ struct fm_mac_dev *fm_mac_dev;
|
|
+ bool rx_pause, tx_pause;
|
|
+ int _errno;
|
|
+
|
|
+ fm_mac_dev = mac_dev->get_mac_handle(mac_dev);
|
|
+ fm_mac_adjust_link(fm_mac_dev, phy_dev->link, phy_dev->speed,
|
|
+ phy_dev->duplex);
|
|
+
|
|
+ get_pause_cfg(mac_dev, &rx_pause, &tx_pause);
|
|
+ _errno = set_mac_active_pause(mac_dev, rx_pause, tx_pause);
|
|
+ if (unlikely(_errno < 0))
|
|
+ netdev_err(net_dev, "set_mac_active_pause() = %d\n", _errno);
|
|
+}
|
|
+
|
|
+/* Initializes driver's PHY state, and attaches to the PHY.
|
|
+ * Returns 0 on success.
|
|
+ */
|
|
+static int dtsec_init_phy(struct net_device *net_dev,
|
|
+ struct mac_device *mac_dev)
|
|
+{
|
|
+ struct phy_device *phy_dev;
|
|
+
|
|
+ if (of_phy_is_fixed_link(mac_dev->phy_node))
|
|
+ phy_dev = of_phy_attach(net_dev, mac_dev->phy_node,
|
|
+ 0, mac_dev->phy_if);
|
|
+ else
|
|
+ phy_dev = of_phy_connect(net_dev, mac_dev->phy_node,
|
|
+ &adjust_link, 0, mac_dev->phy_if);
|
|
+ if (unlikely(phy_dev == NULL) || IS_ERR(phy_dev)) {
|
|
+ netdev_err(net_dev, "Could not connect to PHY %s\n",
|
|
+ mac_dev->phy_node ?
|
|
+ mac_dev->phy_node->full_name :
|
|
+ mac_dev->fixed_bus_id);
|
|
+ return phy_dev == NULL ? -ENODEV : PTR_ERR(phy_dev);
|
|
+ }
|
|
+
|
|
+ /* Remove any features not supported by the controller */
|
|
+ phy_dev->supported &= mac_dev->if_support;
|
|
+ /* Enable the symmetric and asymmetric PAUSE frame advertisements,
|
|
+ * as most of the PHY drivers do not enable them by default.
|
|
+ */
|
|
+ phy_dev->supported |= (SUPPORTED_Pause | SUPPORTED_Asym_Pause);
|
|
+ phy_dev->advertising = phy_dev->supported;
|
|
+
|
|
+ mac_dev->phy_dev = phy_dev;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int xgmac_init_phy(struct net_device *net_dev,
|
|
+ struct mac_device *mac_dev)
|
|
+{
|
|
+ struct phy_device *phy_dev;
|
|
+
|
|
+ if (of_phy_is_fixed_link(mac_dev->phy_node))
|
|
+ phy_dev = of_phy_attach(net_dev, mac_dev->phy_node,
|
|
+ 0, mac_dev->phy_if);
|
|
+ else
|
|
+ phy_dev = of_phy_connect(net_dev, mac_dev->phy_node,
|
|
+ &adjust_link_void, 0, mac_dev->phy_if);
|
|
+ if (unlikely(phy_dev == NULL) || IS_ERR(phy_dev)) {
|
|
+ netdev_err(net_dev, "Could not attach to PHY %s\n",
|
|
+ mac_dev->phy_node ?
|
|
+ mac_dev->phy_node->full_name :
|
|
+ mac_dev->fixed_bus_id);
|
|
+ return phy_dev == NULL ? -ENODEV : PTR_ERR(phy_dev);
|
|
+ }
|
|
+
|
|
+ phy_dev->supported &= mac_dev->if_support;
|
|
+ /* Enable the symmetric and asymmetric PAUSE frame advertisements,
|
|
+ * as most of the PHY drivers do not enable them by default.
|
|
+ */
|
|
+ phy_dev->supported |= (SUPPORTED_Pause | SUPPORTED_Asym_Pause);
|
|
+ phy_dev->advertising = phy_dev->supported;
|
|
+
|
|
+ mac_dev->phy_dev = phy_dev;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int memac_init_phy(struct net_device *net_dev,
|
|
+ struct mac_device *mac_dev)
|
|
+{
|
|
+ struct phy_device *phy_dev;
|
|
+
|
|
+ if (of_phy_is_fixed_link(mac_dev->phy_node)) {
|
|
+ phy_dev = of_phy_attach(net_dev, mac_dev->phy_node,
|
|
+ 0, mac_dev->phy_if);
|
|
+ } else if ((macdev2enetinterface(mac_dev) == e_ENET_MODE_XGMII_10000) ||
|
|
+ (macdev2enetinterface(mac_dev) == e_ENET_MODE_SGMII_2500)) {
|
|
+ phy_dev = of_phy_connect(net_dev, mac_dev->phy_node,
|
|
+ &adjust_link_void, 0,
|
|
+ mac_dev->phy_if);
|
|
+ } else {
|
|
+ phy_dev = of_phy_connect(net_dev, mac_dev->phy_node,
|
|
+ &adjust_link, 0, mac_dev->phy_if);
|
|
+ }
|
|
+
|
|
+ if (unlikely(phy_dev == NULL) || IS_ERR(phy_dev)) {
|
|
+ netdev_err(net_dev, "Could not connect to PHY %s\n",
|
|
+ mac_dev->phy_node ?
|
|
+ mac_dev->phy_node->full_name :
|
|
+ mac_dev->fixed_bus_id);
|
|
+ return phy_dev == NULL ? -ENODEV : PTR_ERR(phy_dev);
|
|
+ }
|
|
+
|
|
+ /* Remove any features not supported by the controller */
|
|
+ phy_dev->supported &= mac_dev->if_support;
|
|
+ /* Enable the symmetric and asymmetric PAUSE frame advertisements,
|
|
+ * as most of the PHY drivers do not enable them by default.
|
|
+ */
|
|
+ phy_dev->supported |= (SUPPORTED_Pause | SUPPORTED_Asym_Pause);
|
|
+ phy_dev->advertising = phy_dev->supported;
|
|
+
|
|
+ mac_dev->phy_dev = phy_dev;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __cold uninit(struct fm_mac_dev *fm_mac_dev)
|
|
+{
|
|
+ int _errno, __errno;
|
|
+
|
|
+ _errno = fm_mac_disable(fm_mac_dev);
|
|
+ __errno = fm_mac_free(fm_mac_dev);
|
|
+
|
|
+ if (unlikely(__errno < 0))
|
|
+ _errno = __errno;
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static struct fm_mac_dev *get_mac_handle(struct mac_device *mac_dev)
|
|
+{
|
|
+ const struct mac_priv_s *priv;
|
|
+ priv = macdev_priv(mac_dev);
|
|
+ return priv->fm_mac;
|
|
+}
|
|
+
|
|
+static int dtsec_dump_regs(struct mac_device *h_mac, char *buf, int nn)
|
|
+{
|
|
+ struct dtsec_regs *p_mm = (struct dtsec_regs *) h_mac->vaddr;
|
|
+ int i = 0, n = nn;
|
|
+
|
|
+ FM_DMP_SUBTITLE(buf, n, "\n");
|
|
+
|
|
+ FM_DMP_TITLE(buf, n, p_mm, "FM MAC - DTSEC-%d", h_mac->cell_index);
|
|
+
|
|
+ FM_DMP_V32(buf, n, p_mm, tsec_id);
|
|
+ FM_DMP_V32(buf, n, p_mm, tsec_id2);
|
|
+ FM_DMP_V32(buf, n, p_mm, ievent);
|
|
+ FM_DMP_V32(buf, n, p_mm, imask);
|
|
+ FM_DMP_V32(buf, n, p_mm, ecntrl);
|
|
+ FM_DMP_V32(buf, n, p_mm, ptv);
|
|
+ FM_DMP_V32(buf, n, p_mm, tmr_ctrl);
|
|
+ FM_DMP_V32(buf, n, p_mm, tmr_pevent);
|
|
+ FM_DMP_V32(buf, n, p_mm, tmr_pemask);
|
|
+ FM_DMP_V32(buf, n, p_mm, tctrl);
|
|
+ FM_DMP_V32(buf, n, p_mm, rctrl);
|
|
+ FM_DMP_V32(buf, n, p_mm, maccfg1);
|
|
+ FM_DMP_V32(buf, n, p_mm, maccfg2);
|
|
+ FM_DMP_V32(buf, n, p_mm, ipgifg);
|
|
+ FM_DMP_V32(buf, n, p_mm, hafdup);
|
|
+ FM_DMP_V32(buf, n, p_mm, maxfrm);
|
|
+
|
|
+ FM_DMP_V32(buf, n, p_mm, macstnaddr1);
|
|
+ FM_DMP_V32(buf, n, p_mm, macstnaddr2);
|
|
+
|
|
+ for (i = 0; i < 7; ++i) {
|
|
+ FM_DMP_V32(buf, n, p_mm, macaddr[i].exact_match1);
|
|
+ FM_DMP_V32(buf, n, p_mm, macaddr[i].exact_match2);
|
|
+ }
|
|
+
|
|
+ FM_DMP_V32(buf, n, p_mm, car1);
|
|
+ FM_DMP_V32(buf, n, p_mm, car2);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static int xgmac_dump_regs(struct mac_device *h_mac, char *buf, int nn)
|
|
+{
|
|
+ struct tgec_regs *p_mm = (struct tgec_regs *) h_mac->vaddr;
|
|
+ int n = nn;
|
|
+
|
|
+ FM_DMP_SUBTITLE(buf, n, "\n");
|
|
+ FM_DMP_TITLE(buf, n, p_mm, "FM MAC - TGEC -%d", h_mac->cell_index);
|
|
+
|
|
+ FM_DMP_V32(buf, n, p_mm, tgec_id);
|
|
+ FM_DMP_V32(buf, n, p_mm, command_config);
|
|
+ FM_DMP_V32(buf, n, p_mm, mac_addr_0);
|
|
+ FM_DMP_V32(buf, n, p_mm, mac_addr_1);
|
|
+ FM_DMP_V32(buf, n, p_mm, maxfrm);
|
|
+ FM_DMP_V32(buf, n, p_mm, pause_quant);
|
|
+ FM_DMP_V32(buf, n, p_mm, rx_fifo_sections);
|
|
+ FM_DMP_V32(buf, n, p_mm, tx_fifo_sections);
|
|
+ FM_DMP_V32(buf, n, p_mm, rx_fifo_almost_f_e);
|
|
+ FM_DMP_V32(buf, n, p_mm, tx_fifo_almost_f_e);
|
|
+ FM_DMP_V32(buf, n, p_mm, hashtable_ctrl);
|
|
+ FM_DMP_V32(buf, n, p_mm, mdio_cfg_status);
|
|
+ FM_DMP_V32(buf, n, p_mm, mdio_command);
|
|
+ FM_DMP_V32(buf, n, p_mm, mdio_data);
|
|
+ FM_DMP_V32(buf, n, p_mm, mdio_regaddr);
|
|
+ FM_DMP_V32(buf, n, p_mm, status);
|
|
+ FM_DMP_V32(buf, n, p_mm, tx_ipg_len);
|
|
+ FM_DMP_V32(buf, n, p_mm, mac_addr_2);
|
|
+ FM_DMP_V32(buf, n, p_mm, mac_addr_3);
|
|
+ FM_DMP_V32(buf, n, p_mm, rx_fifo_ptr_rd);
|
|
+ FM_DMP_V32(buf, n, p_mm, rx_fifo_ptr_wr);
|
|
+ FM_DMP_V32(buf, n, p_mm, tx_fifo_ptr_rd);
|
|
+ FM_DMP_V32(buf, n, p_mm, tx_fifo_ptr_wr);
|
|
+ FM_DMP_V32(buf, n, p_mm, imask);
|
|
+ FM_DMP_V32(buf, n, p_mm, ievent);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static int memac_dump_regs(struct mac_device *h_mac, char *buf, int nn)
|
|
+{
|
|
+ struct memac_regs *p_mm = (struct memac_regs *) h_mac->vaddr;
|
|
+ int i = 0, n = nn;
|
|
+
|
|
+ FM_DMP_SUBTITLE(buf, n, "\n");
|
|
+ FM_DMP_TITLE(buf, n, p_mm, "FM MAC - MEMAC -%d", h_mac->cell_index);
|
|
+
|
|
+ FM_DMP_V32(buf, n, p_mm, command_config);
|
|
+ FM_DMP_V32(buf, n, p_mm, mac_addr0.mac_addr_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, mac_addr0.mac_addr_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, maxfrm);
|
|
+ FM_DMP_V32(buf, n, p_mm, hashtable_ctrl);
|
|
+ FM_DMP_V32(buf, n, p_mm, ievent);
|
|
+ FM_DMP_V32(buf, n, p_mm, tx_ipg_length);
|
|
+ FM_DMP_V32(buf, n, p_mm, imask);
|
|
+
|
|
+ for (i = 0; i < 4; ++i)
|
|
+ FM_DMP_V32(buf, n, p_mm, pause_quanta[i]);
|
|
+
|
|
+ for (i = 0; i < 4; ++i)
|
|
+ FM_DMP_V32(buf, n, p_mm, pause_thresh[i]);
|
|
+
|
|
+ FM_DMP_V32(buf, n, p_mm, rx_pause_status);
|
|
+
|
|
+ for (i = 0; i < MEMAC_NUM_OF_PADDRS; ++i) {
|
|
+ FM_DMP_V32(buf, n, p_mm, mac_addr[i].mac_addr_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, mac_addr[i].mac_addr_u);
|
|
+ }
|
|
+
|
|
+ FM_DMP_V32(buf, n, p_mm, lpwake_timer);
|
|
+ FM_DMP_V32(buf, n, p_mm, sleep_timer);
|
|
+ FM_DMP_V32(buf, n, p_mm, statn_config);
|
|
+ FM_DMP_V32(buf, n, p_mm, if_mode);
|
|
+ FM_DMP_V32(buf, n, p_mm, if_status);
|
|
+ FM_DMP_V32(buf, n, p_mm, hg_config);
|
|
+ FM_DMP_V32(buf, n, p_mm, hg_pause_quanta);
|
|
+ FM_DMP_V32(buf, n, p_mm, hg_pause_thresh);
|
|
+ FM_DMP_V32(buf, n, p_mm, hgrx_pause_status);
|
|
+ FM_DMP_V32(buf, n, p_mm, hg_fifos_status);
|
|
+ FM_DMP_V32(buf, n, p_mm, rhm);
|
|
+ FM_DMP_V32(buf, n, p_mm, thm);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static int memac_dump_regs_rx(struct mac_device *h_mac, char *buf, int nn)
|
|
+{
|
|
+ struct memac_regs *p_mm = (struct memac_regs *) h_mac->vaddr;
|
|
+ int n = nn;
|
|
+
|
|
+ FM_DMP_SUBTITLE(buf, n, "\n");
|
|
+ FM_DMP_TITLE(buf, n, p_mm, "FM MAC - MEMAC -%d Rx stats", h_mac->cell_index);
|
|
+
|
|
+ /* Rx Statistics Counter */
|
|
+ FM_DMP_V32(buf, n, p_mm, reoct_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, reoct_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, roct_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, roct_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, raln_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, raln_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rxpf_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rxpf_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rfrm_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rfrm_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rfcs_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rfcs_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rvlan_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rvlan_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rerr_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rerr_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, ruca_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, ruca_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rmca_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rmca_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rbca_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rbca_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rdrp_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rdrp_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rpkt_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rpkt_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rund_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rund_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, r64_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, r64_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, r127_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, r127_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, r255_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, r255_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, r511_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, r511_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, r1023_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, r1023_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, r1518_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, r1518_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, r1519x_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, r1519x_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rovr_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rovr_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rjbr_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rjbr_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rfrg_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rfrg_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rcnp_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rcnp_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, rdrntp_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, rdrntp_u);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static int memac_dump_regs_tx(struct mac_device *h_mac, char *buf, int nn)
|
|
+{
|
|
+ struct memac_regs *p_mm = (struct memac_regs *) h_mac->vaddr;
|
|
+ int n = nn;
|
|
+
|
|
+ FM_DMP_SUBTITLE(buf, n, "\n");
|
|
+ FM_DMP_TITLE(buf, n, p_mm, "FM MAC - MEMAC -%d Tx stats", h_mac->cell_index);
|
|
+
|
|
+
|
|
+ /* Tx Statistics Counter */
|
|
+ FM_DMP_V32(buf, n, p_mm, teoct_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, teoct_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, toct_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, toct_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, txpf_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, txpf_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tfrm_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tfrm_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tfcs_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tfcs_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tvlan_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tvlan_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, terr_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, terr_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tuca_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tuca_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tmca_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tmca_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tbca_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tbca_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tpkt_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tpkt_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tund_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tund_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, t64_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, t64_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, t127_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, t127_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, t255_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, t255_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, t511_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, t511_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, t1023_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, t1023_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, t1518_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, t1518_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, t1519x_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, t1519x_u);
|
|
+ FM_DMP_V32(buf, n, p_mm, tcnp_l);
|
|
+ FM_DMP_V32(buf, n, p_mm, tcnp_u);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+int fm_mac_dump_regs(struct mac_device *h_mac, char *buf, int nn)
|
|
+{
|
|
+ int n = nn;
|
|
+
|
|
+ n = h_mac->dump_mac_regs(h_mac, buf, n);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+EXPORT_SYMBOL(fm_mac_dump_regs);
|
|
+
|
|
+int fm_mac_dump_rx_stats(struct mac_device *h_mac, char *buf, int nn)
|
|
+{
|
|
+ int n = nn;
|
|
+
|
|
+ if(h_mac->dump_mac_rx_stats)
|
|
+ n = h_mac->dump_mac_rx_stats(h_mac, buf, n);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+EXPORT_SYMBOL(fm_mac_dump_rx_stats);
|
|
+
|
|
+int fm_mac_dump_tx_stats(struct mac_device *h_mac, char *buf, int nn)
|
|
+{
|
|
+ int n = nn;
|
|
+
|
|
+ if(h_mac->dump_mac_tx_stats)
|
|
+ n = h_mac->dump_mac_tx_stats(h_mac, buf, n);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+EXPORT_SYMBOL(fm_mac_dump_tx_stats);
|
|
+
|
|
+static void __cold setup_dtsec(struct mac_device *mac_dev)
|
|
+{
|
|
+ mac_dev->init_phy = dtsec_init_phy;
|
|
+ mac_dev->init = init;
|
|
+ mac_dev->start = start;
|
|
+ mac_dev->stop = stop;
|
|
+ mac_dev->set_promisc = fm_mac_set_promiscuous;
|
|
+ mac_dev->change_addr = fm_mac_modify_mac_addr;
|
|
+ mac_dev->set_multi = set_multi;
|
|
+ mac_dev->uninit = uninit;
|
|
+ mac_dev->ptp_enable = fm_mac_enable_1588_time_stamp;
|
|
+ mac_dev->ptp_disable = fm_mac_disable_1588_time_stamp;
|
|
+ mac_dev->get_mac_handle = get_mac_handle;
|
|
+ mac_dev->set_tx_pause = fm_mac_set_tx_pause_frames;
|
|
+ mac_dev->set_rx_pause = fm_mac_set_rx_pause_frames;
|
|
+ mac_dev->fm_rtc_enable = fm_rtc_enable;
|
|
+ mac_dev->fm_rtc_disable = fm_rtc_disable;
|
|
+ mac_dev->fm_rtc_get_cnt = fm_rtc_get_cnt;
|
|
+ mac_dev->fm_rtc_set_cnt = fm_rtc_set_cnt;
|
|
+ mac_dev->fm_rtc_get_drift = fm_rtc_get_drift;
|
|
+ mac_dev->fm_rtc_set_drift = fm_rtc_set_drift;
|
|
+ mac_dev->fm_rtc_set_alarm = fm_rtc_set_alarm;
|
|
+ mac_dev->fm_rtc_set_fiper = fm_rtc_set_fiper;
|
|
+ mac_dev->set_wol = fm_mac_set_wol;
|
|
+ mac_dev->dump_mac_regs = dtsec_dump_regs;
|
|
+}
|
|
+
|
|
+static void __cold setup_xgmac(struct mac_device *mac_dev)
|
|
+{
|
|
+ mac_dev->init_phy = xgmac_init_phy;
|
|
+ mac_dev->init = init;
|
|
+ mac_dev->start = start;
|
|
+ mac_dev->stop = stop;
|
|
+ mac_dev->set_promisc = fm_mac_set_promiscuous;
|
|
+ mac_dev->change_addr = fm_mac_modify_mac_addr;
|
|
+ mac_dev->set_multi = set_multi;
|
|
+ mac_dev->uninit = uninit;
|
|
+ mac_dev->get_mac_handle = get_mac_handle;
|
|
+ mac_dev->set_tx_pause = fm_mac_set_tx_pause_frames;
|
|
+ mac_dev->set_rx_pause = fm_mac_set_rx_pause_frames;
|
|
+ mac_dev->set_wol = fm_mac_set_wol;
|
|
+ mac_dev->dump_mac_regs = xgmac_dump_regs;
|
|
+}
|
|
+
|
|
+static void __cold setup_memac(struct mac_device *mac_dev)
|
|
+{
|
|
+ mac_dev->init_phy = memac_init_phy;
|
|
+ mac_dev->init = memac_init;
|
|
+ mac_dev->start = start;
|
|
+ mac_dev->stop = stop;
|
|
+ mac_dev->set_promisc = fm_mac_set_promiscuous;
|
|
+ mac_dev->change_addr = fm_mac_modify_mac_addr;
|
|
+ mac_dev->set_multi = set_multi;
|
|
+ mac_dev->uninit = uninit;
|
|
+ mac_dev->get_mac_handle = get_mac_handle;
|
|
+ mac_dev->set_tx_pause = fm_mac_set_tx_pause_frames;
|
|
+ mac_dev->set_rx_pause = fm_mac_set_rx_pause_frames;
|
|
+ mac_dev->fm_rtc_enable = fm_rtc_enable;
|
|
+ mac_dev->fm_rtc_disable = fm_rtc_disable;
|
|
+ mac_dev->fm_rtc_get_cnt = fm_rtc_get_cnt;
|
|
+ mac_dev->fm_rtc_set_cnt = fm_rtc_set_cnt;
|
|
+ mac_dev->fm_rtc_get_drift = fm_rtc_get_drift;
|
|
+ mac_dev->fm_rtc_set_drift = fm_rtc_set_drift;
|
|
+ mac_dev->fm_rtc_set_alarm = fm_rtc_set_alarm;
|
|
+ mac_dev->fm_rtc_set_fiper = fm_rtc_set_fiper;
|
|
+ mac_dev->set_wol = fm_mac_set_wol;
|
|
+ mac_dev->dump_mac_regs = memac_dump_regs;
|
|
+ mac_dev->dump_mac_rx_stats = memac_dump_regs_rx;
|
|
+ mac_dev->dump_mac_tx_stats = memac_dump_regs_tx;
|
|
+}
|
|
+
|
|
+void (*const mac_setup[])(struct mac_device *mac_dev) = {
|
|
+ [DTSEC] = setup_dtsec,
|
|
+ [XGMAC] = setup_xgmac,
|
|
+ [MEMAC] = setup_memac
|
|
+};
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/mac.c
|
|
@@ -0,0 +1,489 @@
|
|
+/* Copyright 2008-2012 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": %s:%hu:%s() " fmt, \
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__
|
|
+#else
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": " fmt
|
|
+#endif
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/of_net.h>
|
|
+#include <linux/of_mdio.h>
|
|
+#include <linux/phy_fixed.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/io.h>
|
|
+
|
|
+#include "lnxwrp_fm_ext.h"
|
|
+
|
|
+#include "mac.h"
|
|
+
|
|
+#define DTSEC_SUPPORTED \
|
|
+ (SUPPORTED_10baseT_Half \
|
|
+ | SUPPORTED_10baseT_Full \
|
|
+ | SUPPORTED_100baseT_Half \
|
|
+ | SUPPORTED_100baseT_Full \
|
|
+ | SUPPORTED_Autoneg \
|
|
+ | SUPPORTED_Pause \
|
|
+ | SUPPORTED_Asym_Pause \
|
|
+ | SUPPORTED_MII)
|
|
+
|
|
+static const char phy_str[][11] = {
|
|
+ [PHY_INTERFACE_MODE_MII] = "mii",
|
|
+ [PHY_INTERFACE_MODE_GMII] = "gmii",
|
|
+ [PHY_INTERFACE_MODE_SGMII] = "sgmii",
|
|
+ [PHY_INTERFACE_MODE_QSGMII] = "qsgmii",
|
|
+ [PHY_INTERFACE_MODE_TBI] = "tbi",
|
|
+ [PHY_INTERFACE_MODE_RMII] = "rmii",
|
|
+ [PHY_INTERFACE_MODE_RGMII] = "rgmii",
|
|
+ [PHY_INTERFACE_MODE_RGMII_ID] = "rgmii-id",
|
|
+ [PHY_INTERFACE_MODE_RGMII_RXID] = "rgmii-rxid",
|
|
+ [PHY_INTERFACE_MODE_RGMII_TXID] = "rgmii-txid",
|
|
+ [PHY_INTERFACE_MODE_RTBI] = "rtbi",
|
|
+ [PHY_INTERFACE_MODE_XGMII] = "xgmii",
|
|
+ [PHY_INTERFACE_MODE_SGMII_2500] = "sgmii-2500",
|
|
+};
|
|
+
|
|
+static phy_interface_t __pure __attribute__((nonnull)) str2phy(const char *str)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(phy_str); i++)
|
|
+ if (strcmp(str, phy_str[i]) == 0)
|
|
+ return (phy_interface_t)i;
|
|
+
|
|
+ return PHY_INTERFACE_MODE_MII;
|
|
+}
|
|
+
|
|
+static const uint16_t phy2speed[] = {
|
|
+ [PHY_INTERFACE_MODE_MII] = SPEED_100,
|
|
+ [PHY_INTERFACE_MODE_GMII] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_SGMII] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_QSGMII] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_TBI] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_RMII] = SPEED_100,
|
|
+ [PHY_INTERFACE_MODE_RGMII] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_RGMII_ID] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_RGMII_RXID] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_RGMII_TXID] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_RTBI] = SPEED_1000,
|
|
+ [PHY_INTERFACE_MODE_XGMII] = SPEED_10000,
|
|
+ [PHY_INTERFACE_MODE_SGMII_2500] = SPEED_2500,
|
|
+};
|
|
+
|
|
+static struct mac_device * __cold
|
|
+alloc_macdev(struct device *dev, size_t sizeof_priv,
|
|
+ void (*setup)(struct mac_device *mac_dev))
|
|
+{
|
|
+ struct mac_device *mac_dev;
|
|
+
|
|
+ mac_dev = devm_kzalloc(dev, sizeof(*mac_dev) + sizeof_priv, GFP_KERNEL);
|
|
+ if (unlikely(mac_dev == NULL))
|
|
+ mac_dev = ERR_PTR(-ENOMEM);
|
|
+ else {
|
|
+ mac_dev->dev = dev;
|
|
+ dev_set_drvdata(dev, mac_dev);
|
|
+ setup(mac_dev);
|
|
+ }
|
|
+
|
|
+ return mac_dev;
|
|
+}
|
|
+
|
|
+static int __cold free_macdev(struct mac_device *mac_dev)
|
|
+{
|
|
+ dev_set_drvdata(mac_dev->dev, NULL);
|
|
+
|
|
+ return mac_dev->uninit(mac_dev->get_mac_handle(mac_dev));
|
|
+}
|
|
+
|
|
+static const struct of_device_id mac_match[] = {
|
|
+ [DTSEC] = {
|
|
+ .compatible = "fsl,fman-1g-mac"
|
|
+ },
|
|
+ [XGMAC] = {
|
|
+ .compatible = "fsl,fman-10g-mac"
|
|
+ },
|
|
+ [MEMAC] = {
|
|
+ .compatible = "fsl,fman-memac"
|
|
+ },
|
|
+ {}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, mac_match);
|
|
+
|
|
+static int __cold mac_probe(struct platform_device *_of_dev)
|
|
+{
|
|
+ int _errno, i;
|
|
+ struct device *dev;
|
|
+ struct device_node *mac_node, *dev_node;
|
|
+ struct mac_device *mac_dev;
|
|
+ struct platform_device *of_dev;
|
|
+ struct resource res;
|
|
+ const uint8_t *mac_addr;
|
|
+ const char *char_prop;
|
|
+ int nph;
|
|
+ u32 cell_index;
|
|
+ const struct of_device_id *match;
|
|
+
|
|
+ dev = &_of_dev->dev;
|
|
+ mac_node = dev->of_node;
|
|
+
|
|
+ match = of_match_device(mac_match, dev);
|
|
+ if (!match)
|
|
+ return -EINVAL;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(mac_match) - 1 && match != mac_match + i;
|
|
+ i++)
|
|
+ ;
|
|
+ BUG_ON(i >= ARRAY_SIZE(mac_match) - 1);
|
|
+
|
|
+ mac_dev = alloc_macdev(dev, mac_sizeof_priv[i], mac_setup[i]);
|
|
+ if (IS_ERR(mac_dev)) {
|
|
+ _errno = PTR_ERR(mac_dev);
|
|
+ dev_err(dev, "alloc_macdev() = %d\n", _errno);
|
|
+ goto _return;
|
|
+ }
|
|
+
|
|
+ INIT_LIST_HEAD(&mac_dev->mc_addr_list);
|
|
+
|
|
+ /* Get the FM node */
|
|
+ dev_node = of_get_parent(mac_node);
|
|
+ if (unlikely(dev_node == NULL)) {
|
|
+ dev_err(dev, "of_get_parent(%s) failed\n",
|
|
+ mac_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+
|
|
+ of_dev = of_find_device_by_node(dev_node);
|
|
+ if (unlikely(of_dev == NULL)) {
|
|
+ dev_err(dev, "of_find_device_by_node(%s) failed\n",
|
|
+ dev_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto _return_of_node_put;
|
|
+ }
|
|
+
|
|
+ mac_dev->fm_dev = fm_bind(&of_dev->dev);
|
|
+ if (unlikely(mac_dev->fm_dev == NULL)) {
|
|
+ dev_err(dev, "fm_bind(%s) failed\n", dev_node->full_name);
|
|
+ _errno = -ENODEV;
|
|
+ goto _return_of_node_put;
|
|
+ }
|
|
+
|
|
+ mac_dev->fm = (void *)fm_get_handle(mac_dev->fm_dev);
|
|
+ of_node_put(dev_node);
|
|
+
|
|
+ /* Get the address of the memory mapped registers */
|
|
+ _errno = of_address_to_resource(mac_node, 0, &res);
|
|
+ if (unlikely(_errno < 0)) {
|
|
+ dev_err(dev, "of_address_to_resource(%s) = %d\n",
|
|
+ mac_node->full_name, _errno);
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+
|
|
+ mac_dev->res = __devm_request_region(
|
|
+ dev,
|
|
+ fm_get_mem_region(mac_dev->fm_dev),
|
|
+ res.start, res.end + 1 - res.start, "mac");
|
|
+ if (unlikely(mac_dev->res == NULL)) {
|
|
+ dev_err(dev, "__devm_request_mem_region(mac) failed\n");
|
|
+ _errno = -EBUSY;
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+
|
|
+ mac_dev->vaddr = devm_ioremap(dev, mac_dev->res->start,
|
|
+ mac_dev->res->end + 1
|
|
+ - mac_dev->res->start);
|
|
+ if (unlikely(mac_dev->vaddr == NULL)) {
|
|
+ dev_err(dev, "devm_ioremap() failed\n");
|
|
+ _errno = -EIO;
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+
|
|
+#define TBIPA_OFFSET 0x1c
|
|
+#define TBIPA_DEFAULT_ADDR 5 /* override if used as external PHY addr. */
|
|
+ mac_dev->tbi_node = of_parse_phandle(mac_node, "tbi-handle", 0);
|
|
+ if (mac_dev->tbi_node) {
|
|
+ u32 tbiaddr = TBIPA_DEFAULT_ADDR;
|
|
+ const __be32 *tbi_reg;
|
|
+ void __iomem *addr;
|
|
+
|
|
+ tbi_reg = of_get_property(mac_dev->tbi_node, "reg", NULL);
|
|
+ if (tbi_reg)
|
|
+ tbiaddr = be32_to_cpup(tbi_reg);
|
|
+ addr = mac_dev->vaddr + TBIPA_OFFSET;
|
|
+ /* TODO: out_be32 does not exist on ARM */
|
|
+ out_be32(addr, tbiaddr);
|
|
+ }
|
|
+
|
|
+ if (!of_device_is_available(mac_node)) {
|
|
+ devm_iounmap(dev, mac_dev->vaddr);
|
|
+ __devm_release_region(dev, fm_get_mem_region(mac_dev->fm_dev),
|
|
+ res.start, res.end + 1 - res.start);
|
|
+ fm_unbind(mac_dev->fm_dev);
|
|
+ devm_kfree(dev, mac_dev);
|
|
+ dev_set_drvdata(dev, NULL);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ /* Get the cell-index */
|
|
+ _errno = of_property_read_u32(mac_node, "cell-index", &cell_index);
|
|
+ if (unlikely(_errno)) {
|
|
+ dev_err(dev, "Cannot read cell-index of mac node %s from device tree\n",
|
|
+ mac_node->full_name);
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+ mac_dev->cell_index = (uint8_t)cell_index;
|
|
+ if (mac_dev->cell_index >= 8)
|
|
+ mac_dev->cell_index -= 8;
|
|
+
|
|
+ /* Get the MAC address */
|
|
+ mac_addr = of_get_mac_address(mac_node);
|
|
+ if (unlikely(mac_addr == NULL)) {
|
|
+ dev_err(dev, "of_get_mac_address(%s) failed\n",
|
|
+ mac_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+ memcpy(mac_dev->addr, mac_addr, sizeof(mac_dev->addr));
|
|
+
|
|
+ /* Verify the number of port handles */
|
|
+ nph = of_count_phandle_with_args(mac_node, "fsl,fman-ports", NULL);
|
|
+ if (unlikely(nph < 0)) {
|
|
+ dev_err(dev, "Cannot read port handles of mac node %s from device tree\n",
|
|
+ mac_node->full_name);
|
|
+ _errno = nph;
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+
|
|
+ if (nph != ARRAY_SIZE(mac_dev->port_dev)) {
|
|
+ dev_err(dev, "Not supported number of port handles of mac node %s from device tree\n",
|
|
+ mac_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+
|
|
+ for_each_port_device(i, mac_dev->port_dev) {
|
|
+ dev_node = of_parse_phandle(mac_node, "fsl,fman-ports", i);
|
|
+ if (unlikely(dev_node == NULL)) {
|
|
+ dev_err(dev, "Cannot find port node referenced by mac node %s from device tree\n",
|
|
+ mac_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto _return_of_node_put;
|
|
+ }
|
|
+
|
|
+ of_dev = of_find_device_by_node(dev_node);
|
|
+ if (unlikely(of_dev == NULL)) {
|
|
+ dev_err(dev, "of_find_device_by_node(%s) failed\n",
|
|
+ dev_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto _return_of_node_put;
|
|
+ }
|
|
+
|
|
+ mac_dev->port_dev[i] = fm_port_bind(&of_dev->dev);
|
|
+ if (unlikely(mac_dev->port_dev[i] == NULL)) {
|
|
+ dev_err(dev, "dev_get_drvdata(%s) failed\n",
|
|
+ dev_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto _return_of_node_put;
|
|
+ }
|
|
+ of_node_put(dev_node);
|
|
+ }
|
|
+
|
|
+ /* Get the PHY connection type */
|
|
+ _errno = of_property_read_string(mac_node, "phy-connection-type",
|
|
+ &char_prop);
|
|
+ if (unlikely(_errno)) {
|
|
+ dev_warn(dev,
|
|
+ "Cannot read PHY connection type of mac node %s from device tree. Defaulting to MII\n",
|
|
+ mac_node->full_name);
|
|
+ mac_dev->phy_if = PHY_INTERFACE_MODE_MII;
|
|
+ } else
|
|
+ mac_dev->phy_if = str2phy(char_prop);
|
|
+
|
|
+ mac_dev->link = false;
|
|
+ mac_dev->half_duplex = false;
|
|
+ mac_dev->speed = phy2speed[mac_dev->phy_if];
|
|
+ mac_dev->max_speed = mac_dev->speed;
|
|
+ mac_dev->if_support = DTSEC_SUPPORTED;
|
|
+ /* We don't support half-duplex in SGMII mode */
|
|
+ if (strstr(char_prop, "sgmii") || strstr(char_prop, "qsgmii") ||
|
|
+ strstr(char_prop, "sgmii-2500"))
|
|
+ mac_dev->if_support &= ~(SUPPORTED_10baseT_Half |
|
|
+ SUPPORTED_100baseT_Half);
|
|
+
|
|
+ /* Gigabit support (no half-duplex) */
|
|
+ if (mac_dev->max_speed == SPEED_1000 ||
|
|
+ mac_dev->max_speed == SPEED_2500)
|
|
+ mac_dev->if_support |= SUPPORTED_1000baseT_Full;
|
|
+
|
|
+ /* The 10G interface only supports one mode */
|
|
+ if (strstr(char_prop, "xgmii"))
|
|
+ mac_dev->if_support = SUPPORTED_10000baseT_Full;
|
|
+
|
|
+ /* Get the rest of the PHY information */
|
|
+ mac_dev->phy_node = of_parse_phandle(mac_node, "phy-handle", 0);
|
|
+ if (!mac_dev->phy_node) {
|
|
+ struct phy_device *phy;
|
|
+
|
|
+ if (!of_phy_is_fixed_link(mac_node)) {
|
|
+ dev_err(dev, "Wrong PHY information of mac node %s\n",
|
|
+ mac_node->full_name);
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+
|
|
+ _errno = of_phy_register_fixed_link(mac_node);
|
|
+ if (_errno)
|
|
+ goto _return_dev_set_drvdata;
|
|
+
|
|
+ mac_dev->fixed_link = devm_kzalloc(mac_dev->dev,
|
|
+ sizeof(*mac_dev->fixed_link),
|
|
+ GFP_KERNEL);
|
|
+ if (!mac_dev->fixed_link)
|
|
+ goto _return_dev_set_drvdata;
|
|
+
|
|
+ mac_dev->phy_node = of_node_get(mac_node);
|
|
+ phy = of_phy_find_device(mac_dev->phy_node);
|
|
+ if (!phy)
|
|
+ goto _return_dev_set_drvdata;
|
|
+
|
|
+ mac_dev->fixed_link->link = phy->link;
|
|
+ mac_dev->fixed_link->speed = phy->speed;
|
|
+ mac_dev->fixed_link->duplex = phy->duplex;
|
|
+ mac_dev->fixed_link->pause = phy->pause;
|
|
+ mac_dev->fixed_link->asym_pause = phy->asym_pause;
|
|
+ }
|
|
+
|
|
+ _errno = mac_dev->init(mac_dev);
|
|
+ if (unlikely(_errno < 0)) {
|
|
+ dev_err(dev, "mac_dev->init() = %d\n", _errno);
|
|
+ goto _return_dev_set_drvdata;
|
|
+ }
|
|
+
|
|
+ /* pause frame autonegotiation enabled*/
|
|
+ mac_dev->autoneg_pause = true;
|
|
+
|
|
+ /* by intializing the values to false, force FMD to enable PAUSE frames
|
|
+ * on RX and TX
|
|
+ */
|
|
+ mac_dev->rx_pause_req = mac_dev->tx_pause_req = true;
|
|
+ mac_dev->rx_pause_active = mac_dev->tx_pause_active = false;
|
|
+ _errno = set_mac_active_pause(mac_dev, true, true);
|
|
+ if (unlikely(_errno < 0))
|
|
+ dev_err(dev, "set_mac_active_pause() = %d\n", _errno);
|
|
+
|
|
+ dev_info(dev,
|
|
+ "FMan MAC address: %02hx:%02hx:%02hx:%02hx:%02hx:%02hx\n",
|
|
+ mac_dev->addr[0], mac_dev->addr[1], mac_dev->addr[2],
|
|
+ mac_dev->addr[3], mac_dev->addr[4], mac_dev->addr[5]);
|
|
+
|
|
+ goto _return;
|
|
+
|
|
+_return_of_node_put:
|
|
+ of_node_put(dev_node);
|
|
+_return_dev_set_drvdata:
|
|
+ dev_set_drvdata(dev, NULL);
|
|
+_return:
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static int __cold mac_remove(struct platform_device *of_dev)
|
|
+{
|
|
+ int i, _errno;
|
|
+ struct device *dev;
|
|
+ struct mac_device *mac_dev;
|
|
+
|
|
+ dev = &of_dev->dev;
|
|
+ mac_dev = (struct mac_device *)dev_get_drvdata(dev);
|
|
+
|
|
+ for_each_port_device(i, mac_dev->port_dev)
|
|
+ fm_port_unbind(mac_dev->port_dev[i]);
|
|
+
|
|
+ fm_unbind(mac_dev->fm_dev);
|
|
+
|
|
+ _errno = free_macdev(mac_dev);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static struct platform_driver mac_driver = {
|
|
+ .driver = {
|
|
+ .name = KBUILD_MODNAME,
|
|
+ .of_match_table = mac_match,
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+ .probe = mac_probe,
|
|
+ .remove = mac_remove
|
|
+};
|
|
+
|
|
+static int __init __cold mac_load(void)
|
|
+{
|
|
+ int _errno;
|
|
+
|
|
+ pr_debug(KBUILD_MODNAME ": -> %s:%s()\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+
|
|
+ pr_info(KBUILD_MODNAME ": %s\n", mac_driver_description);
|
|
+
|
|
+ _errno = platform_driver_register(&mac_driver);
|
|
+ if (unlikely(_errno < 0)) {
|
|
+ pr_err(KBUILD_MODNAME ": %s:%hu:%s(): platform_driver_register() = %d\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__, _errno);
|
|
+ goto _return;
|
|
+ }
|
|
+
|
|
+ goto _return;
|
|
+
|
|
+_return:
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+
|
|
+ return _errno;
|
|
+}
|
|
+module_init(mac_load);
|
|
+
|
|
+static void __exit __cold mac_unload(void)
|
|
+{
|
|
+ pr_debug(KBUILD_MODNAME ": -> %s:%s()\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+
|
|
+ platform_driver_unregister(&mac_driver);
|
|
+
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+}
|
|
+module_exit(mac_unload);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/mac.h
|
|
@@ -0,0 +1,135 @@
|
|
+/* Copyright 2008-2011 Freescale Semiconductor, Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifndef __MAC_H
|
|
+#define __MAC_H
|
|
+
|
|
+#include <linux/device.h> /* struct device, BUS_ID_SIZE */
|
|
+#include <linux/if_ether.h> /* ETH_ALEN */
|
|
+#include <linux/phy.h> /* phy_interface_t, struct phy_device */
|
|
+#include <linux/list.h>
|
|
+
|
|
+#include "lnxwrp_fsl_fman.h" /* struct port_device */
|
|
+
|
|
+enum {DTSEC, XGMAC, MEMAC};
|
|
+
|
|
+struct mac_device {
|
|
+ struct device *dev;
|
|
+ void *priv;
|
|
+ uint8_t cell_index;
|
|
+ struct resource *res;
|
|
+ void __iomem *vaddr;
|
|
+ uint8_t addr[ETH_ALEN];
|
|
+ bool promisc;
|
|
+
|
|
+ struct fm *fm_dev;
|
|
+ struct fm_port *port_dev[2];
|
|
+
|
|
+ phy_interface_t phy_if;
|
|
+ u32 if_support;
|
|
+ bool link;
|
|
+ bool half_duplex;
|
|
+ uint16_t speed;
|
|
+ uint16_t max_speed;
|
|
+ struct device_node *phy_node;
|
|
+ char fixed_bus_id[MII_BUS_ID_SIZE + 3];
|
|
+ struct device_node *tbi_node;
|
|
+ struct phy_device *phy_dev;
|
|
+ void *fm;
|
|
+ /* List of multicast addresses */
|
|
+ struct list_head mc_addr_list;
|
|
+ struct fixed_phy_status *fixed_link;
|
|
+
|
|
+ bool autoneg_pause;
|
|
+ bool rx_pause_req;
|
|
+ bool tx_pause_req;
|
|
+ bool rx_pause_active;
|
|
+ bool tx_pause_active;
|
|
+
|
|
+ struct fm_mac_dev *(*get_mac_handle)(struct mac_device *mac_dev);
|
|
+ int (*init_phy)(struct net_device *net_dev, struct mac_device *mac_dev);
|
|
+ int (*init)(struct mac_device *mac_dev);
|
|
+ int (*start)(struct mac_device *mac_dev);
|
|
+ int (*stop)(struct mac_device *mac_dev);
|
|
+ int (*set_promisc)(struct fm_mac_dev *fm_mac_dev, bool enable);
|
|
+ int (*change_addr)(struct fm_mac_dev *fm_mac_dev, uint8_t *addr);
|
|
+ int (*set_multi)(struct net_device *net_dev,
|
|
+ struct mac_device *mac_dev);
|
|
+ int (*uninit)(struct fm_mac_dev *fm_mac_dev);
|
|
+ int (*ptp_enable)(struct fm_mac_dev *fm_mac_dev);
|
|
+ int (*ptp_disable)(struct fm_mac_dev *fm_mac_dev);
|
|
+ int (*set_rx_pause)(struct fm_mac_dev *fm_mac_dev, bool en);
|
|
+ int (*set_tx_pause)(struct fm_mac_dev *fm_mac_dev, bool en);
|
|
+ int (*fm_rtc_enable)(struct fm *fm_dev);
|
|
+ int (*fm_rtc_disable)(struct fm *fm_dev);
|
|
+ int (*fm_rtc_get_cnt)(struct fm *fm_dev, uint64_t *ts);
|
|
+ int (*fm_rtc_set_cnt)(struct fm *fm_dev, uint64_t ts);
|
|
+ int (*fm_rtc_get_drift)(struct fm *fm_dev, uint32_t *drift);
|
|
+ int (*fm_rtc_set_drift)(struct fm *fm_dev, uint32_t drift);
|
|
+ int (*fm_rtc_set_alarm)(struct fm *fm_dev, uint32_t id, uint64_t time);
|
|
+ int (*fm_rtc_set_fiper)(struct fm *fm_dev, uint32_t id,
|
|
+ uint64_t fiper);
|
|
+#ifdef CONFIG_PTP_1588_CLOCK_DPAA
|
|
+ int (*fm_rtc_enable_interrupt)(struct fm *fm_dev, uint32_t events);
|
|
+ int (*fm_rtc_disable_interrupt)(struct fm *fm_dev, uint32_t events);
|
|
+#endif
|
|
+ int (*set_wol)(struct fm_port *port, struct fm_mac_dev *fm_mac_dev,
|
|
+ bool en);
|
|
+ int (*dump_mac_regs)(struct mac_device *h_mac, char *buf, int nn);
|
|
+ int (*dump_mac_rx_stats)(struct mac_device *h_mac, char *buf, int nn);
|
|
+ int (*dump_mac_tx_stats)(struct mac_device *h_mac, char *buf, int nn);
|
|
+};
|
|
+
|
|
+struct mac_address {
|
|
+ uint8_t addr[ETH_ALEN];
|
|
+ struct list_head list;
|
|
+};
|
|
+
|
|
+#define get_fm_handle(net_dev) \
|
|
+ (((struct dpa_priv_s *)netdev_priv(net_dev))->mac_dev->fm_dev)
|
|
+
|
|
+#define for_each_port_device(i, port_dev) \
|
|
+ for (i = 0; i < ARRAY_SIZE(port_dev); i++)
|
|
+
|
|
+static inline __attribute((nonnull)) void *macdev_priv(
|
|
+ const struct mac_device *mac_dev)
|
|
+{
|
|
+ return (void *)mac_dev + sizeof(*mac_dev);
|
|
+}
|
|
+
|
|
+extern const char *mac_driver_description;
|
|
+extern const size_t mac_sizeof_priv[];
|
|
+extern void (*const mac_setup[])(struct mac_device *mac_dev);
|
|
+
|
|
+int set_mac_active_pause(struct mac_device *mac_dev, bool rx, bool tx);
|
|
+void get_pause_cfg(struct mac_device *mac_dev, bool *rx_pause, bool *tx_pause);
|
|
+
|
|
+#endif /* __MAC_H */
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/offline_port.c
|
|
@@ -0,0 +1,848 @@
|
|
+/* Copyright 2011-2012 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+/* Offline Parsing / Host Command port driver for FSL QorIQ FMan.
|
|
+ * Validates device-tree configuration and sets up the offline ports.
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_FSL_DPAA_ETH_DEBUG
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": %s:%hu:%s() " fmt, \
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__
|
|
+#else
|
|
+#define pr_fmt(fmt) \
|
|
+ KBUILD_MODNAME ": " fmt
|
|
+#endif
|
|
+
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/fsl_qman.h>
|
|
+
|
|
+#include "offline_port.h"
|
|
+#include "dpaa_eth.h"
|
|
+#include "dpaa_eth_common.h"
|
|
+
|
|
+#define OH_MOD_DESCRIPTION "FSL FMan Offline Parsing port driver"
|
|
+/* Manip extra space and data alignment for fragmentation */
|
|
+#define FRAG_MANIP_SPACE 128
|
|
+#define FRAG_DATA_ALIGN 64
|
|
+
|
|
+
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
+MODULE_AUTHOR("Bogdan Hamciuc <bogdan.hamciuc@freescale.com>");
|
|
+MODULE_DESCRIPTION(OH_MOD_DESCRIPTION);
|
|
+
|
|
+
|
|
+static const struct of_device_id oh_port_match_table[] = {
|
|
+ {
|
|
+ .compatible = "fsl,dpa-oh"
|
|
+ },
|
|
+ {
|
|
+ .compatible = "fsl,dpa-oh-shared"
|
|
+ },
|
|
+ {}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, oh_port_match_table);
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+
|
|
+static int oh_suspend(struct device *dev)
|
|
+{
|
|
+ struct dpa_oh_config_s *oh_config;
|
|
+
|
|
+ oh_config = dev_get_drvdata(dev);
|
|
+ return fm_port_suspend(oh_config->oh_port);
|
|
+}
|
|
+
|
|
+static int oh_resume(struct device *dev)
|
|
+{
|
|
+ struct dpa_oh_config_s *oh_config;
|
|
+
|
|
+ oh_config = dev_get_drvdata(dev);
|
|
+ return fm_port_resume(oh_config->oh_port);
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops oh_pm_ops = {
|
|
+ .suspend = oh_suspend,
|
|
+ .resume = oh_resume,
|
|
+};
|
|
+
|
|
+#define OH_PM_OPS (&oh_pm_ops)
|
|
+
|
|
+#else /* CONFIG_PM */
|
|
+
|
|
+#define OH_PM_OPS NULL
|
|
+
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+/* Creates Frame Queues */
|
|
+static uint32_t oh_fq_create(struct qman_fq *fq,
|
|
+ uint32_t fq_id, uint16_t channel,
|
|
+ uint16_t wq_id)
|
|
+{
|
|
+ struct qm_mcc_initfq fq_opts;
|
|
+ uint32_t create_flags, init_flags;
|
|
+ uint32_t ret = 0;
|
|
+
|
|
+ if (fq == NULL)
|
|
+ return 1;
|
|
+
|
|
+ /* Set flags for FQ create */
|
|
+ create_flags = QMAN_FQ_FLAG_LOCKED | QMAN_FQ_FLAG_TO_DCPORTAL;
|
|
+
|
|
+ /* Create frame queue */
|
|
+ ret = qman_create_fq(fq_id, create_flags, fq);
|
|
+ if (ret != 0)
|
|
+ return 1;
|
|
+
|
|
+ /* Set flags for FQ init */
|
|
+ init_flags = QMAN_INITFQ_FLAG_SCHED;
|
|
+
|
|
+ /* Set FQ init options. Specify destination WQ ID and channel */
|
|
+ fq_opts.we_mask = QM_INITFQ_WE_DESTWQ;
|
|
+ fq_opts.fqd.dest.wq = wq_id;
|
|
+ fq_opts.fqd.dest.channel = channel;
|
|
+
|
|
+ /* Initialize frame queue */
|
|
+ ret = qman_init_fq(fq, init_flags, &fq_opts);
|
|
+ if (ret != 0) {
|
|
+ qman_destroy_fq(fq, 0);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dump_fq(struct device *dev, int fqid, uint16_t channel)
|
|
+{
|
|
+ if (channel) {
|
|
+ /* display fqs with a valid (!= 0) destination channel */
|
|
+ dev_info(dev, "FQ ID:%d Channel ID:%d\n", fqid, channel);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void dump_fq_duple(struct device *dev, struct qman_fq *fqs,
|
|
+ int fqs_count, uint16_t channel_id)
|
|
+{
|
|
+ int i;
|
|
+ for (i = 0; i < fqs_count; i++)
|
|
+ dump_fq(dev, (fqs + i)->fqid, channel_id);
|
|
+}
|
|
+
|
|
+static void dump_oh_config(struct device *dev, struct dpa_oh_config_s *conf)
|
|
+{
|
|
+ struct list_head *fq_list;
|
|
+ struct fq_duple *fqd;
|
|
+ int i;
|
|
+
|
|
+ dev_info(dev, "Default egress frame queue: %d\n", conf->default_fqid);
|
|
+ dev_info(dev, "Default error frame queue: %d\n", conf->error_fqid);
|
|
+
|
|
+ /* TX queues (old initialization) */
|
|
+ dev_info(dev, "Initialized queues:");
|
|
+ for (i = 0; i < conf->egress_cnt; i++)
|
|
+ dump_fq_duple(dev, conf->egress_fqs, conf->egress_cnt,
|
|
+ conf->channel);
|
|
+
|
|
+ /* initialized ingress queues */
|
|
+ list_for_each(fq_list, &conf->fqs_ingress_list) {
|
|
+ fqd = list_entry(fq_list, struct fq_duple, fq_list);
|
|
+ dump_fq_duple(dev, fqd->fqs, fqd->fqs_count, fqd->channel_id);
|
|
+ }
|
|
+
|
|
+ /* initialized egress queues */
|
|
+ list_for_each(fq_list, &conf->fqs_egress_list) {
|
|
+ fqd = list_entry(fq_list, struct fq_duple, fq_list);
|
|
+ dump_fq_duple(dev, fqd->fqs, fqd->fqs_count, fqd->channel_id);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Destroys Frame Queues */
|
|
+static void oh_fq_destroy(struct qman_fq *fq)
|
|
+{
|
|
+ int _errno = 0;
|
|
+
|
|
+ _errno = qman_retire_fq(fq, NULL);
|
|
+ if (unlikely(_errno < 0))
|
|
+ pr_err(KBUILD_MODNAME": %s:%hu:%s(): qman_retire_fq(%u)=%d\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__,
|
|
+ qman_fq_fqid(fq), _errno);
|
|
+
|
|
+ _errno = qman_oos_fq(fq);
|
|
+ if (unlikely(_errno < 0)) {
|
|
+ pr_err(KBUILD_MODNAME": %s:%hu:%s(): qman_oos_fq(%u)=%d\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__,
|
|
+ qman_fq_fqid(fq), _errno);
|
|
+ }
|
|
+
|
|
+ qman_destroy_fq(fq, 0);
|
|
+}
|
|
+
|
|
+/* Allocation code for the OH port's PCD frame queues */
|
|
+static int __cold oh_alloc_pcd_fqids(struct device *dev,
|
|
+ uint32_t num,
|
|
+ uint8_t alignment,
|
|
+ uint32_t *base_fqid)
|
|
+{
|
|
+ dev_crit(dev, "callback not implemented!\n");
|
|
+ BUG();
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __cold oh_free_pcd_fqids(struct device *dev, uint32_t base_fqid)
|
|
+{
|
|
+ dev_crit(dev, "callback not implemented!\n");
|
|
+ BUG();
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void oh_set_buffer_layout(struct fm_port *port,
|
|
+ struct dpa_buffer_layout_s *layout)
|
|
+{
|
|
+ struct fm_port_params params;
|
|
+
|
|
+ layout->priv_data_size = DPA_TX_PRIV_DATA_SIZE;
|
|
+ layout->parse_results = true;
|
|
+ layout->hash_results = true;
|
|
+ layout->time_stamp = false;
|
|
+
|
|
+ fm_port_get_buff_layout_ext_params(port, ¶ms);
|
|
+ layout->manip_extra_space = params.manip_extra_space;
|
|
+ layout->data_align = params.data_align;
|
|
+}
|
|
+
|
|
+static int
|
|
+oh_port_probe(struct platform_device *_of_dev)
|
|
+{
|
|
+ struct device *dpa_oh_dev;
|
|
+ struct device_node *dpa_oh_node;
|
|
+ int lenp, _errno = 0, fq_idx, duple_idx;
|
|
+ int n_size, i, j, ret, duples_count;
|
|
+ struct platform_device *oh_of_dev;
|
|
+ struct device_node *oh_node, *bpool_node = NULL, *root_node;
|
|
+ struct device *oh_dev;
|
|
+ struct dpa_oh_config_s *oh_config = NULL;
|
|
+ const __be32 *oh_all_queues;
|
|
+ const __be32 *channel_ids;
|
|
+ const __be32 *oh_tx_queues;
|
|
+ uint32_t queues_count;
|
|
+ uint32_t crt_fqid_base;
|
|
+ uint32_t crt_fq_count;
|
|
+ bool frag_enabled = false;
|
|
+ struct fm_port_params oh_port_tx_params;
|
|
+ struct fm_port_pcd_param oh_port_pcd_params;
|
|
+ struct dpa_buffer_layout_s buf_layout;
|
|
+
|
|
+ /* True if the current partition owns the OH port. */
|
|
+ bool init_oh_port;
|
|
+
|
|
+ const struct of_device_id *match;
|
|
+ int crt_ext_pools_count;
|
|
+ u32 ext_pool_size;
|
|
+ u32 port_id;
|
|
+ u32 channel_id;
|
|
+
|
|
+ int channel_ids_count;
|
|
+ int channel_idx;
|
|
+ struct fq_duple *fqd;
|
|
+ struct list_head *fq_list, *fq_list_tmp;
|
|
+
|
|
+ const __be32 *bpool_cfg;
|
|
+ uint32_t bpid;
|
|
+
|
|
+ memset(&oh_port_tx_params, 0, sizeof(oh_port_tx_params));
|
|
+ dpa_oh_dev = &_of_dev->dev;
|
|
+ dpa_oh_node = dpa_oh_dev->of_node;
|
|
+ BUG_ON(dpa_oh_node == NULL);
|
|
+
|
|
+ match = of_match_device(oh_port_match_table, dpa_oh_dev);
|
|
+ if (!match)
|
|
+ return -EINVAL;
|
|
+
|
|
+ dev_dbg(dpa_oh_dev, "Probing OH port...\n");
|
|
+
|
|
+ /* Find the referenced OH node */
|
|
+ oh_node = of_parse_phandle(dpa_oh_node, "fsl,fman-oh-port", 0);
|
|
+ if (oh_node == NULL) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "Can't find OH node referenced from node %s\n",
|
|
+ dpa_oh_node->full_name);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ dev_info(dpa_oh_dev, "Found OH node handle compatible with %s\n",
|
|
+ match->compatible);
|
|
+
|
|
+ _errno = of_property_read_u32(oh_node, "cell-index", &port_id);
|
|
+ if (_errno) {
|
|
+ dev_err(dpa_oh_dev, "No port id found in node %s\n",
|
|
+ dpa_oh_node->full_name);
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ _errno = of_property_read_u32(oh_node, "fsl,qman-channel-id",
|
|
+ &channel_id);
|
|
+ if (_errno) {
|
|
+ dev_err(dpa_oh_dev, "No channel id found in node %s\n",
|
|
+ dpa_oh_node->full_name);
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ oh_of_dev = of_find_device_by_node(oh_node);
|
|
+ BUG_ON(oh_of_dev == NULL);
|
|
+ oh_dev = &oh_of_dev->dev;
|
|
+
|
|
+ /* The OH port must be initialized exactly once.
|
|
+ * The following scenarios are of interest:
|
|
+ * - the node is Linux-private (will always initialize it);
|
|
+ * - the node is shared between two Linux partitions
|
|
+ * (only one of them will initialize it);
|
|
+ * - the node is shared between a Linux and a LWE partition
|
|
+ * (Linux will initialize it) - "fsl,dpa-oh-shared"
|
|
+ */
|
|
+
|
|
+ /* Check if the current partition owns the OH port
|
|
+ * and ought to initialize it. It may be the case that we leave this
|
|
+ * to another (also Linux) partition.
|
|
+ */
|
|
+ init_oh_port = strcmp(match->compatible, "fsl,dpa-oh-shared");
|
|
+
|
|
+ /* If we aren't the "owner" of the OH node, we're done here. */
|
|
+ if (!init_oh_port) {
|
|
+ dev_dbg(dpa_oh_dev,
|
|
+ "Not owning the shared OH port %s, will not initialize it.\n",
|
|
+ oh_node->full_name);
|
|
+ of_node_put(oh_node);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ /* Allocate OH dev private data */
|
|
+ oh_config = devm_kzalloc(dpa_oh_dev, sizeof(*oh_config), GFP_KERNEL);
|
|
+ if (oh_config == NULL) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "Can't allocate private data for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ _errno = -ENOMEM;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ INIT_LIST_HEAD(&oh_config->fqs_ingress_list);
|
|
+ INIT_LIST_HEAD(&oh_config->fqs_egress_list);
|
|
+
|
|
+ /* FQs that enter OH port */
|
|
+ lenp = 0;
|
|
+ oh_all_queues = of_get_property(dpa_oh_node,
|
|
+ "fsl,qman-frame-queues-ingress", &lenp);
|
|
+ if (lenp % (2 * sizeof(*oh_all_queues))) {
|
|
+ dev_warn(dpa_oh_dev,
|
|
+ "Wrong ingress queues format for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ /* just ignore the last unpaired value */
|
|
+ }
|
|
+
|
|
+ duples_count = lenp / (2 * sizeof(*oh_all_queues));
|
|
+ dev_err(dpa_oh_dev, "Allocating %d ingress frame queues duples\n",
|
|
+ duples_count);
|
|
+ for (duple_idx = 0; duple_idx < duples_count; duple_idx++) {
|
|
+ crt_fqid_base = be32_to_cpu(oh_all_queues[2 * duple_idx]);
|
|
+ crt_fq_count = be32_to_cpu(oh_all_queues[2 * duple_idx + 1]);
|
|
+
|
|
+ fqd = devm_kzalloc(dpa_oh_dev,
|
|
+ sizeof(struct fq_duple), GFP_KERNEL);
|
|
+ if (!fqd) {
|
|
+ dev_err(dpa_oh_dev, "Can't allocate structures for ingress frame queues for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name,
|
|
+ dpa_oh_node->full_name);
|
|
+ _errno = -ENOMEM;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ fqd->fqs = devm_kzalloc(dpa_oh_dev,
|
|
+ crt_fq_count * sizeof(struct qman_fq),
|
|
+ GFP_KERNEL);
|
|
+ if (!fqd->fqs) {
|
|
+ dev_err(dpa_oh_dev, "Can't allocate structures for ingress frame queues for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name,
|
|
+ dpa_oh_node->full_name);
|
|
+ _errno = -ENOMEM;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ for (j = 0; j < crt_fq_count; j++)
|
|
+ (fqd->fqs + j)->fqid = crt_fqid_base + j;
|
|
+ fqd->fqs_count = crt_fq_count;
|
|
+ fqd->channel_id = (uint16_t)channel_id;
|
|
+ list_add(&fqd->fq_list, &oh_config->fqs_ingress_list);
|
|
+ }
|
|
+
|
|
+ /* create the ingress queues */
|
|
+ list_for_each(fq_list, &oh_config->fqs_ingress_list) {
|
|
+ fqd = list_entry(fq_list, struct fq_duple, fq_list);
|
|
+
|
|
+ for (j = 0; j < fqd->fqs_count; j++) {
|
|
+ ret = oh_fq_create(fqd->fqs + j,
|
|
+ (fqd->fqs + j)->fqid,
|
|
+ fqd->channel_id, 3);
|
|
+ if (ret != 0) {
|
|
+ dev_err(dpa_oh_dev, "Unable to create ingress frame queue %d for OH node %s referenced from node %s!\n",
|
|
+ (fqd->fqs + j)->fqid,
|
|
+ oh_node->full_name,
|
|
+ dpa_oh_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* FQs that exit OH port */
|
|
+ lenp = 0;
|
|
+ oh_all_queues = of_get_property(dpa_oh_node,
|
|
+ "fsl,qman-frame-queues-egress", &lenp);
|
|
+ if (lenp % (2 * sizeof(*oh_all_queues))) {
|
|
+ dev_warn(dpa_oh_dev,
|
|
+ "Wrong egress queues format for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ /* just ignore the last unpaired value */
|
|
+ }
|
|
+
|
|
+ duples_count = lenp / (2 * sizeof(*oh_all_queues));
|
|
+ dev_dbg(dpa_oh_dev, "Allocating %d egress frame queues duples\n",
|
|
+ duples_count);
|
|
+ for (duple_idx = 0; duple_idx < duples_count; duple_idx++) {
|
|
+ crt_fqid_base = be32_to_cpu(oh_all_queues[2 * duple_idx]);
|
|
+ crt_fq_count = be32_to_cpu(oh_all_queues[2 * duple_idx + 1]);
|
|
+
|
|
+ fqd = devm_kzalloc(dpa_oh_dev,
|
|
+ sizeof(struct fq_duple), GFP_KERNEL);
|
|
+ if (!fqd) {
|
|
+ dev_err(dpa_oh_dev, "Can't allocate structures for egress frame queues for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name,
|
|
+ dpa_oh_node->full_name);
|
|
+ _errno = -ENOMEM;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ fqd->fqs = devm_kzalloc(dpa_oh_dev,
|
|
+ crt_fq_count * sizeof(struct qman_fq),
|
|
+ GFP_KERNEL);
|
|
+ if (!fqd->fqs) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "Can't allocate structures for egress frame queues for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name,
|
|
+ dpa_oh_node->full_name);
|
|
+ _errno = -ENOMEM;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ for (j = 0; j < crt_fq_count; j++)
|
|
+ (fqd->fqs + j)->fqid = crt_fqid_base + j;
|
|
+ fqd->fqs_count = crt_fq_count;
|
|
+ /* channel ID is specified in another attribute */
|
|
+ fqd->channel_id = 0;
|
|
+ list_add_tail(&fqd->fq_list, &oh_config->fqs_egress_list);
|
|
+
|
|
+ /* allocate the queue */
|
|
+
|
|
+ }
|
|
+
|
|
+ /* channel_ids for FQs that exit OH port */
|
|
+ lenp = 0;
|
|
+ channel_ids = of_get_property(dpa_oh_node,
|
|
+ "fsl,qman-channel-ids-egress", &lenp);
|
|
+
|
|
+ channel_ids_count = lenp / (sizeof(*channel_ids));
|
|
+ if (channel_ids_count != duples_count) {
|
|
+ dev_warn(dpa_oh_dev,
|
|
+ "Not all egress queues have a channel id for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ /* just ignore the queues that do not have a Channel ID */
|
|
+ }
|
|
+
|
|
+ channel_idx = 0;
|
|
+ list_for_each(fq_list, &oh_config->fqs_egress_list) {
|
|
+ if (channel_idx + 1 > channel_ids_count)
|
|
+ break;
|
|
+ fqd = list_entry(fq_list, struct fq_duple, fq_list);
|
|
+ fqd->channel_id =
|
|
+ (uint16_t)be32_to_cpu(channel_ids[channel_idx++]);
|
|
+ }
|
|
+
|
|
+ /* create egress queues */
|
|
+ list_for_each(fq_list, &oh_config->fqs_egress_list) {
|
|
+ fqd = list_entry(fq_list, struct fq_duple, fq_list);
|
|
+
|
|
+ if (fqd->channel_id == 0) {
|
|
+ /* missing channel id in dts */
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ for (j = 0; j < fqd->fqs_count; j++) {
|
|
+ ret = oh_fq_create(fqd->fqs + j,
|
|
+ (fqd->fqs + j)->fqid,
|
|
+ fqd->channel_id, 3);
|
|
+ if (ret != 0) {
|
|
+ dev_err(dpa_oh_dev, "Unable to create egress frame queue %d for OH node %s referenced from node %s!\n",
|
|
+ (fqd->fqs + j)->fqid,
|
|
+ oh_node->full_name,
|
|
+ dpa_oh_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Read FQ ids/nums for the DPA OH node */
|
|
+ oh_all_queues = of_get_property(dpa_oh_node,
|
|
+ "fsl,qman-frame-queues-oh", &lenp);
|
|
+ if (oh_all_queues == NULL) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "No frame queues have been defined for OH node %s referenced from node %s\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ /* Check that the OH error and default FQs are there */
|
|
+ BUG_ON(lenp % (2 * sizeof(*oh_all_queues)));
|
|
+ queues_count = lenp / (2 * sizeof(*oh_all_queues));
|
|
+ if (queues_count != 2) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "Error and Default queues must be defined for OH node %s referenced from node %s\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ /* Read the FQIDs defined for this OH port */
|
|
+ dev_dbg(dpa_oh_dev, "Reading %d queues...\n", queues_count);
|
|
+ fq_idx = 0;
|
|
+
|
|
+ /* Error FQID - must be present */
|
|
+ crt_fqid_base = be32_to_cpu(oh_all_queues[fq_idx++]);
|
|
+ crt_fq_count = be32_to_cpu(oh_all_queues[fq_idx++]);
|
|
+ if (crt_fq_count != 1) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "Only 1 Error FQ allowed in OH node %s referenced from node %s (read: %d FQIDs).\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name,
|
|
+ crt_fq_count);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+ oh_config->error_fqid = crt_fqid_base;
|
|
+ dev_dbg(dpa_oh_dev, "Read Error FQID 0x%x for OH port %s.\n",
|
|
+ oh_config->error_fqid, oh_node->full_name);
|
|
+
|
|
+ /* Default FQID - must be present */
|
|
+ crt_fqid_base = be32_to_cpu(oh_all_queues[fq_idx++]);
|
|
+ crt_fq_count = be32_to_cpu(oh_all_queues[fq_idx++]);
|
|
+ if (crt_fq_count != 1) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "Only 1 Default FQ allowed in OH node %s referenced from %s (read: %d FQIDs).\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name,
|
|
+ crt_fq_count);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+ oh_config->default_fqid = crt_fqid_base;
|
|
+ dev_dbg(dpa_oh_dev, "Read Default FQID 0x%x for OH port %s.\n",
|
|
+ oh_config->default_fqid, oh_node->full_name);
|
|
+
|
|
+ /* TX FQID - presence is optional */
|
|
+ oh_tx_queues = of_get_property(dpa_oh_node, "fsl,qman-frame-queues-tx",
|
|
+ &lenp);
|
|
+ if (oh_tx_queues == NULL) {
|
|
+ dev_dbg(dpa_oh_dev,
|
|
+ "No tx queues have been defined for OH node %s referenced from node %s\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ goto config_port;
|
|
+ }
|
|
+
|
|
+ /* Check that queues-tx has only a base and a count defined */
|
|
+ BUG_ON(lenp % (2 * sizeof(*oh_tx_queues)));
|
|
+ queues_count = lenp / (2 * sizeof(*oh_tx_queues));
|
|
+ if (queues_count != 1) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "TX queues must be defined in only one <base count> tuple for OH node %s referenced from node %s\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ fq_idx = 0;
|
|
+ crt_fqid_base = be32_to_cpu(oh_tx_queues[fq_idx++]);
|
|
+ crt_fq_count = be32_to_cpu(oh_tx_queues[fq_idx++]);
|
|
+ oh_config->egress_cnt = crt_fq_count;
|
|
+
|
|
+ /* Allocate TX queues */
|
|
+ dev_dbg(dpa_oh_dev, "Allocating %d queues for TX...\n", crt_fq_count);
|
|
+ oh_config->egress_fqs = devm_kzalloc(dpa_oh_dev,
|
|
+ crt_fq_count * sizeof(struct qman_fq), GFP_KERNEL);
|
|
+ if (oh_config->egress_fqs == NULL) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "Can't allocate private data for TX queues for OH node %s referenced from node %s!\n",
|
|
+ oh_node->full_name, dpa_oh_node->full_name);
|
|
+ _errno = -ENOMEM;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ /* Create TX queues */
|
|
+ for (i = 0; i < crt_fq_count; i++) {
|
|
+ ret = oh_fq_create(oh_config->egress_fqs + i,
|
|
+ crt_fqid_base + i, (uint16_t)channel_id, 3);
|
|
+ if (ret != 0) {
|
|
+ dev_err(dpa_oh_dev,
|
|
+ "Unable to create TX frame queue %d for OH node %s referenced from node %s!\n",
|
|
+ crt_fqid_base + i, oh_node->full_name,
|
|
+ dpa_oh_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+ }
|
|
+
|
|
+config_port:
|
|
+ /* Get a handle to the fm_port so we can set
|
|
+ * its configuration params
|
|
+ */
|
|
+ oh_config->oh_port = fm_port_bind(oh_dev);
|
|
+ if (oh_config->oh_port == NULL) {
|
|
+ dev_err(dpa_oh_dev, "NULL drvdata from fm port dev %s!\n",
|
|
+ oh_node->full_name);
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ oh_set_buffer_layout(oh_config->oh_port, &buf_layout);
|
|
+
|
|
+ /* read the pool handlers */
|
|
+ crt_ext_pools_count = of_count_phandle_with_args(dpa_oh_node,
|
|
+ "fsl,bman-buffer-pools", NULL);
|
|
+ if (crt_ext_pools_count <= 0) {
|
|
+ dev_info(dpa_oh_dev,
|
|
+ "OH port %s has no buffer pool. Fragmentation will not be enabled\n",
|
|
+ oh_node->full_name);
|
|
+ goto init_port;
|
|
+ }
|
|
+
|
|
+ /* used for reading ext_pool_size*/
|
|
+ root_node = of_find_node_by_path("/");
|
|
+ if (root_node == NULL) {
|
|
+ dev_err(dpa_oh_dev, "of_find_node_by_path(/) failed\n");
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ n_size = of_n_size_cells(root_node);
|
|
+ of_node_put(root_node);
|
|
+
|
|
+ dev_dbg(dpa_oh_dev, "OH port number of pools = %d\n",
|
|
+ crt_ext_pools_count);
|
|
+
|
|
+ oh_port_tx_params.num_pools = (uint8_t)crt_ext_pools_count;
|
|
+
|
|
+ for (i = 0; i < crt_ext_pools_count; i++) {
|
|
+ bpool_node = of_parse_phandle(dpa_oh_node,
|
|
+ "fsl,bman-buffer-pools", i);
|
|
+ if (bpool_node == NULL) {
|
|
+ dev_err(dpa_oh_dev, "Invalid Buffer pool node\n");
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ _errno = of_property_read_u32(bpool_node, "fsl,bpid", &bpid);
|
|
+ if (_errno) {
|
|
+ dev_err(dpa_oh_dev, "Invalid Buffer Pool ID\n");
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ oh_port_tx_params.pool_param[i].id = (uint8_t)bpid;
|
|
+ dev_dbg(dpa_oh_dev, "OH port bpool id = %u\n", bpid);
|
|
+
|
|
+ bpool_cfg = of_get_property(bpool_node,
|
|
+ "fsl,bpool-ethernet-cfg", &lenp);
|
|
+ if (bpool_cfg == NULL) {
|
|
+ dev_err(dpa_oh_dev, "Invalid Buffer pool config params\n");
|
|
+ _errno = -EINVAL;
|
|
+ goto return_kfree;
|
|
+ }
|
|
+
|
|
+ ext_pool_size = of_read_number(bpool_cfg + n_size, n_size);
|
|
+ oh_port_tx_params.pool_param[i].size = (uint16_t)ext_pool_size;
|
|
+ dev_dbg(dpa_oh_dev, "OH port bpool size = %u\n",
|
|
+ ext_pool_size);
|
|
+ of_node_put(bpool_node);
|
|
+
|
|
+ }
|
|
+
|
|
+ if (buf_layout.data_align != FRAG_DATA_ALIGN ||
|
|
+ buf_layout.manip_extra_space != FRAG_MANIP_SPACE)
|
|
+ goto init_port;
|
|
+
|
|
+ frag_enabled = true;
|
|
+ dev_info(dpa_oh_dev, "IP Fragmentation enabled for OH port %d",
|
|
+ port_id);
|
|
+
|
|
+init_port:
|
|
+ of_node_put(oh_node);
|
|
+ /* Set Tx params */
|
|
+ dpaa_eth_init_port(tx, oh_config->oh_port, oh_port_tx_params,
|
|
+ oh_config->error_fqid, oh_config->default_fqid, (&buf_layout),
|
|
+ frag_enabled);
|
|
+ /* Set PCD params */
|
|
+ oh_port_pcd_params.cba = oh_alloc_pcd_fqids;
|
|
+ oh_port_pcd_params.cbf = oh_free_pcd_fqids;
|
|
+ oh_port_pcd_params.dev = dpa_oh_dev;
|
|
+ fm_port_pcd_bind(oh_config->oh_port, &oh_port_pcd_params);
|
|
+
|
|
+ dev_set_drvdata(dpa_oh_dev, oh_config);
|
|
+
|
|
+ /* Enable the OH port */
|
|
+ _errno = fm_port_enable(oh_config->oh_port);
|
|
+ if (_errno)
|
|
+ goto return_kfree;
|
|
+
|
|
+ dev_info(dpa_oh_dev, "OH port %s enabled.\n", oh_node->full_name);
|
|
+
|
|
+ /* print of all referenced & created queues */
|
|
+ dump_oh_config(dpa_oh_dev, oh_config);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+return_kfree:
|
|
+ if (bpool_node)
|
|
+ of_node_put(bpool_node);
|
|
+ if (oh_node)
|
|
+ of_node_put(oh_node);
|
|
+ if (oh_config && oh_config->egress_fqs)
|
|
+ devm_kfree(dpa_oh_dev, oh_config->egress_fqs);
|
|
+
|
|
+ list_for_each_safe(fq_list, fq_list_tmp, &oh_config->fqs_ingress_list) {
|
|
+ fqd = list_entry(fq_list, struct fq_duple, fq_list);
|
|
+ list_del(fq_list);
|
|
+ devm_kfree(dpa_oh_dev, fqd->fqs);
|
|
+ devm_kfree(dpa_oh_dev, fqd);
|
|
+ }
|
|
+
|
|
+ list_for_each_safe(fq_list, fq_list_tmp, &oh_config->fqs_egress_list) {
|
|
+ fqd = list_entry(fq_list, struct fq_duple, fq_list);
|
|
+ list_del(fq_list);
|
|
+ devm_kfree(dpa_oh_dev, fqd->fqs);
|
|
+ devm_kfree(dpa_oh_dev, fqd);
|
|
+ }
|
|
+
|
|
+ devm_kfree(dpa_oh_dev, oh_config);
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static int __cold oh_port_remove(struct platform_device *_of_dev)
|
|
+{
|
|
+ int _errno = 0, i;
|
|
+ struct dpa_oh_config_s *oh_config;
|
|
+
|
|
+ pr_info("Removing OH port...\n");
|
|
+
|
|
+ oh_config = dev_get_drvdata(&_of_dev->dev);
|
|
+ if (oh_config == NULL) {
|
|
+ pr_err(KBUILD_MODNAME
|
|
+ ": %s:%hu:%s(): No OH config in device private data!\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__);
|
|
+ _errno = -ENODEV;
|
|
+ goto return_error;
|
|
+ }
|
|
+
|
|
+ if (oh_config->egress_fqs)
|
|
+ for (i = 0; i < oh_config->egress_cnt; i++)
|
|
+ oh_fq_destroy(oh_config->egress_fqs + i);
|
|
+
|
|
+ if (oh_config->oh_port == NULL) {
|
|
+ pr_err(KBUILD_MODNAME
|
|
+ ": %s:%hu:%s(): No fm port in device private data!\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__);
|
|
+ _errno = -EINVAL;
|
|
+ goto free_egress_fqs;
|
|
+ }
|
|
+
|
|
+ _errno = fm_port_disable(oh_config->oh_port);
|
|
+
|
|
+free_egress_fqs:
|
|
+ if (oh_config->egress_fqs)
|
|
+ devm_kfree(&_of_dev->dev, oh_config->egress_fqs);
|
|
+ devm_kfree(&_of_dev->dev, oh_config);
|
|
+ dev_set_drvdata(&_of_dev->dev, NULL);
|
|
+
|
|
+return_error:
|
|
+ return _errno;
|
|
+}
|
|
+
|
|
+static struct platform_driver oh_port_driver = {
|
|
+ .driver = {
|
|
+ .name = KBUILD_MODNAME,
|
|
+ .of_match_table = oh_port_match_table,
|
|
+ .owner = THIS_MODULE,
|
|
+ .pm = OH_PM_OPS,
|
|
+ },
|
|
+ .probe = oh_port_probe,
|
|
+ .remove = oh_port_remove
|
|
+};
|
|
+
|
|
+static int __init __cold oh_port_load(void)
|
|
+{
|
|
+ int _errno;
|
|
+
|
|
+ pr_info(OH_MOD_DESCRIPTION "\n");
|
|
+
|
|
+ _errno = platform_driver_register(&oh_port_driver);
|
|
+ if (_errno < 0) {
|
|
+ pr_err(KBUILD_MODNAME
|
|
+ ": %s:%hu:%s(): platform_driver_register() = %d\n",
|
|
+ KBUILD_BASENAME".c", __LINE__, __func__, _errno);
|
|
+ }
|
|
+
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+ return _errno;
|
|
+}
|
|
+module_init(oh_port_load);
|
|
+
|
|
+static void __exit __cold oh_port_unload(void)
|
|
+{
|
|
+ pr_debug(KBUILD_MODNAME ": -> %s:%s()\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+
|
|
+ platform_driver_unregister(&oh_port_driver);
|
|
+
|
|
+ pr_debug(KBUILD_MODNAME ": %s:%s() ->\n",
|
|
+ KBUILD_BASENAME".c", __func__);
|
|
+}
|
|
+module_exit(oh_port_unload);
|
|
--- /dev/null
|
|
+++ b/drivers/net/ethernet/freescale/sdk_dpaa/offline_port.h
|
|
@@ -0,0 +1,59 @@
|
|
+/* Copyright 2011 Freescale Semiconductor Inc.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of Freescale Semiconductor nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ *
|
|
+ * ALTERNATIVELY, this software may be distributed under the terms of the
|
|
+ * GNU General Public License ("GPL") as published by the Free Software
|
|
+ * Foundation, either version 2 of that License or (at your option) any
|
|
+ * later version.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ */
|
|
+
|
|
+#ifndef __OFFLINE_PORT_H
|
|
+#define __OFFLINE_PORT_H
|
|
+
|
|
+struct fm_port;
|
|
+struct qman_fq;
|
|
+
|
|
+/* fqs are defined in duples (base_fq, fq_count) */
|
|
+struct fq_duple {
|
|
+ struct qman_fq *fqs;
|
|
+ int fqs_count;
|
|
+ uint16_t channel_id;
|
|
+ struct list_head fq_list;
|
|
+};
|
|
+
|
|
+/* OH port configuration */
|
|
+struct dpa_oh_config_s {
|
|
+ uint32_t error_fqid;
|
|
+ uint32_t default_fqid;
|
|
+ struct fm_port *oh_port;
|
|
+ uint32_t egress_cnt;
|
|
+ struct qman_fq *egress_fqs;
|
|
+ uint16_t channel;
|
|
+
|
|
+ struct list_head fqs_ingress_list;
|
|
+ struct list_head fqs_egress_list;
|
|
+};
|
|
+
|
|
+#endif /* __OFFLINE_PORT_H */
|