Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,80 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Qualcomm network device configuration
#
config NET_VENDOR_QUALCOMM
bool "Qualcomm devices"
default y
help
If you have a network (Ethernet) card belonging to this class, say Y.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all
the questions about Qualcomm cards. If you say Y, you will be asked
for your specific card in the following questions.
if NET_VENDOR_QUALCOMM
config QCA7000
tristate
help
This enables support for the Qualcomm Atheros QCA7000.
config QCA7000_SPI
tristate "Qualcomm Atheros QCA7000 SPI support"
select QCA7000
depends on SPI_MASTER && OF
help
This SPI protocol driver supports the Qualcomm Atheros QCA7000.
To compile this driver as a module, choose M here. The module
will be called qcaspi.
config QCA7000_UART
tristate "Qualcomm Atheros QCA7000 UART support"
select QCA7000
depends on SERIAL_DEV_BUS && OF
help
This UART protocol driver supports the Qualcomm Atheros QCA7000.
Currently the driver assumes these device UART settings:
Data bits: 8
Parity: None
Stop bits: 1
Flow control: None
To compile this driver as a module, choose M here. The module
will be called qcauart.
config QCOM_EMAC
tristate "Qualcomm EMAC Gigabit Ethernet support"
depends on HAS_DMA && HAS_IOMEM
select CRC32
select PHYLIB
help
This driver supports the Qualcomm Technologies, Inc. Gigabit
Ethernet Media Access Controller (EMAC). The controller
supports IEEE 802.3-2002, half-duplex mode at 10/100 Mb/s,
full-duplex mode at 10/100/1000Mb/s, Wake On LAN (WOL) for
low power, Receive-Side Scaling (RSS), and IEEE 1588-2008
Precision Clock Synchronization Protocol.
config QCOM_PPE
tristate "Qualcomm PPE Ethernet support"
depends on COMMON_CLK && HAS_IOMEM && OF
depends on ARCH_QCOM || COMPILE_TEST
select REGMAP_MMIO
help
This driver supports the Qualcomm Technologies, Inc. packet
process engine (PPE) available with IPQ SoC. The PPE includes
the Ethernet MACs, Ethernet DMA (EDMA) and switch core that
supports L3 flow offload, L2 switch function, RSS and tunnel
offload.
To compile this driver as a module, choose M here. The module
will be called qcom-ppe.
source "drivers/net/ethernet/qualcomm/rmnet/Kconfig"
endif # NET_VENDOR_QUALCOMM
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Qualcomm network device drivers.
#
obj-$(CONFIG_QCA7000) += qca_7k_common.o
obj-$(CONFIG_QCA7000_SPI) += qcaspi.o
qcaspi-objs := qca_7k.o qca_debug.o qca_spi.o
obj-$(CONFIG_QCA7000_UART) += qcauart.o
qcauart-objs := qca_uart.o
obj-y += emac/
obj-$(CONFIG_QCOM_PPE) += ppe/
obj-$(CONFIG_RMNET) += rmnet/
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the Qualcomm Technologies, Inc. EMAC Gigabit Ethernet driver
#
obj-$(CONFIG_QCOM_EMAC) += qcom-emac.o
qcom-emac-objs := emac.o emac-mac.o emac-phy.o emac-sgmii.o emac-ethtool.o \
emac-sgmii-fsm9900.o emac-sgmii-qdf2432.o \
emac-sgmii-qdf2400.o
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
*/
#include <linux/ethtool.h>
#include <linux/phy.h>
#include "emac.h"
static const char * const emac_ethtool_stat_strings[] = {
"rx_ok",
"rx_bcast",
"rx_mcast",
"rx_pause",
"rx_ctrl",
"rx_fcs_err",
"rx_len_err",
"rx_byte_cnt",
"rx_runt",
"rx_frag",
"rx_sz_64",
"rx_sz_65_127",
"rx_sz_128_255",
"rx_sz_256_511",
"rx_sz_512_1023",
"rx_sz_1024_1518",
"rx_sz_1519_max",
"rx_sz_ov",
"rx_rxf_ov",
"rx_align_err",
"rx_bcast_byte_cnt",
"rx_mcast_byte_cnt",
"rx_err_addr",
"rx_crc_align",
"rx_jabbers",
"tx_ok",
"tx_bcast",
"tx_mcast",
"tx_pause",
"tx_exc_defer",
"tx_ctrl",
"tx_defer",
"tx_byte_cnt",
"tx_sz_64",
"tx_sz_65_127",
"tx_sz_128_255",
"tx_sz_256_511",
"tx_sz_512_1023",
"tx_sz_1024_1518",
"tx_sz_1519_max",
"tx_1_col",
"tx_2_col",
"tx_late_col",
"tx_abort_col",
"tx_underrun",
"tx_rd_eop",
"tx_len_err",
"tx_trunc",
"tx_bcast_byte",
"tx_mcast_byte",
"tx_col",
};
#define EMAC_STATS_LEN ARRAY_SIZE(emac_ethtool_stat_strings)
static u32 emac_get_msglevel(struct net_device *netdev)
{
struct emac_adapter *adpt = netdev_priv(netdev);
return adpt->msg_enable;
}
static void emac_set_msglevel(struct net_device *netdev, u32 data)
{
struct emac_adapter *adpt = netdev_priv(netdev);
adpt->msg_enable = data;
}
static int emac_get_sset_count(struct net_device *netdev, int sset)
{
switch (sset) {
case ETH_SS_PRIV_FLAGS:
return 1;
case ETH_SS_STATS:
return EMAC_STATS_LEN;
default:
return -EOPNOTSUPP;
}
}
static void emac_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
{
unsigned int i;
switch (stringset) {
case ETH_SS_PRIV_FLAGS:
strcpy(data, "single-pause-mode");
break;
case ETH_SS_STATS:
for (i = 0; i < EMAC_STATS_LEN; i++) {
strscpy(data, emac_ethtool_stat_strings[i],
ETH_GSTRING_LEN);
data += ETH_GSTRING_LEN;
}
break;
}
}
static void emac_get_ethtool_stats(struct net_device *netdev,
struct ethtool_stats *stats,
u64 *data)
{
struct emac_adapter *adpt = netdev_priv(netdev);
spin_lock(&adpt->stats.lock);
emac_update_hw_stats(adpt);
memcpy(data, &adpt->stats, EMAC_STATS_LEN * sizeof(u64));
spin_unlock(&adpt->stats.lock);
}
static int emac_nway_reset(struct net_device *netdev)
{
struct phy_device *phydev = netdev->phydev;
if (!phydev)
return -ENODEV;
return genphy_restart_aneg(phydev);
}
static void emac_get_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct emac_adapter *adpt = netdev_priv(netdev);
ring->rx_max_pending = EMAC_MAX_RX_DESCS;
ring->tx_max_pending = EMAC_MAX_TX_DESCS;
ring->rx_pending = adpt->rx_desc_cnt;
ring->tx_pending = adpt->tx_desc_cnt;
}
static int emac_set_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct emac_adapter *adpt = netdev_priv(netdev);
/* We don't have separate queues/rings for small/large frames, so
* reject any attempt to specify those values separately.
*/
if (ring->rx_mini_pending || ring->rx_jumbo_pending)
return -EINVAL;
adpt->tx_desc_cnt =
clamp_val(ring->tx_pending, EMAC_MIN_TX_DESCS, EMAC_MAX_TX_DESCS);
adpt->rx_desc_cnt =
clamp_val(ring->rx_pending, EMAC_MIN_RX_DESCS, EMAC_MAX_RX_DESCS);
if (netif_running(netdev))
return emac_reinit_locked(adpt);
return 0;
}
static void emac_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct emac_adapter *adpt = netdev_priv(netdev);
pause->autoneg = adpt->automatic ? AUTONEG_ENABLE : AUTONEG_DISABLE;
pause->rx_pause = adpt->rx_flow_control ? 1 : 0;
pause->tx_pause = adpt->tx_flow_control ? 1 : 0;
}
static int emac_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct emac_adapter *adpt = netdev_priv(netdev);
adpt->automatic = pause->autoneg == AUTONEG_ENABLE;
adpt->rx_flow_control = pause->rx_pause != 0;
adpt->tx_flow_control = pause->tx_pause != 0;
if (netif_running(netdev))
return emac_reinit_locked(adpt);
return 0;
}
/* Selected registers that might want to track during runtime. */
static const u16 emac_regs[] = {
EMAC_DMA_MAS_CTRL,
EMAC_MAC_CTRL,
EMAC_TXQ_CTRL_0,
EMAC_RXQ_CTRL_0,
EMAC_DMA_CTRL,
EMAC_INT_MASK,
EMAC_AXI_MAST_CTRL,
EMAC_CORE_HW_VERSION,
EMAC_MISC_CTRL,
};
/* Every time emac_regs[] above is changed, increase this version number. */
#define EMAC_REGS_VERSION 0
#define EMAC_MAX_REG_SIZE ARRAY_SIZE(emac_regs)
static void emac_get_regs(struct net_device *netdev,
struct ethtool_regs *regs, void *buff)
{
struct emac_adapter *adpt = netdev_priv(netdev);
u32 *val = buff;
unsigned int i;
regs->version = EMAC_REGS_VERSION;
regs->len = EMAC_MAX_REG_SIZE * sizeof(u32);
for (i = 0; i < EMAC_MAX_REG_SIZE; i++)
val[i] = readl(adpt->base + emac_regs[i]);
}
static int emac_get_regs_len(struct net_device *netdev)
{
return EMAC_MAX_REG_SIZE * sizeof(u32);
}
#define EMAC_PRIV_ENABLE_SINGLE_PAUSE BIT(0)
static int emac_set_priv_flags(struct net_device *netdev, u32 flags)
{
struct emac_adapter *adpt = netdev_priv(netdev);
adpt->single_pause_mode = !!(flags & EMAC_PRIV_ENABLE_SINGLE_PAUSE);
if (netif_running(netdev))
return emac_reinit_locked(adpt);
return 0;
}
static u32 emac_get_priv_flags(struct net_device *netdev)
{
struct emac_adapter *adpt = netdev_priv(netdev);
return adpt->single_pause_mode ? EMAC_PRIV_ENABLE_SINGLE_PAUSE : 0;
}
static const struct ethtool_ops emac_ethtool_ops = {
.get_link_ksettings = phy_ethtool_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings,
.get_msglevel = emac_get_msglevel,
.set_msglevel = emac_set_msglevel,
.get_sset_count = emac_get_sset_count,
.get_strings = emac_get_strings,
.get_ethtool_stats = emac_get_ethtool_stats,
.get_ringparam = emac_get_ringparam,
.set_ringparam = emac_set_ringparam,
.get_pauseparam = emac_get_pauseparam,
.set_pauseparam = emac_set_pauseparam,
.nway_reset = emac_nway_reset,
.get_link = ethtool_op_get_link,
.get_regs_len = emac_get_regs_len,
.get_regs = emac_get_regs,
.set_priv_flags = emac_set_priv_flags,
.get_priv_flags = emac_get_priv_flags,
};
void emac_set_ethtool_ops(struct net_device *netdev)
{
netdev->ethtool_ops = &emac_ethtool_ops;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,241 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
*/
/* EMAC DMA HW engine uses three rings:
* Tx:
* TPD: Transmit Packet Descriptor ring.
* Rx:
* RFD: Receive Free Descriptor ring.
* Ring of descriptors with empty buffers to be filled by Rx HW.
* RRD: Receive Return Descriptor ring.
* Ring of descriptors with buffers filled with received data.
*/
#ifndef _EMAC_HW_H_
#define _EMAC_HW_H_
/* EMAC_CSR register offsets */
#define EMAC_EMAC_WRAPPER_CSR1 0x000000
#define EMAC_EMAC_WRAPPER_CSR2 0x000004
#define EMAC_EMAC_WRAPPER_TX_TS_LO 0x000104
#define EMAC_EMAC_WRAPPER_TX_TS_HI 0x000108
#define EMAC_EMAC_WRAPPER_TX_TS_INX 0x00010c
/* DMA Order Settings */
enum emac_dma_order {
emac_dma_ord_in = 1,
emac_dma_ord_enh = 2,
emac_dma_ord_out = 4
};
enum emac_dma_req_block {
emac_dma_req_128 = 0,
emac_dma_req_256 = 1,
emac_dma_req_512 = 2,
emac_dma_req_1024 = 3,
emac_dma_req_2048 = 4,
emac_dma_req_4096 = 5
};
/* Returns the value of bits idx...idx+n_bits */
#define BITS_GET(val, lo, hi) ((le32_to_cpu(val) & GENMASK((hi), (lo))) >> lo)
#define BITS_SET(val, lo, hi, new_val) \
val = cpu_to_le32((le32_to_cpu(val) & (~GENMASK((hi), (lo)))) | \
(((new_val) << (lo)) & GENMASK((hi), (lo))))
/* RRD (Receive Return Descriptor) */
struct emac_rrd {
u32 word[6];
/* number of RFD */
#define RRD_NOR(rrd) BITS_GET((rrd)->word[0], 16, 19)
/* start consumer index of rfd-ring */
#define RRD_SI(rrd) BITS_GET((rrd)->word[0], 20, 31)
/* vlan-tag (CVID, CFI and PRI) */
#define RRD_CVALN_TAG(rrd) BITS_GET((rrd)->word[2], 0, 15)
/* length of the packet */
#define RRD_PKT_SIZE(rrd) BITS_GET((rrd)->word[3], 0, 13)
/* L4(TCP/UDP) checksum failed */
#define RRD_L4F(rrd) BITS_GET((rrd)->word[3], 14, 14)
/* vlan tagged */
#define RRD_CVTAG(rrd) BITS_GET((rrd)->word[3], 16, 16)
/* When set, indicates that the descriptor is updated by the IP core.
* When cleared, indicates that the descriptor is invalid.
*/
#define RRD_UPDT(rrd) BITS_GET((rrd)->word[3], 31, 31)
#define RRD_UPDT_SET(rrd, val) BITS_SET((rrd)->word[3], 31, 31, val)
/* timestamp low */
#define RRD_TS_LOW(rrd) BITS_GET((rrd)->word[4], 0, 29)
/* timestamp high */
#define RRD_TS_HI(rrd) le32_to_cpu((rrd)->word[5])
};
/* TPD (Transmit Packet Descriptor) */
struct emac_tpd {
u32 word[4];
/* Number of bytes of the transmit packet. (include 4-byte CRC) */
#define TPD_BUF_LEN_SET(tpd, val) BITS_SET((tpd)->word[0], 0, 15, val)
/* Custom Checksum Offload: When set, ask IP core to offload custom checksum */
#define TPD_CSX_SET(tpd, val) BITS_SET((tpd)->word[1], 8, 8, val)
/* TCP Large Send Offload: When set, ask IP core to do offload TCP Large Send */
#define TPD_LSO(tpd) BITS_GET((tpd)->word[1], 12, 12)
#define TPD_LSO_SET(tpd, val) BITS_SET((tpd)->word[1], 12, 12, val)
/* Large Send Offload Version: When set, indicates this is an LSOv2
* (for both IPv4 and IPv6). When cleared, indicates this is an LSOv1
* (only for IPv4).
*/
#define TPD_LSOV_SET(tpd, val) BITS_SET((tpd)->word[1], 13, 13, val)
/* IPv4 packet: When set, indicates this is an IPv4 packet, this bit is only
* for LSOV2 format.
*/
#define TPD_IPV4_SET(tpd, val) BITS_SET((tpd)->word[1], 16, 16, val)
/* 0: Ethernet frame (DA+SA+TYPE+DATA+CRC)
* 1: IEEE 802.3 frame (DA+SA+LEN+DSAP+SSAP+CTL+ORG+TYPE+DATA+CRC)
*/
#define TPD_TYP_SET(tpd, val) BITS_SET((tpd)->word[1], 17, 17, val)
/* Low-32bit Buffer Address */
#define TPD_BUFFER_ADDR_L_SET(tpd, val) ((tpd)->word[2] = cpu_to_le32(val))
/* CVLAN Tag to be inserted if INS_VLAN_TAG is set, CVLAN TPID based on global
* register configuration.
*/
#define TPD_CVLAN_TAG_SET(tpd, val) BITS_SET((tpd)->word[3], 0, 15, val)
/* Insert CVlan Tag: When set, ask MAC to insert CVLAN TAG to outgoing packet
*/
#define TPD_INSTC_SET(tpd, val) BITS_SET((tpd)->word[3], 17, 17, val)
/* High-14bit Buffer Address, So, the 64b-bit address is
* {DESC_CTRL_11_TX_DATA_HIADDR[17:0],(register) BUFFER_ADDR_H, BUFFER_ADDR_L}
* Extend TPD_BUFFER_ADDR_H to [31, 18], because we never enable timestamping.
*/
#define TPD_BUFFER_ADDR_H_SET(tpd, val) BITS_SET((tpd)->word[3], 18, 31, val)
/* Format D. Word offset from the 1st byte of this packet to start to calculate
* the custom checksum.
*/
#define TPD_PAYLOAD_OFFSET_SET(tpd, val) BITS_SET((tpd)->word[1], 0, 7, val)
/* Format D. Word offset from the 1st byte of this packet to fill the custom
* checksum to
*/
#define TPD_CXSUM_OFFSET_SET(tpd, val) BITS_SET((tpd)->word[1], 18, 25, val)
/* Format C. TCP Header offset from the 1st byte of this packet. (byte unit) */
#define TPD_TCPHDR_OFFSET_SET(tpd, val) BITS_SET((tpd)->word[1], 0, 7, val)
/* Format C. MSS (Maximum Segment Size) got from the protocol layer. (byte unit)
*/
#define TPD_MSS_SET(tpd, val) BITS_SET((tpd)->word[1], 18, 30, val)
/* packet length in ext tpd */
#define TPD_PKT_LEN_SET(tpd, val) ((tpd)->word[2] = cpu_to_le32(val))
};
/* emac_ring_header represents a single, contiguous block of DMA space
* mapped for the three descriptor rings (tpd, rfd, rrd)
*/
struct emac_ring_header {
void *v_addr; /* virtual address */
dma_addr_t dma_addr; /* dma address */
size_t size; /* length in bytes */
size_t used;
};
/* emac_buffer is wrapper around a pointer to a socket buffer
* so a DMA handle can be stored along with the skb
*/
struct emac_buffer {
struct sk_buff *skb; /* socket buffer */
u16 length; /* rx buffer length */
dma_addr_t dma_addr; /* dma address */
};
/* receive free descriptor (rfd) ring */
struct emac_rfd_ring {
struct emac_buffer *rfbuff;
u32 *v_addr; /* virtual address */
dma_addr_t dma_addr; /* dma address */
size_t size; /* length in bytes */
unsigned int count; /* number of desc in the ring */
unsigned int produce_idx;
unsigned int process_idx;
unsigned int consume_idx; /* unused */
};
/* Receive Return Desciptor (RRD) ring */
struct emac_rrd_ring {
u32 *v_addr; /* virtual address */
dma_addr_t dma_addr; /* physical address */
size_t size; /* length in bytes */
unsigned int count; /* number of desc in the ring */
unsigned int produce_idx; /* unused */
unsigned int consume_idx;
};
/* Rx queue */
struct emac_rx_queue {
struct net_device *netdev; /* netdev ring belongs to */
struct emac_rrd_ring rrd;
struct emac_rfd_ring rfd;
struct napi_struct napi;
struct emac_irq *irq;
u32 intr;
u32 produce_mask;
u32 process_mask;
u32 consume_mask;
u16 produce_reg;
u16 process_reg;
u16 consume_reg;
u8 produce_shift;
u8 process_shft;
u8 consume_shift;
};
/* Transimit Packet Descriptor (tpd) ring */
struct emac_tpd_ring {
struct emac_buffer *tpbuff;
u32 *v_addr; /* virtual address */
dma_addr_t dma_addr; /* dma address */
size_t size; /* length in bytes */
unsigned int count; /* number of desc in the ring */
unsigned int produce_idx;
unsigned int consume_idx;
unsigned int last_produce_idx;
};
/* Tx queue */
struct emac_tx_queue {
struct emac_tpd_ring tpd;
u32 produce_mask;
u32 consume_mask;
u16 max_packets; /* max packets per interrupt */
u16 produce_reg;
u16 consume_reg;
u8 produce_shift;
u8 consume_shift;
};
struct emac_adapter;
int emac_mac_up(struct emac_adapter *adpt);
void emac_mac_down(struct emac_adapter *adpt);
void emac_mac_reset(struct emac_adapter *adpt);
void emac_mac_stop(struct emac_adapter *adpt);
void emac_mac_mode_config(struct emac_adapter *adpt);
void emac_mac_rx_process(struct emac_adapter *adpt, struct emac_rx_queue *rx_q,
int *num_pkts, int max_pkts);
netdev_tx_t emac_mac_tx_buf_send(struct emac_adapter *adpt,
struct emac_tx_queue *tx_q,
struct sk_buff *skb);
void emac_mac_tx_process(struct emac_adapter *adpt, struct emac_tx_queue *tx_q);
void emac_mac_rx_tx_ring_init_all(struct platform_device *pdev,
struct emac_adapter *adpt);
int emac_mac_rx_tx_rings_alloc_all(struct emac_adapter *adpt);
void emac_mac_rx_tx_rings_free_all(struct emac_adapter *adpt);
void emac_mac_multicast_addr_clear(struct emac_adapter *adpt);
void emac_mac_multicast_addr_set(struct emac_adapter *adpt, u8 *addr);
#endif /*_EMAC_HW_H_*/
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
*/
/* Qualcomm Technologies, Inc. EMAC PHY Controller driver.
*/
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/iopoll.h>
#include <linux/acpi.h>
#include "emac.h"
/* EMAC base register offsets */
#define EMAC_MDIO_CTRL 0x001414
#define EMAC_PHY_STS 0x001418
#define EMAC_MDIO_EX_CTRL 0x001440
/* EMAC_MDIO_CTRL */
#define MDIO_MODE BIT(30)
#define MDIO_PR BIT(29)
#define MDIO_AP_EN BIT(28)
#define MDIO_BUSY BIT(27)
#define MDIO_CLK_SEL_BMSK 0x7000000
#define MDIO_CLK_SEL_SHFT 24
#define MDIO_START BIT(23)
#define SUP_PREAMBLE BIT(22)
#define MDIO_RD_NWR BIT(21)
#define MDIO_REG_ADDR_BMSK 0x1f0000
#define MDIO_REG_ADDR_SHFT 16
#define MDIO_DATA_BMSK 0xffff
#define MDIO_DATA_SHFT 0
/* EMAC_PHY_STS */
#define PHY_ADDR_BMSK 0x1f0000
#define PHY_ADDR_SHFT 16
#define MDIO_CLK_25_4 0
#define MDIO_CLK_25_28 7
#define MDIO_WAIT_TIMES 1000
#define MDIO_STATUS_DELAY_TIME 1
static int emac_mdio_read(struct mii_bus *bus, int addr, int regnum)
{
struct emac_adapter *adpt = bus->priv;
u32 reg;
emac_reg_update32(adpt->base + EMAC_PHY_STS, PHY_ADDR_BMSK,
(addr << PHY_ADDR_SHFT));
reg = SUP_PREAMBLE |
((MDIO_CLK_25_4 << MDIO_CLK_SEL_SHFT) & MDIO_CLK_SEL_BMSK) |
((regnum << MDIO_REG_ADDR_SHFT) & MDIO_REG_ADDR_BMSK) |
MDIO_START | MDIO_RD_NWR;
writel(reg, adpt->base + EMAC_MDIO_CTRL);
if (readl_poll_timeout(adpt->base + EMAC_MDIO_CTRL, reg,
!(reg & (MDIO_START | MDIO_BUSY)),
MDIO_STATUS_DELAY_TIME, MDIO_WAIT_TIMES * 100))
return -EIO;
return (reg >> MDIO_DATA_SHFT) & MDIO_DATA_BMSK;
}
static int emac_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val)
{
struct emac_adapter *adpt = bus->priv;
u32 reg;
emac_reg_update32(adpt->base + EMAC_PHY_STS, PHY_ADDR_BMSK,
(addr << PHY_ADDR_SHFT));
reg = SUP_PREAMBLE |
((MDIO_CLK_25_4 << MDIO_CLK_SEL_SHFT) & MDIO_CLK_SEL_BMSK) |
((regnum << MDIO_REG_ADDR_SHFT) & MDIO_REG_ADDR_BMSK) |
((val << MDIO_DATA_SHFT) & MDIO_DATA_BMSK) |
MDIO_START;
writel(reg, adpt->base + EMAC_MDIO_CTRL);
if (readl_poll_timeout(adpt->base + EMAC_MDIO_CTRL, reg,
!(reg & (MDIO_START | MDIO_BUSY)),
MDIO_STATUS_DELAY_TIME, MDIO_WAIT_TIMES * 100))
return -EIO;
return 0;
}
/* Configure the MDIO bus and connect the external PHY */
int emac_phy_config(struct platform_device *pdev, struct emac_adapter *adpt)
{
struct device_node *np = pdev->dev.of_node;
struct mii_bus *mii_bus;
int ret;
/* Create the mii_bus object for talking to the MDIO bus */
adpt->mii_bus = mii_bus = devm_mdiobus_alloc(&pdev->dev);
if (!mii_bus)
return -ENOMEM;
mii_bus->name = "emac-mdio";
snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
mii_bus->read = emac_mdio_read;
mii_bus->write = emac_mdio_write;
mii_bus->parent = &pdev->dev;
mii_bus->priv = adpt;
if (has_acpi_companion(&pdev->dev)) {
u32 phy_addr;
ret = mdiobus_register(mii_bus);
if (ret) {
dev_err(&pdev->dev, "could not register mdio bus\n");
return ret;
}
ret = device_property_read_u32(&pdev->dev, "phy-channel",
&phy_addr);
if (ret)
/* If we can't read a valid phy address, then assume
* that there is only one phy on this mdio bus.
*/
adpt->phydev = phy_find_first(mii_bus);
else
adpt->phydev = mdiobus_get_phy(mii_bus, phy_addr);
/* of_phy_find_device() claims a reference to the phydev,
* so we do that here manually as well. When the driver
* later unloads, it can unilaterally drop the reference
* without worrying about ACPI vs DT.
*/
if (adpt->phydev)
get_device(&adpt->phydev->mdio.dev);
} else {
struct device_node *phy_np;
ret = of_mdiobus_register(mii_bus, np);
if (ret) {
dev_err(&pdev->dev, "could not register mdio bus\n");
return ret;
}
phy_np = of_parse_phandle(np, "phy-handle", 0);
adpt->phydev = of_phy_find_device(phy_np);
of_node_put(phy_np);
}
if (!adpt->phydev) {
dev_err(&pdev->dev, "could not find external phy\n");
mdiobus_unregister(mii_bus);
return -ENODEV;
}
return 0;
}
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*/
#ifndef _EMAC_PHY_H_
#define _EMAC_PHY_H_
struct emac_adapter;
int emac_phy_config(struct platform_device *pdev, struct emac_adapter *adpt);
#endif /* _EMAC_PHY_H_ */
@@ -0,0 +1,237 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*/
/* Qualcomm Technologies, Inc. FSM9900 EMAC SGMII Controller driver.
*/
#include <linux/iopoll.h>
#include "emac.h"
/* EMAC_QSERDES register offsets */
#define EMAC_QSERDES_COM_SYS_CLK_CTRL 0x0000
#define EMAC_QSERDES_COM_PLL_CNTRL 0x0014
#define EMAC_QSERDES_COM_PLL_IP_SETI 0x0018
#define EMAC_QSERDES_COM_PLL_CP_SETI 0x0024
#define EMAC_QSERDES_COM_PLL_IP_SETP 0x0028
#define EMAC_QSERDES_COM_PLL_CP_SETP 0x002c
#define EMAC_QSERDES_COM_SYSCLK_EN_SEL 0x0038
#define EMAC_QSERDES_COM_RESETSM_CNTRL 0x0040
#define EMAC_QSERDES_COM_PLLLOCK_CMP1 0x0044
#define EMAC_QSERDES_COM_PLLLOCK_CMP2 0x0048
#define EMAC_QSERDES_COM_PLLLOCK_CMP3 0x004c
#define EMAC_QSERDES_COM_PLLLOCK_CMP_EN 0x0050
#define EMAC_QSERDES_COM_DEC_START1 0x0064
#define EMAC_QSERDES_COM_DIV_FRAC_START1 0x0098
#define EMAC_QSERDES_COM_DIV_FRAC_START2 0x009c
#define EMAC_QSERDES_COM_DIV_FRAC_START3 0x00a0
#define EMAC_QSERDES_COM_DEC_START2 0x00a4
#define EMAC_QSERDES_COM_PLL_CRCTRL 0x00ac
#define EMAC_QSERDES_COM_RESET_SM 0x00bc
#define EMAC_QSERDES_TX_BIST_MODE_LANENO 0x0100
#define EMAC_QSERDES_TX_TX_EMP_POST1_LVL 0x0108
#define EMAC_QSERDES_TX_TX_DRV_LVL 0x010c
#define EMAC_QSERDES_TX_LANE_MODE 0x0150
#define EMAC_QSERDES_TX_TRAN_DRVR_EMP_EN 0x0170
#define EMAC_QSERDES_RX_CDR_CONTROL 0x0200
#define EMAC_QSERDES_RX_CDR_CONTROL2 0x0210
#define EMAC_QSERDES_RX_RX_EQ_GAIN12 0x0230
/* EMAC_SGMII register offsets */
#define EMAC_SGMII_PHY_SERDES_START 0x0000
#define EMAC_SGMII_PHY_CMN_PWR_CTRL 0x0004
#define EMAC_SGMII_PHY_RX_PWR_CTRL 0x0008
#define EMAC_SGMII_PHY_TX_PWR_CTRL 0x000C
#define EMAC_SGMII_PHY_LANE_CTRL1 0x0018
#define EMAC_SGMII_PHY_CDR_CTRL0 0x0058
#define EMAC_SGMII_PHY_POW_DWN_CTRL0 0x0080
#define EMAC_SGMII_PHY_INTERRUPT_MASK 0x00b4
#define PLL_IPSETI(x) ((x) & 0x3f)
#define PLL_CPSETI(x) ((x) & 0xff)
#define PLL_IPSETP(x) ((x) & 0x3f)
#define PLL_CPSETP(x) ((x) & 0x1f)
#define PLL_RCTRL(x) (((x) & 0xf) << 4)
#define PLL_CCTRL(x) ((x) & 0xf)
#define LANE_MODE(x) ((x) & 0x1f)
#define SYSCLK_CM BIT(4)
#define SYSCLK_AC_COUPLE BIT(3)
#define OCP_EN BIT(5)
#define PLL_DIV_FFEN BIT(2)
#define PLL_DIV_ORD BIT(1)
#define SYSCLK_SEL_CMOS BIT(3)
#define FRQ_TUNE_MODE BIT(4)
#define PLLLOCK_CMP_EN BIT(0)
#define DEC_START1_MUX BIT(7)
#define DEC_START1(x) ((x) & 0x7f)
#define DIV_FRAC_START_MUX BIT(7)
#define DIV_FRAC_START(x) ((x) & 0x7f)
#define DIV_FRAC_START3_MUX BIT(4)
#define DIV_FRAC_START3(x) ((x) & 0xf)
#define DEC_START2_MUX BIT(1)
#define DEC_START2 BIT(0)
#define READY BIT(5)
#define TX_EMP_POST1_LVL_MUX BIT(5)
#define TX_EMP_POST1_LVL(x) ((x) & 0x1f)
#define TX_DRV_LVL_MUX BIT(4)
#define TX_DRV_LVL(x) ((x) & 0xf)
#define EMP_EN_MUX BIT(1)
#define EMP_EN BIT(0)
#define SECONDORDERENABLE BIT(6)
#define FIRSTORDER_THRESH(x) (((x) & 0x7) << 3)
#define SECONDORDERGAIN(x) ((x) & 0x7)
#define RX_EQ_GAIN2(x) (((x) & 0xf) << 4)
#define RX_EQ_GAIN1(x) ((x) & 0xf)
#define SERDES_START BIT(0)
#define BIAS_EN BIT(6)
#define PLL_EN BIT(5)
#define SYSCLK_EN BIT(4)
#define CLKBUF_L_EN BIT(3)
#define PLL_TXCLK_EN BIT(1)
#define PLL_RXCLK_EN BIT(0)
#define L0_RX_SIGDET_EN BIT(7)
#define L0_RX_TERM_MODE(x) (((x) & 3) << 4)
#define L0_RX_I_EN BIT(1)
#define L0_TX_EN BIT(5)
#define L0_CLKBUF_EN BIT(4)
#define L0_TRAN_BIAS_EN BIT(1)
#define L0_RX_EQUALIZE_ENABLE BIT(6)
#define L0_RESET_TSYNC_EN BIT(4)
#define L0_DRV_LVL(x) ((x) & 0xf)
#define PWRDN_B BIT(0)
#define CDR_MAX_CNT(x) ((x) & 0xff)
#define PLLLOCK_CMP(x) ((x) & 0xff)
#define SERDES_START_WAIT_TIMES 100
struct emac_reg_write {
unsigned int offset;
u32 val;
};
static void emac_reg_write_all(void __iomem *base,
const struct emac_reg_write *itr, size_t size)
{
size_t i;
for (i = 0; i < size; ++itr, ++i)
writel(itr->val, base + itr->offset);
}
static const struct emac_reg_write physical_coding_sublayer_programming[] = {
{EMAC_SGMII_PHY_CDR_CTRL0, CDR_MAX_CNT(15)},
{EMAC_SGMII_PHY_POW_DWN_CTRL0, PWRDN_B},
{EMAC_SGMII_PHY_CMN_PWR_CTRL,
BIAS_EN | SYSCLK_EN | CLKBUF_L_EN | PLL_TXCLK_EN | PLL_RXCLK_EN},
{EMAC_SGMII_PHY_TX_PWR_CTRL, L0_TX_EN | L0_CLKBUF_EN | L0_TRAN_BIAS_EN},
{EMAC_SGMII_PHY_RX_PWR_CTRL,
L0_RX_SIGDET_EN | L0_RX_TERM_MODE(1) | L0_RX_I_EN},
{EMAC_SGMII_PHY_CMN_PWR_CTRL,
BIAS_EN | PLL_EN | SYSCLK_EN | CLKBUF_L_EN | PLL_TXCLK_EN |
PLL_RXCLK_EN},
{EMAC_SGMII_PHY_LANE_CTRL1,
L0_RX_EQUALIZE_ENABLE | L0_RESET_TSYNC_EN | L0_DRV_LVL(15)},
};
static const struct emac_reg_write sysclk_refclk_setting[] = {
{EMAC_QSERDES_COM_SYSCLK_EN_SEL, SYSCLK_SEL_CMOS},
{EMAC_QSERDES_COM_SYS_CLK_CTRL, SYSCLK_CM | SYSCLK_AC_COUPLE},
};
static const struct emac_reg_write pll_setting[] = {
{EMAC_QSERDES_COM_PLL_IP_SETI, PLL_IPSETI(1)},
{EMAC_QSERDES_COM_PLL_CP_SETI, PLL_CPSETI(59)},
{EMAC_QSERDES_COM_PLL_IP_SETP, PLL_IPSETP(10)},
{EMAC_QSERDES_COM_PLL_CP_SETP, PLL_CPSETP(9)},
{EMAC_QSERDES_COM_PLL_CRCTRL, PLL_RCTRL(15) | PLL_CCTRL(11)},
{EMAC_QSERDES_COM_PLL_CNTRL, OCP_EN | PLL_DIV_FFEN | PLL_DIV_ORD},
{EMAC_QSERDES_COM_DEC_START1, DEC_START1_MUX | DEC_START1(2)},
{EMAC_QSERDES_COM_DEC_START2, DEC_START2_MUX | DEC_START2},
{EMAC_QSERDES_COM_DIV_FRAC_START1,
DIV_FRAC_START_MUX | DIV_FRAC_START(85)},
{EMAC_QSERDES_COM_DIV_FRAC_START2,
DIV_FRAC_START_MUX | DIV_FRAC_START(42)},
{EMAC_QSERDES_COM_DIV_FRAC_START3,
DIV_FRAC_START3_MUX | DIV_FRAC_START3(3)},
{EMAC_QSERDES_COM_PLLLOCK_CMP1, PLLLOCK_CMP(43)},
{EMAC_QSERDES_COM_PLLLOCK_CMP2, PLLLOCK_CMP(104)},
{EMAC_QSERDES_COM_PLLLOCK_CMP3, PLLLOCK_CMP(0)},
{EMAC_QSERDES_COM_PLLLOCK_CMP_EN, PLLLOCK_CMP_EN},
{EMAC_QSERDES_COM_RESETSM_CNTRL, FRQ_TUNE_MODE},
};
static const struct emac_reg_write cdr_setting[] = {
{EMAC_QSERDES_RX_CDR_CONTROL,
SECONDORDERENABLE | FIRSTORDER_THRESH(3) | SECONDORDERGAIN(2)},
{EMAC_QSERDES_RX_CDR_CONTROL2,
SECONDORDERENABLE | FIRSTORDER_THRESH(3) | SECONDORDERGAIN(4)},
};
static const struct emac_reg_write tx_rx_setting[] = {
{EMAC_QSERDES_TX_BIST_MODE_LANENO, 0},
{EMAC_QSERDES_TX_TX_DRV_LVL, TX_DRV_LVL_MUX | TX_DRV_LVL(15)},
{EMAC_QSERDES_TX_TRAN_DRVR_EMP_EN, EMP_EN_MUX | EMP_EN},
{EMAC_QSERDES_TX_TX_EMP_POST1_LVL,
TX_EMP_POST1_LVL_MUX | TX_EMP_POST1_LVL(1)},
{EMAC_QSERDES_RX_RX_EQ_GAIN12, RX_EQ_GAIN2(15) | RX_EQ_GAIN1(15)},
{EMAC_QSERDES_TX_LANE_MODE, LANE_MODE(8)},
};
int emac_sgmii_init_fsm9900(struct emac_adapter *adpt)
{
struct emac_sgmii *phy = &adpt->phy;
unsigned int i;
emac_reg_write_all(phy->base, physical_coding_sublayer_programming,
ARRAY_SIZE(physical_coding_sublayer_programming));
emac_reg_write_all(phy->base, sysclk_refclk_setting,
ARRAY_SIZE(sysclk_refclk_setting));
emac_reg_write_all(phy->base, pll_setting, ARRAY_SIZE(pll_setting));
emac_reg_write_all(phy->base, cdr_setting, ARRAY_SIZE(cdr_setting));
emac_reg_write_all(phy->base, tx_rx_setting, ARRAY_SIZE(tx_rx_setting));
/* Power up the Ser/Des engine */
writel(SERDES_START, phy->base + EMAC_SGMII_PHY_SERDES_START);
for (i = 0; i < SERDES_START_WAIT_TIMES; i++) {
if (readl(phy->base + EMAC_QSERDES_COM_RESET_SM) & READY)
break;
usleep_range(100, 200);
}
if (i == SERDES_START_WAIT_TIMES) {
netdev_err(adpt->netdev, "error: ser/des failed to start\n");
return -EIO;
}
/* Mask out all the SGMII Interrupt */
writel(0, phy->base + EMAC_SGMII_PHY_INTERRUPT_MASK);
return 0;
}
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*/
/* Qualcomm Technologies, Inc. QDF2400 EMAC SGMII Controller driver.
*/
#include <linux/iopoll.h>
#include "emac.h"
/* EMAC_SGMII register offsets */
#define EMAC_SGMII_PHY_TX_PWR_CTRL 0x000C
#define EMAC_SGMII_PHY_LANE_CTRL1 0x0018
#define EMAC_SGMII_PHY_CDR_CTRL0 0x0058
#define EMAC_SGMII_PHY_POW_DWN_CTRL0 0x0080
#define EMAC_SGMII_PHY_RESET_CTRL 0x00a8
#define EMAC_SGMII_PHY_INTERRUPT_MASK 0x00b4
/* SGMII digital lane registers */
#define EMAC_SGMII_LN_DRVR_CTRL0 0x000C
#define EMAC_SGMII_LN_DRVR_CTRL1 0x0010
#define EMAC_SGMII_LN_DRVR_TAP_EN 0x0018
#define EMAC_SGMII_LN_TX_MARGINING 0x001C
#define EMAC_SGMII_LN_TX_PRE 0x0020
#define EMAC_SGMII_LN_TX_POST 0x0024
#define EMAC_SGMII_LN_TX_BAND_MODE 0x0060
#define EMAC_SGMII_LN_LANE_MODE 0x0064
#define EMAC_SGMII_LN_PARALLEL_RATE 0x007C
#define EMAC_SGMII_LN_CML_CTRL_MODE0 0x00C0
#define EMAC_SGMII_LN_MIXER_CTRL_MODE0 0x00D8
#define EMAC_SGMII_LN_VGA_INITVAL 0x013C
#define EMAC_SGMII_LN_UCDR_FO_GAIN_MODE0 0x0184
#define EMAC_SGMII_LN_UCDR_SO_GAIN_MODE0 0x0190
#define EMAC_SGMII_LN_UCDR_SO_CONFIG 0x019C
#define EMAC_SGMII_LN_RX_BAND 0x01A4
#define EMAC_SGMII_LN_RX_RCVR_PATH1_MODE0 0x01C0
#define EMAC_SGMII_LN_RSM_CONFIG 0x01F8
#define EMAC_SGMII_LN_SIGDET_ENABLES 0x0230
#define EMAC_SGMII_LN_SIGDET_CNTRL 0x0234
#define EMAC_SGMII_LN_SIGDET_DEGLITCH_CNTRL 0x0238
#define EMAC_SGMII_LN_RX_EN_SIGNAL 0x02AC
#define EMAC_SGMII_LN_RX_MISC_CNTRL0 0x02B8
#define EMAC_SGMII_LN_DRVR_LOGIC_CLKDIV 0x02C8
#define EMAC_SGMII_LN_RX_RESECODE_OFFSET 0x02CC
/* SGMII digital lane register values */
#define UCDR_STEP_BY_TWO_MODE0 BIT(7)
#define UCDR_xO_GAIN_MODE(x) ((x) & 0x7f)
#define UCDR_ENABLE BIT(6)
#define UCDR_SO_SATURATION(x) ((x) & 0x3f)
#define SIGDET_LP_BYP_PS4 BIT(7)
#define SIGDET_EN_PS0_TO_PS2 BIT(6)
#define TXVAL_VALID_INIT BIT(4)
#define KR_PCIGEN3_MODE BIT(0)
#define MAIN_EN BIT(0)
#define TX_MARGINING_MUX BIT(6)
#define TX_MARGINING(x) ((x) & 0x3f)
#define TX_PRE_MUX BIT(6)
#define TX_POST_MUX BIT(6)
#define CML_GEAR_MODE(x) (((x) & 7) << 3)
#define CML2CMOS_IBOOST_MODE(x) ((x) & 7)
#define RESCODE_OFFSET(x) ((x) & 0x1f)
#define MIXER_LOADB_MODE(x) (((x) & 0xf) << 2)
#define MIXER_DATARATE_MODE(x) ((x) & 3)
#define VGA_THRESH_DFE(x) ((x) & 0x3f)
#define SIGDET_LP_BYP_PS0_TO_PS2 BIT(5)
#define SIGDET_FLT_BYP BIT(0)
#define SIGDET_LVL(x) (((x) & 0xf) << 4)
#define SIGDET_DEGLITCH_CTRL(x) (((x) & 0xf) << 1)
#define INVERT_PCS_RX_CLK BIT(7)
#define DRVR_LOGIC_CLK_EN BIT(4)
#define DRVR_LOGIC_CLK_DIV(x) ((x) & 0xf)
#define PARALLEL_RATE_MODE0(x) ((x) & 0x3)
#define BAND_MODE0(x) ((x) & 0x3)
#define LANE_MODE(x) ((x) & 0x1f)
#define CDR_PD_SEL_MODE0(x) (((x) & 0x3) << 5)
#define EN_DLL_MODE0 BIT(4)
#define EN_IQ_DCC_MODE0 BIT(3)
#define EN_IQCAL_MODE0 BIT(2)
#define BYPASS_RSM_SAMP_CAL BIT(1)
#define BYPASS_RSM_DLL_CAL BIT(0)
#define L0_RX_EQUALIZE_ENABLE BIT(6)
#define PWRDN_B BIT(0)
#define CDR_MAX_CNT(x) ((x) & 0xff)
#define SERDES_START_WAIT_TIMES 100
struct emac_reg_write {
unsigned int offset;
u32 val;
};
static void emac_reg_write_all(void __iomem *base,
const struct emac_reg_write *itr, size_t size)
{
size_t i;
for (i = 0; i < size; ++itr, ++i)
writel(itr->val, base + itr->offset);
}
static const struct emac_reg_write sgmii_laned[] = {
/* CDR Settings */
{EMAC_SGMII_LN_UCDR_FO_GAIN_MODE0,
UCDR_STEP_BY_TWO_MODE0 | UCDR_xO_GAIN_MODE(10)},
{EMAC_SGMII_LN_UCDR_SO_GAIN_MODE0, UCDR_xO_GAIN_MODE(0)},
{EMAC_SGMII_LN_UCDR_SO_CONFIG, UCDR_ENABLE | UCDR_SO_SATURATION(12)},
/* TX/RX Settings */
{EMAC_SGMII_LN_RX_EN_SIGNAL, SIGDET_LP_BYP_PS4 | SIGDET_EN_PS0_TO_PS2},
{EMAC_SGMII_LN_DRVR_CTRL0, TXVAL_VALID_INIT | KR_PCIGEN3_MODE},
{EMAC_SGMII_LN_DRVR_TAP_EN, MAIN_EN},
{EMAC_SGMII_LN_TX_MARGINING, TX_MARGINING_MUX | TX_MARGINING(25)},
{EMAC_SGMII_LN_TX_PRE, TX_PRE_MUX},
{EMAC_SGMII_LN_TX_POST, TX_POST_MUX},
{EMAC_SGMII_LN_CML_CTRL_MODE0,
CML_GEAR_MODE(1) | CML2CMOS_IBOOST_MODE(1)},
{EMAC_SGMII_LN_MIXER_CTRL_MODE0,
MIXER_LOADB_MODE(12) | MIXER_DATARATE_MODE(1)},
{EMAC_SGMII_LN_VGA_INITVAL, VGA_THRESH_DFE(31)},
{EMAC_SGMII_LN_SIGDET_ENABLES,
SIGDET_LP_BYP_PS0_TO_PS2 | SIGDET_FLT_BYP},
{EMAC_SGMII_LN_SIGDET_CNTRL, SIGDET_LVL(8)},
{EMAC_SGMII_LN_SIGDET_DEGLITCH_CNTRL, SIGDET_DEGLITCH_CTRL(4)},
{EMAC_SGMII_LN_RX_MISC_CNTRL0, INVERT_PCS_RX_CLK},
{EMAC_SGMII_LN_DRVR_LOGIC_CLKDIV,
DRVR_LOGIC_CLK_EN | DRVR_LOGIC_CLK_DIV(4)},
{EMAC_SGMII_LN_PARALLEL_RATE, PARALLEL_RATE_MODE0(1)},
{EMAC_SGMII_LN_TX_BAND_MODE, BAND_MODE0(1)},
{EMAC_SGMII_LN_RX_BAND, BAND_MODE0(2)},
{EMAC_SGMII_LN_DRVR_CTRL1, RESCODE_OFFSET(7)},
{EMAC_SGMII_LN_RX_RESECODE_OFFSET, RESCODE_OFFSET(9)},
{EMAC_SGMII_LN_LANE_MODE, LANE_MODE(26)},
{EMAC_SGMII_LN_RX_RCVR_PATH1_MODE0, CDR_PD_SEL_MODE0(2) |
EN_DLL_MODE0 | EN_IQ_DCC_MODE0 | EN_IQCAL_MODE0},
{EMAC_SGMII_LN_RSM_CONFIG, BYPASS_RSM_SAMP_CAL | BYPASS_RSM_DLL_CAL},
};
static const struct emac_reg_write physical_coding_sublayer_programming[] = {
{EMAC_SGMII_PHY_POW_DWN_CTRL0, PWRDN_B},
{EMAC_SGMII_PHY_CDR_CTRL0, CDR_MAX_CNT(15)},
{EMAC_SGMII_PHY_TX_PWR_CTRL, 0},
{EMAC_SGMII_PHY_LANE_CTRL1, L0_RX_EQUALIZE_ENABLE},
};
int emac_sgmii_init_qdf2400(struct emac_adapter *adpt)
{
struct emac_sgmii *phy = &adpt->phy;
void __iomem *phy_regs = phy->base;
void __iomem *laned = phy->digital;
unsigned int i;
u32 lnstatus;
/* PCS lane-x init */
emac_reg_write_all(phy->base, physical_coding_sublayer_programming,
ARRAY_SIZE(physical_coding_sublayer_programming));
/* SGMII lane-x init */
emac_reg_write_all(phy->digital, sgmii_laned, ARRAY_SIZE(sgmii_laned));
/* Power up PCS and start reset lane state machine */
writel(0, phy_regs + EMAC_SGMII_PHY_RESET_CTRL);
writel(1, laned + SGMII_LN_RSM_START);
/* Wait for c_ready assertion */
for (i = 0; i < SERDES_START_WAIT_TIMES; i++) {
lnstatus = readl(phy_regs + SGMII_PHY_LN_LANE_STATUS);
if (lnstatus & BIT(1))
break;
usleep_range(100, 200);
}
if (i == SERDES_START_WAIT_TIMES) {
netdev_err(adpt->netdev, "SGMII failed to start\n");
return -EIO;
}
/* Disable digital and SERDES loopback */
writel(0, phy_regs + SGMII_PHY_LN_BIST_GEN0);
writel(0, phy_regs + SGMII_PHY_LN_BIST_GEN2);
writel(0, phy_regs + SGMII_PHY_LN_CDR_CTRL1);
/* Mask out all the SGMII Interrupt */
writel(0, phy_regs + EMAC_SGMII_PHY_INTERRUPT_MASK);
return 0;
}
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*/
/* Qualcomm Technologies, Inc. QDF2432 EMAC SGMII Controller driver.
*/
#include <linux/iopoll.h>
#include "emac.h"
/* EMAC_SGMII register offsets */
#define EMAC_SGMII_PHY_TX_PWR_CTRL 0x000C
#define EMAC_SGMII_PHY_LANE_CTRL1 0x0018
#define EMAC_SGMII_PHY_CDR_CTRL0 0x0058
#define EMAC_SGMII_PHY_POW_DWN_CTRL0 0x0080
#define EMAC_SGMII_PHY_RESET_CTRL 0x00a8
#define EMAC_SGMII_PHY_INTERRUPT_MASK 0x00b4
/* SGMII digital lane registers */
#define EMAC_SGMII_LN_DRVR_CTRL0 0x000C
#define EMAC_SGMII_LN_DRVR_TAP_EN 0x0018
#define EMAC_SGMII_LN_TX_MARGINING 0x001C
#define EMAC_SGMII_LN_TX_PRE 0x0020
#define EMAC_SGMII_LN_TX_POST 0x0024
#define EMAC_SGMII_LN_TX_BAND_MODE 0x0060
#define EMAC_SGMII_LN_LANE_MODE 0x0064
#define EMAC_SGMII_LN_PARALLEL_RATE 0x0078
#define EMAC_SGMII_LN_CML_CTRL_MODE0 0x00B8
#define EMAC_SGMII_LN_MIXER_CTRL_MODE0 0x00D0
#define EMAC_SGMII_LN_VGA_INITVAL 0x0134
#define EMAC_SGMII_LN_UCDR_FO_GAIN_MODE0 0x017C
#define EMAC_SGMII_LN_UCDR_SO_GAIN_MODE0 0x0188
#define EMAC_SGMII_LN_UCDR_SO_CONFIG 0x0194
#define EMAC_SGMII_LN_RX_BAND 0x019C
#define EMAC_SGMII_LN_RX_RCVR_PATH1_MODE0 0x01B8
#define EMAC_SGMII_LN_RSM_CONFIG 0x01F0
#define EMAC_SGMII_LN_SIGDET_ENABLES 0x0224
#define EMAC_SGMII_LN_SIGDET_CNTRL 0x0228
#define EMAC_SGMII_LN_SIGDET_DEGLITCH_CNTRL 0x022C
#define EMAC_SGMII_LN_RX_EN_SIGNAL 0x02A0
#define EMAC_SGMII_LN_RX_MISC_CNTRL0 0x02AC
#define EMAC_SGMII_LN_DRVR_LOGIC_CLKDIV 0x02BC
/* SGMII digital lane register values */
#define UCDR_STEP_BY_TWO_MODE0 BIT(7)
#define UCDR_xO_GAIN_MODE(x) ((x) & 0x7f)
#define UCDR_ENABLE BIT(6)
#define UCDR_SO_SATURATION(x) ((x) & 0x3f)
#define SIGDET_LP_BYP_PS4 BIT(7)
#define SIGDET_EN_PS0_TO_PS2 BIT(6)
#define TXVAL_VALID_INIT BIT(4)
#define KR_PCIGEN3_MODE BIT(0)
#define MAIN_EN BIT(0)
#define TX_MARGINING_MUX BIT(6)
#define TX_MARGINING(x) ((x) & 0x3f)
#define TX_PRE_MUX BIT(6)
#define TX_POST_MUX BIT(6)
#define CML_GEAR_MODE(x) (((x) & 7) << 3)
#define CML2CMOS_IBOOST_MODE(x) ((x) & 7)
#define MIXER_LOADB_MODE(x) (((x) & 0xf) << 2)
#define MIXER_DATARATE_MODE(x) ((x) & 3)
#define VGA_THRESH_DFE(x) ((x) & 0x3f)
#define SIGDET_LP_BYP_PS0_TO_PS2 BIT(5)
#define SIGDET_FLT_BYP BIT(0)
#define SIGDET_LVL(x) (((x) & 0xf) << 4)
#define SIGDET_DEGLITCH_CTRL(x) (((x) & 0xf) << 1)
#define DRVR_LOGIC_CLK_EN BIT(4)
#define DRVR_LOGIC_CLK_DIV(x) ((x) & 0xf)
#define PARALLEL_RATE_MODE0(x) ((x) & 0x3)
#define BAND_MODE0(x) ((x) & 0x3)
#define LANE_MODE(x) ((x) & 0x1f)
#define CDR_PD_SEL_MODE0(x) (((x) & 0x3) << 5)
#define BYPASS_RSM_SAMP_CAL BIT(1)
#define BYPASS_RSM_DLL_CAL BIT(0)
#define L0_RX_EQUALIZE_ENABLE BIT(6)
#define PWRDN_B BIT(0)
#define CDR_MAX_CNT(x) ((x) & 0xff)
#define SERDES_START_WAIT_TIMES 100
struct emac_reg_write {
unsigned int offset;
u32 val;
};
static void emac_reg_write_all(void __iomem *base,
const struct emac_reg_write *itr, size_t size)
{
size_t i;
for (i = 0; i < size; ++itr, ++i)
writel(itr->val, base + itr->offset);
}
static const struct emac_reg_write sgmii_laned[] = {
/* CDR Settings */
{EMAC_SGMII_LN_UCDR_FO_GAIN_MODE0,
UCDR_STEP_BY_TWO_MODE0 | UCDR_xO_GAIN_MODE(10)},
{EMAC_SGMII_LN_UCDR_SO_GAIN_MODE0, UCDR_xO_GAIN_MODE(0)},
{EMAC_SGMII_LN_UCDR_SO_CONFIG, UCDR_ENABLE | UCDR_SO_SATURATION(12)},
/* TX/RX Settings */
{EMAC_SGMII_LN_RX_EN_SIGNAL, SIGDET_LP_BYP_PS4 | SIGDET_EN_PS0_TO_PS2},
{EMAC_SGMII_LN_DRVR_CTRL0, TXVAL_VALID_INIT | KR_PCIGEN3_MODE},
{EMAC_SGMII_LN_DRVR_TAP_EN, MAIN_EN},
{EMAC_SGMII_LN_TX_MARGINING, TX_MARGINING_MUX | TX_MARGINING(25)},
{EMAC_SGMII_LN_TX_PRE, TX_PRE_MUX},
{EMAC_SGMII_LN_TX_POST, TX_POST_MUX},
{EMAC_SGMII_LN_CML_CTRL_MODE0,
CML_GEAR_MODE(1) | CML2CMOS_IBOOST_MODE(1)},
{EMAC_SGMII_LN_MIXER_CTRL_MODE0,
MIXER_LOADB_MODE(12) | MIXER_DATARATE_MODE(1)},
{EMAC_SGMII_LN_VGA_INITVAL, VGA_THRESH_DFE(31)},
{EMAC_SGMII_LN_SIGDET_ENABLES,
SIGDET_LP_BYP_PS0_TO_PS2 | SIGDET_FLT_BYP},
{EMAC_SGMII_LN_SIGDET_CNTRL, SIGDET_LVL(8)},
{EMAC_SGMII_LN_SIGDET_DEGLITCH_CNTRL, SIGDET_DEGLITCH_CTRL(4)},
{EMAC_SGMII_LN_RX_MISC_CNTRL0, 0},
{EMAC_SGMII_LN_DRVR_LOGIC_CLKDIV,
DRVR_LOGIC_CLK_EN | DRVR_LOGIC_CLK_DIV(4)},
{EMAC_SGMII_LN_PARALLEL_RATE, PARALLEL_RATE_MODE0(1)},
{EMAC_SGMII_LN_TX_BAND_MODE, BAND_MODE0(2)},
{EMAC_SGMII_LN_RX_BAND, BAND_MODE0(3)},
{EMAC_SGMII_LN_LANE_MODE, LANE_MODE(26)},
{EMAC_SGMII_LN_RX_RCVR_PATH1_MODE0, CDR_PD_SEL_MODE0(3)},
{EMAC_SGMII_LN_RSM_CONFIG, BYPASS_RSM_SAMP_CAL | BYPASS_RSM_DLL_CAL},
};
static const struct emac_reg_write physical_coding_sublayer_programming[] = {
{EMAC_SGMII_PHY_POW_DWN_CTRL0, PWRDN_B},
{EMAC_SGMII_PHY_CDR_CTRL0, CDR_MAX_CNT(15)},
{EMAC_SGMII_PHY_TX_PWR_CTRL, 0},
{EMAC_SGMII_PHY_LANE_CTRL1, L0_RX_EQUALIZE_ENABLE},
};
int emac_sgmii_init_qdf2432(struct emac_adapter *adpt)
{
struct emac_sgmii *phy = &adpt->phy;
void __iomem *phy_regs = phy->base;
void __iomem *laned = phy->digital;
unsigned int i;
u32 lnstatus;
/* PCS lane-x init */
emac_reg_write_all(phy->base, physical_coding_sublayer_programming,
ARRAY_SIZE(physical_coding_sublayer_programming));
/* SGMII lane-x init */
emac_reg_write_all(phy->digital, sgmii_laned, ARRAY_SIZE(sgmii_laned));
/* Power up PCS and start reset lane state machine */
writel(0, phy_regs + EMAC_SGMII_PHY_RESET_CTRL);
writel(1, laned + SGMII_LN_RSM_START);
/* Wait for c_ready assertion */
for (i = 0; i < SERDES_START_WAIT_TIMES; i++) {
lnstatus = readl(phy_regs + SGMII_PHY_LN_LANE_STATUS);
if (lnstatus & BIT(1))
break;
usleep_range(100, 200);
}
if (i == SERDES_START_WAIT_TIMES) {
netdev_err(adpt->netdev, "SGMII failed to start\n");
return -EIO;
}
/* Disable digital and SERDES loopback */
writel(0, phy_regs + SGMII_PHY_LN_BIST_GEN0);
writel(0, phy_regs + SGMII_PHY_LN_BIST_GEN2);
writel(0, phy_regs + SGMII_PHY_LN_CDR_CTRL1);
/* Mask out all the SGMII Interrupt */
writel(0, phy_regs + EMAC_SGMII_PHY_INTERRUPT_MASK);
return 0;
}
@@ -0,0 +1,458 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*/
/* Qualcomm Technologies, Inc. EMAC SGMII Controller driver.
*/
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/acpi.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include "emac.h"
#include "emac-mac.h"
#include "emac-sgmii.h"
/* EMAC_SGMII register offsets */
#define EMAC_SGMII_PHY_AUTONEG_CFG2 0x0048
#define EMAC_SGMII_PHY_SPEED_CFG1 0x0074
#define EMAC_SGMII_PHY_IRQ_CMD 0x00ac
#define EMAC_SGMII_PHY_INTERRUPT_CLEAR 0x00b0
#define EMAC_SGMII_PHY_INTERRUPT_MASK 0x00b4
#define EMAC_SGMII_PHY_INTERRUPT_STATUS 0x00b8
#define EMAC_SGMII_PHY_RX_CHK_STATUS 0x00d4
#define FORCE_AN_TX_CFG BIT(5)
#define FORCE_AN_RX_CFG BIT(4)
#define AN_ENABLE BIT(0)
#define DUPLEX_MODE BIT(4)
#define SPDMODE_1000 BIT(1)
#define SPDMODE_100 BIT(0)
#define SPDMODE_10 0
#define CDR_ALIGN_DET BIT(6)
#define IRQ_GLOBAL_CLEAR BIT(0)
#define DECODE_CODE_ERR BIT(7)
#define DECODE_DISP_ERR BIT(6)
#define SGMII_PHY_IRQ_CLR_WAIT_TIME 10
#define SGMII_PHY_INTERRUPT_ERR (DECODE_CODE_ERR | DECODE_DISP_ERR)
#define SGMII_ISR_MASK (SGMII_PHY_INTERRUPT_ERR)
#define SERDES_START_WAIT_TIMES 100
int emac_sgmii_init(struct emac_adapter *adpt)
{
if (!(adpt->phy.sgmii_ops && adpt->phy.sgmii_ops->init))
return 0;
return adpt->phy.sgmii_ops->init(adpt);
}
int emac_sgmii_open(struct emac_adapter *adpt)
{
if (!(adpt->phy.sgmii_ops && adpt->phy.sgmii_ops->open))
return 0;
return adpt->phy.sgmii_ops->open(adpt);
}
void emac_sgmii_close(struct emac_adapter *adpt)
{
if (!(adpt->phy.sgmii_ops && adpt->phy.sgmii_ops->close))
return;
adpt->phy.sgmii_ops->close(adpt);
}
int emac_sgmii_link_change(struct emac_adapter *adpt, bool link_state)
{
if (!(adpt->phy.sgmii_ops && adpt->phy.sgmii_ops->link_change))
return 0;
return adpt->phy.sgmii_ops->link_change(adpt, link_state);
}
void emac_sgmii_reset(struct emac_adapter *adpt)
{
if (!(adpt->phy.sgmii_ops && adpt->phy.sgmii_ops->reset))
return;
adpt->phy.sgmii_ops->reset(adpt);
}
/* Initialize the SGMII link between the internal and external PHYs. */
static void emac_sgmii_link_init(struct emac_adapter *adpt)
{
struct emac_sgmii *phy = &adpt->phy;
u32 val;
/* Always use autonegotiation. It works no matter how the external
* PHY is configured.
*/
val = readl(phy->base + EMAC_SGMII_PHY_AUTONEG_CFG2);
val &= ~(FORCE_AN_RX_CFG | FORCE_AN_TX_CFG);
val |= AN_ENABLE;
writel(val, phy->base + EMAC_SGMII_PHY_AUTONEG_CFG2);
}
static int emac_sgmii_irq_clear(struct emac_adapter *adpt, u8 irq_bits)
{
struct emac_sgmii *phy = &adpt->phy;
u8 status;
writel_relaxed(irq_bits, phy->base + EMAC_SGMII_PHY_INTERRUPT_CLEAR);
writel_relaxed(IRQ_GLOBAL_CLEAR, phy->base + EMAC_SGMII_PHY_IRQ_CMD);
/* Ensure interrupt clear command is written to HW */
wmb();
/* After set the IRQ_GLOBAL_CLEAR bit, the status clearing must
* be confirmed before clearing the bits in other registers.
* It takes a few cycles for hw to clear the interrupt status.
*/
if (readl_poll_timeout_atomic(phy->base +
EMAC_SGMII_PHY_INTERRUPT_STATUS,
status, !(status & irq_bits), 1,
SGMII_PHY_IRQ_CLR_WAIT_TIME)) {
net_err_ratelimited("%s: failed to clear SGMII irq: status:0x%x bits:0x%x\n",
adpt->netdev->name, status, irq_bits);
return -EIO;
}
/* Finalize clearing procedure */
writel_relaxed(0, phy->base + EMAC_SGMII_PHY_IRQ_CMD);
writel_relaxed(0, phy->base + EMAC_SGMII_PHY_INTERRUPT_CLEAR);
/* Ensure that clearing procedure finalization is written to HW */
wmb();
return 0;
}
/* The number of decode errors that triggers a reset */
#define DECODE_ERROR_LIMIT 2
static irqreturn_t emac_sgmii_interrupt(int irq, void *data)
{
struct emac_adapter *adpt = data;
struct emac_sgmii *phy = &adpt->phy;
u8 status;
status = readl(phy->base + EMAC_SGMII_PHY_INTERRUPT_STATUS);
status &= SGMII_ISR_MASK;
if (!status)
return IRQ_HANDLED;
/* If we get a decoding error and CDR is not locked, then try
* resetting the internal PHY. The internal PHY uses an embedded
* clock with Clock and Data Recovery (CDR) to recover the
* clock and data.
*/
if (status & SGMII_PHY_INTERRUPT_ERR) {
int count;
/* The SGMII is capable of recovering from some decode
* errors automatically. However, if we get multiple
* decode errors in a row, then assume that something
* is wrong and reset the interface.
*/
count = atomic_inc_return(&phy->decode_error_count);
if (count == DECODE_ERROR_LIMIT) {
schedule_work(&adpt->work_thread);
atomic_set(&phy->decode_error_count, 0);
}
} else {
/* We only care about consecutive decode errors. */
atomic_set(&phy->decode_error_count, 0);
}
if (emac_sgmii_irq_clear(adpt, status))
schedule_work(&adpt->work_thread);
return IRQ_HANDLED;
}
static void emac_sgmii_reset_prepare(struct emac_adapter *adpt)
{
struct emac_sgmii *phy = &adpt->phy;
u32 val;
/* Reset PHY */
val = readl(phy->base + EMAC_EMAC_WRAPPER_CSR2);
writel(((val & ~PHY_RESET) | PHY_RESET), phy->base +
EMAC_EMAC_WRAPPER_CSR2);
/* Ensure phy-reset command is written to HW before the release cmd */
msleep(50);
val = readl(phy->base + EMAC_EMAC_WRAPPER_CSR2);
writel((val & ~PHY_RESET), phy->base + EMAC_EMAC_WRAPPER_CSR2);
/* Ensure phy-reset release command is written to HW before initializing
* SGMII
*/
msleep(50);
}
static void emac_sgmii_common_reset(struct emac_adapter *adpt)
{
int ret;
emac_sgmii_reset_prepare(adpt);
emac_sgmii_link_init(adpt);
ret = emac_sgmii_init(adpt);
if (ret)
netdev_err(adpt->netdev,
"could not reinitialize internal PHY (error=%i)\n",
ret);
}
static int emac_sgmii_common_open(struct emac_adapter *adpt)
{
struct emac_sgmii *sgmii = &adpt->phy;
int ret;
if (sgmii->irq) {
/* Make sure interrupts are cleared and disabled first */
ret = emac_sgmii_irq_clear(adpt, 0xff);
if (ret)
return ret;
writel(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK);
ret = request_irq(sgmii->irq, emac_sgmii_interrupt, 0,
"emac-sgmii", adpt);
if (ret) {
netdev_err(adpt->netdev,
"could not register handler for internal PHY\n");
return ret;
}
}
return 0;
}
static void emac_sgmii_common_close(struct emac_adapter *adpt)
{
struct emac_sgmii *sgmii = &adpt->phy;
/* Make sure interrupts are disabled */
writel(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK);
free_irq(sgmii->irq, adpt);
}
/* The error interrupts are only valid after the link is up */
static int emac_sgmii_common_link_change(struct emac_adapter *adpt, bool linkup)
{
struct emac_sgmii *sgmii = &adpt->phy;
int ret;
if (linkup) {
/* Clear and enable interrupts */
ret = emac_sgmii_irq_clear(adpt, 0xff);
if (ret)
return ret;
writel(SGMII_ISR_MASK,
sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK);
} else {
/* Disable interrupts */
writel(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK);
synchronize_irq(sgmii->irq);
}
return 0;
}
static struct sgmii_ops fsm9900_ops = {
.init = emac_sgmii_init_fsm9900,
.open = emac_sgmii_common_open,
.close = emac_sgmii_common_close,
.link_change = emac_sgmii_common_link_change,
.reset = emac_sgmii_common_reset,
};
static struct sgmii_ops qdf2432_ops = {
.init = emac_sgmii_init_qdf2432,
.open = emac_sgmii_common_open,
.close = emac_sgmii_common_close,
.link_change = emac_sgmii_common_link_change,
.reset = emac_sgmii_common_reset,
};
#ifdef CONFIG_ACPI
static struct sgmii_ops qdf2400_ops = {
.init = emac_sgmii_init_qdf2400,
.open = emac_sgmii_common_open,
.close = emac_sgmii_common_close,
.link_change = emac_sgmii_common_link_change,
.reset = emac_sgmii_common_reset,
};
#endif
struct emac_match_data {
struct sgmii_ops **sgmii_ops;
struct device *target_device;
};
static int emac_sgmii_acpi_match(struct device *dev, void *data)
{
#ifdef CONFIG_ACPI
static const struct acpi_device_id match_table[] = {
{
.id = "QCOM8071",
},
{}
};
const struct acpi_device_id *id = acpi_match_device(match_table, dev);
struct emac_match_data *match_data = data;
if (id) {
acpi_handle handle = ACPI_HANDLE(dev);
unsigned long long hrv;
acpi_status status;
status = acpi_evaluate_integer(handle, "_HRV", NULL, &hrv);
if (status) {
if (status == AE_NOT_FOUND)
/* Older versions of the QDF2432 ACPI tables do
* not have an _HRV property.
*/
hrv = 1;
else
/* Something is wrong with the tables */
return 0;
}
switch (hrv) {
case 1:
*match_data->sgmii_ops = &qdf2432_ops;
match_data->target_device = dev;
return 1;
case 2:
*match_data->sgmii_ops = &qdf2400_ops;
match_data->target_device = dev;
return 1;
}
}
#endif
return 0;
}
static const struct of_device_id emac_sgmii_dt_match[] = {
{
.compatible = "qcom,fsm9900-emac-sgmii",
.data = &fsm9900_ops,
},
{
.compatible = "qcom,qdf2432-emac-sgmii",
.data = &qdf2432_ops,
},
{}
};
int emac_sgmii_config(struct platform_device *pdev, struct emac_adapter *adpt)
{
struct platform_device *sgmii_pdev = NULL;
struct emac_sgmii *phy = &adpt->phy;
struct resource *res;
int ret;
if (has_acpi_companion(&pdev->dev)) {
struct emac_match_data match_data = {
.sgmii_ops = &phy->sgmii_ops,
.target_device = NULL,
};
struct device *dev;
device_for_each_child(&pdev->dev, &match_data, emac_sgmii_acpi_match);
dev = match_data.target_device;
if (!dev) {
dev_warn(&pdev->dev, "cannot find internal phy node\n");
return 0;
}
get_device(dev);
sgmii_pdev = to_platform_device(dev);
} else {
const struct of_device_id *match;
struct device_node *np;
np = of_parse_phandle(pdev->dev.of_node, "internal-phy", 0);
if (!np) {
dev_err(&pdev->dev, "missing internal-phy property\n");
return -ENODEV;
}
sgmii_pdev = of_find_device_by_node(np);
of_node_put(np);
if (!sgmii_pdev) {
dev_err(&pdev->dev, "invalid internal-phy property\n");
return -ENODEV;
}
match = of_match_device(emac_sgmii_dt_match, &sgmii_pdev->dev);
if (!match) {
dev_err(&pdev->dev, "unrecognized internal phy node\n");
ret = -ENODEV;
goto error_put_device;
}
phy->sgmii_ops = (struct sgmii_ops *)match->data;
}
/* Base address is the first address */
res = platform_get_resource(sgmii_pdev, IORESOURCE_MEM, 0);
if (!res) {
ret = -EINVAL;
goto error_put_device;
}
phy->base = ioremap(res->start, resource_size(res));
if (!phy->base) {
ret = -ENOMEM;
goto error_put_device;
}
/* v2 SGMII has a per-lane digital, so parse it if it exists */
res = platform_get_resource(sgmii_pdev, IORESOURCE_MEM, 1);
if (res) {
phy->digital = ioremap(res->start, resource_size(res));
if (!phy->digital) {
ret = -ENOMEM;
goto error_unmap_base;
}
}
ret = emac_sgmii_init(adpt);
if (ret)
goto error;
emac_sgmii_link_init(adpt);
ret = platform_get_irq(sgmii_pdev, 0);
if (ret > 0)
phy->irq = ret;
/* We've remapped the addresses, so we don't need the device any
* more. of_find_device_by_node() says we should release it.
*/
put_device(&sgmii_pdev->dev);
return 0;
error:
if (phy->digital)
iounmap(phy->digital);
error_unmap_base:
iounmap(phy->base);
error_put_device:
put_device(&sgmii_pdev->dev);
return ret;
}
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*/
#ifndef _EMAC_SGMII_H_
#define _EMAC_SGMII_H_
struct emac_adapter;
struct platform_device;
/** emac_sgmii - internal emac phy
* @init initialization function
* @open called when the driver is opened
* @close called when the driver is closed
* @link_change called when the link state changes
*/
struct sgmii_ops {
int (*init)(struct emac_adapter *adpt);
int (*open)(struct emac_adapter *adpt);
void (*close)(struct emac_adapter *adpt);
int (*link_change)(struct emac_adapter *adpt, bool link_state);
void (*reset)(struct emac_adapter *adpt);
};
/** emac_sgmii - internal emac phy
* @base base address
* @digital per-lane digital block
* @irq the interrupt number
* @decode_error_count reference count of consecutive decode errors
* @sgmii_ops sgmii ops
*/
struct emac_sgmii {
void __iomem *base;
void __iomem *digital;
unsigned int irq;
atomic_t decode_error_count;
struct sgmii_ops *sgmii_ops;
};
int emac_sgmii_config(struct platform_device *pdev, struct emac_adapter *adpt);
int emac_sgmii_init_fsm9900(struct emac_adapter *adpt);
int emac_sgmii_init_qdf2432(struct emac_adapter *adpt);
int emac_sgmii_init_qdf2400(struct emac_adapter *adpt);
int emac_sgmii_init(struct emac_adapter *adpt);
int emac_sgmii_open(struct emac_adapter *adpt);
void emac_sgmii_close(struct emac_adapter *adpt);
int emac_sgmii_link_change(struct emac_adapter *adpt, bool link_state);
void emac_sgmii_reset(struct emac_adapter *adpt);
#endif
@@ -0,0 +1,776 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
*/
/* Qualcomm Technologies, Inc. EMAC Gigabit Ethernet Driver */
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include "emac.h"
#include "emac-mac.h"
#include "emac-phy.h"
#include "emac-sgmii.h"
#define EMAC_MSG_DEFAULT (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK | \
NETIF_MSG_TIMER | NETIF_MSG_IFDOWN | NETIF_MSG_IFUP)
#define EMAC_RRD_SIZE 4
/* The RRD size if timestamping is enabled: */
#define EMAC_TS_RRD_SIZE 6
#define EMAC_TPD_SIZE 4
#define EMAC_RFD_SIZE 2
#define REG_MAC_RX_STATUS_BIN EMAC_RXMAC_STATC_REG0
#define REG_MAC_RX_STATUS_END EMAC_RXMAC_STATC_REG22
#define REG_MAC_TX_STATUS_BIN EMAC_TXMAC_STATC_REG0
#define REG_MAC_TX_STATUS_END EMAC_TXMAC_STATC_REG24
#define RXQ0_NUM_RFD_PREF_DEF 8
#define TXQ0_NUM_TPD_PREF_DEF 5
#define EMAC_PREAMBLE_DEF 7
#define DMAR_DLY_CNT_DEF 15
#define DMAW_DLY_CNT_DEF 4
#define IMR_NORMAL_MASK (ISR_ERROR | ISR_OVER | ISR_TX_PKT)
#define ISR_TX_PKT (\
TX_PKT_INT |\
TX_PKT_INT1 |\
TX_PKT_INT2 |\
TX_PKT_INT3)
#define ISR_OVER (\
RFD0_UR_INT |\
RFD1_UR_INT |\
RFD2_UR_INT |\
RFD3_UR_INT |\
RFD4_UR_INT |\
RXF_OF_INT |\
TXF_UR_INT)
#define ISR_ERROR (\
DMAR_TO_INT |\
DMAW_TO_INT |\
TXQ_TO_INT)
/* in sync with enum emac_clk_id */
static const char * const emac_clk_name[] = {
"axi_clk", "cfg_ahb_clk", "high_speed_clk", "mdio_clk", "tx_clk",
"rx_clk", "sys_clk"
};
void emac_reg_update32(void __iomem *addr, u32 mask, u32 val)
{
u32 data = readl(addr);
writel(((data & ~mask) | val), addr);
}
/* reinitialize */
int emac_reinit_locked(struct emac_adapter *adpt)
{
int ret;
mutex_lock(&adpt->reset_lock);
emac_mac_down(adpt);
emac_sgmii_reset(adpt);
ret = emac_mac_up(adpt);
mutex_unlock(&adpt->reset_lock);
return ret;
}
/* NAPI */
static int emac_napi_rtx(struct napi_struct *napi, int budget)
{
struct emac_rx_queue *rx_q =
container_of(napi, struct emac_rx_queue, napi);
struct emac_adapter *adpt = netdev_priv(rx_q->netdev);
struct emac_irq *irq = rx_q->irq;
int work_done = 0;
emac_mac_rx_process(adpt, rx_q, &work_done, budget);
if (work_done < budget) {
napi_complete_done(napi, work_done);
irq->mask |= rx_q->intr;
writel(irq->mask, adpt->base + EMAC_INT_MASK);
}
return work_done;
}
/* Transmit the packet */
static netdev_tx_t emac_start_xmit(struct sk_buff *skb,
struct net_device *netdev)
{
struct emac_adapter *adpt = netdev_priv(netdev);
return emac_mac_tx_buf_send(adpt, &adpt->tx_q, skb);
}
static irqreturn_t emac_isr(int _irq, void *data)
{
struct emac_irq *irq = data;
struct emac_adapter *adpt =
container_of(irq, struct emac_adapter, irq);
struct emac_rx_queue *rx_q = &adpt->rx_q;
u32 isr, status;
/* disable the interrupt */
writel(0, adpt->base + EMAC_INT_MASK);
isr = readl_relaxed(adpt->base + EMAC_INT_STATUS);
status = isr & irq->mask;
if (status == 0)
goto exit;
if (status & ISR_ERROR) {
net_err_ratelimited("%s: error interrupt 0x%lx\n",
adpt->netdev->name, status & ISR_ERROR);
/* reset MAC */
schedule_work(&adpt->work_thread);
}
/* Schedule the napi for receive queue with interrupt
* status bit set
*/
if (status & rx_q->intr) {
if (napi_schedule_prep(&rx_q->napi)) {
irq->mask &= ~rx_q->intr;
__napi_schedule(&rx_q->napi);
}
}
if (status & TX_PKT_INT)
emac_mac_tx_process(adpt, &adpt->tx_q);
if (status & ISR_OVER)
net_warn_ratelimited("%s: TX/RX overflow interrupt\n",
adpt->netdev->name);
exit:
/* enable the interrupt */
writel(irq->mask, adpt->base + EMAC_INT_MASK);
return IRQ_HANDLED;
}
/* Configure VLAN tag strip/insert feature */
static int emac_set_features(struct net_device *netdev,
netdev_features_t features)
{
netdev_features_t changed = features ^ netdev->features;
struct emac_adapter *adpt = netdev_priv(netdev);
/* We only need to reprogram the hardware if the VLAN tag features
* have changed, and if it's already running.
*/
if (!(changed & (NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX)))
return 0;
if (!netif_running(netdev))
return 0;
/* emac_mac_mode_config() uses netdev->features to configure the EMAC,
* so make sure it's set first.
*/
netdev->features = features;
return emac_reinit_locked(adpt);
}
/* Configure Multicast and Promiscuous modes */
static void emac_rx_mode_set(struct net_device *netdev)
{
struct emac_adapter *adpt = netdev_priv(netdev);
struct netdev_hw_addr *ha;
emac_mac_mode_config(adpt);
/* update multicast address filtering */
emac_mac_multicast_addr_clear(adpt);
netdev_for_each_mc_addr(ha, netdev)
emac_mac_multicast_addr_set(adpt, ha->addr);
}
/* Change the Maximum Transfer Unit (MTU) */
static int emac_change_mtu(struct net_device *netdev, int new_mtu)
{
struct emac_adapter *adpt = netdev_priv(netdev);
netif_dbg(adpt, hw, adpt->netdev,
"changing MTU from %d to %d\n", netdev->mtu,
new_mtu);
WRITE_ONCE(netdev->mtu, new_mtu);
if (netif_running(netdev))
return emac_reinit_locked(adpt);
return 0;
}
/* Called when the network interface is made active */
static int emac_open(struct net_device *netdev)
{
struct emac_adapter *adpt = netdev_priv(netdev);
struct emac_irq *irq = &adpt->irq;
int ret;
ret = request_irq(irq->irq, emac_isr, 0, "emac-core0", irq);
if (ret) {
netdev_err(adpt->netdev, "could not request emac-core0 irq\n");
return ret;
}
/* allocate rx/tx dma buffer & descriptors */
ret = emac_mac_rx_tx_rings_alloc_all(adpt);
if (ret) {
netdev_err(adpt->netdev, "error allocating rx/tx rings\n");
free_irq(irq->irq, irq);
return ret;
}
ret = emac_sgmii_open(adpt);
if (ret) {
emac_mac_rx_tx_rings_free_all(adpt);
free_irq(irq->irq, irq);
return ret;
}
ret = emac_mac_up(adpt);
if (ret) {
emac_mac_rx_tx_rings_free_all(adpt);
free_irq(irq->irq, irq);
emac_sgmii_close(adpt);
return ret;
}
return 0;
}
/* Called when the network interface is disabled */
static int emac_close(struct net_device *netdev)
{
struct emac_adapter *adpt = netdev_priv(netdev);
mutex_lock(&adpt->reset_lock);
emac_sgmii_close(adpt);
emac_mac_down(adpt);
emac_mac_rx_tx_rings_free_all(adpt);
free_irq(adpt->irq.irq, &adpt->irq);
mutex_unlock(&adpt->reset_lock);
return 0;
}
/* Respond to a TX hang */
static void emac_tx_timeout(struct net_device *netdev, unsigned int txqueue)
{
struct emac_adapter *adpt = netdev_priv(netdev);
schedule_work(&adpt->work_thread);
}
/**
* emac_update_hw_stats - read the EMAC stat registers
* @adpt: pointer to adapter struct
*
* Reads the stats registers and write the values to adpt->stats.
*
* adpt->stats.lock must be held while calling this function,
* and while reading from adpt->stats.
*/
void emac_update_hw_stats(struct emac_adapter *adpt)
{
struct emac_stats *stats = &adpt->stats;
u64 *stats_itr = &adpt->stats.rx_ok;
void __iomem *base = adpt->base;
unsigned int addr;
addr = REG_MAC_RX_STATUS_BIN;
while (addr <= REG_MAC_RX_STATUS_END) {
*stats_itr += readl_relaxed(base + addr);
stats_itr++;
addr += sizeof(u32);
}
/* additional rx status */
stats->rx_crc_align += readl_relaxed(base + EMAC_RXMAC_STATC_REG23);
stats->rx_jabbers += readl_relaxed(base + EMAC_RXMAC_STATC_REG24);
/* update tx status */
addr = REG_MAC_TX_STATUS_BIN;
stats_itr = &stats->tx_ok;
while (addr <= REG_MAC_TX_STATUS_END) {
*stats_itr += readl_relaxed(base + addr);
stats_itr++;
addr += sizeof(u32);
}
/* additional tx status */
stats->tx_col += readl_relaxed(base + EMAC_TXMAC_STATC_REG25);
}
/* Provide network statistics info for the interface */
static void emac_get_stats64(struct net_device *netdev,
struct rtnl_link_stats64 *net_stats)
{
struct emac_adapter *adpt = netdev_priv(netdev);
struct emac_stats *stats = &adpt->stats;
spin_lock(&stats->lock);
emac_update_hw_stats(adpt);
/* return parsed statistics */
net_stats->rx_packets = stats->rx_ok;
net_stats->tx_packets = stats->tx_ok;
net_stats->rx_bytes = stats->rx_byte_cnt;
net_stats->tx_bytes = stats->tx_byte_cnt;
net_stats->multicast = stats->rx_mcast;
net_stats->collisions = stats->tx_1_col + stats->tx_2_col * 2 +
stats->tx_late_col + stats->tx_abort_col;
net_stats->rx_errors = stats->rx_frag + stats->rx_fcs_err +
stats->rx_len_err + stats->rx_sz_ov +
stats->rx_align_err;
net_stats->rx_fifo_errors = stats->rx_rxf_ov;
net_stats->rx_length_errors = stats->rx_len_err;
net_stats->rx_crc_errors = stats->rx_fcs_err;
net_stats->rx_frame_errors = stats->rx_align_err;
net_stats->rx_over_errors = stats->rx_rxf_ov;
net_stats->rx_missed_errors = stats->rx_rxf_ov;
net_stats->tx_errors = stats->tx_late_col + stats->tx_abort_col +
stats->tx_underrun + stats->tx_trunc;
net_stats->tx_fifo_errors = stats->tx_underrun;
net_stats->tx_aborted_errors = stats->tx_abort_col;
net_stats->tx_window_errors = stats->tx_late_col;
spin_unlock(&stats->lock);
}
static const struct net_device_ops emac_netdev_ops = {
.ndo_open = emac_open,
.ndo_stop = emac_close,
.ndo_validate_addr = eth_validate_addr,
.ndo_start_xmit = emac_start_xmit,
.ndo_set_mac_address = eth_mac_addr,
.ndo_change_mtu = emac_change_mtu,
.ndo_eth_ioctl = phy_do_ioctl_running,
.ndo_tx_timeout = emac_tx_timeout,
.ndo_get_stats64 = emac_get_stats64,
.ndo_set_features = emac_set_features,
.ndo_set_rx_mode = emac_rx_mode_set,
};
/* Watchdog task routine, called to reinitialize the EMAC */
static void emac_work_thread(struct work_struct *work)
{
struct emac_adapter *adpt =
container_of(work, struct emac_adapter, work_thread);
emac_reinit_locked(adpt);
}
/* Initialize various data structures */
static void emac_init_adapter(struct emac_adapter *adpt)
{
u32 reg;
adpt->rrd_size = EMAC_RRD_SIZE;
adpt->tpd_size = EMAC_TPD_SIZE;
adpt->rfd_size = EMAC_RFD_SIZE;
/* descriptors */
adpt->tx_desc_cnt = EMAC_DEF_TX_DESCS;
adpt->rx_desc_cnt = EMAC_DEF_RX_DESCS;
/* dma */
adpt->dma_order = emac_dma_ord_out;
adpt->dmar_block = emac_dma_req_4096;
adpt->dmaw_block = emac_dma_req_128;
adpt->dmar_dly_cnt = DMAR_DLY_CNT_DEF;
adpt->dmaw_dly_cnt = DMAW_DLY_CNT_DEF;
adpt->tpd_burst = TXQ0_NUM_TPD_PREF_DEF;
adpt->rfd_burst = RXQ0_NUM_RFD_PREF_DEF;
/* irq moderator */
reg = ((EMAC_DEF_RX_IRQ_MOD >> 1) << IRQ_MODERATOR2_INIT_SHFT) |
((EMAC_DEF_TX_IRQ_MOD >> 1) << IRQ_MODERATOR_INIT_SHFT);
adpt->irq_mod = reg;
/* others */
adpt->preamble = EMAC_PREAMBLE_DEF;
/* default to automatic flow control */
adpt->automatic = true;
/* Disable single-pause-frame mode by default */
adpt->single_pause_mode = false;
}
/* Get the clock */
static int emac_clks_get(struct platform_device *pdev,
struct emac_adapter *adpt)
{
unsigned int i;
for (i = 0; i < EMAC_CLK_CNT; i++) {
struct clk *clk = devm_clk_get(&pdev->dev, emac_clk_name[i]);
if (IS_ERR(clk)) {
dev_err(&pdev->dev,
"could not claim clock %s (error=%li)\n",
emac_clk_name[i], PTR_ERR(clk));
return PTR_ERR(clk);
}
adpt->clk[i] = clk;
}
return 0;
}
/* Initialize clocks */
static int emac_clks_phase1_init(struct platform_device *pdev,
struct emac_adapter *adpt)
{
int ret;
/* On ACPI platforms, clocks are controlled by firmware and/or
* ACPI, not by drivers.
*/
if (has_acpi_companion(&pdev->dev))
return 0;
ret = emac_clks_get(pdev, adpt);
if (ret)
return ret;
ret = clk_prepare_enable(adpt->clk[EMAC_CLK_AXI]);
if (ret)
return ret;
ret = clk_prepare_enable(adpt->clk[EMAC_CLK_CFG_AHB]);
if (ret)
goto disable_clk_axi;
ret = clk_set_rate(adpt->clk[EMAC_CLK_HIGH_SPEED], 19200000);
if (ret)
goto disable_clk_cfg_ahb;
ret = clk_prepare_enable(adpt->clk[EMAC_CLK_HIGH_SPEED]);
if (ret)
goto disable_clk_cfg_ahb;
return 0;
disable_clk_cfg_ahb:
clk_disable_unprepare(adpt->clk[EMAC_CLK_CFG_AHB]);
disable_clk_axi:
clk_disable_unprepare(adpt->clk[EMAC_CLK_AXI]);
return ret;
}
/* Enable clocks; needs emac_clks_phase1_init to be called before */
static int emac_clks_phase2_init(struct platform_device *pdev,
struct emac_adapter *adpt)
{
int ret;
if (has_acpi_companion(&pdev->dev))
return 0;
ret = clk_set_rate(adpt->clk[EMAC_CLK_TX], 125000000);
if (ret)
return ret;
ret = clk_prepare_enable(adpt->clk[EMAC_CLK_TX]);
if (ret)
return ret;
ret = clk_set_rate(adpt->clk[EMAC_CLK_HIGH_SPEED], 125000000);
if (ret)
return ret;
ret = clk_set_rate(adpt->clk[EMAC_CLK_MDIO], 25000000);
if (ret)
return ret;
ret = clk_prepare_enable(adpt->clk[EMAC_CLK_MDIO]);
if (ret)
return ret;
ret = clk_prepare_enable(adpt->clk[EMAC_CLK_RX]);
if (ret)
return ret;
return clk_prepare_enable(adpt->clk[EMAC_CLK_SYS]);
}
static void emac_clks_teardown(struct emac_adapter *adpt)
{
unsigned int i;
for (i = 0; i < EMAC_CLK_CNT; i++)
clk_disable_unprepare(adpt->clk[i]);
}
/* Get the resources */
static int emac_probe_resources(struct platform_device *pdev,
struct emac_adapter *adpt)
{
struct net_device *netdev = adpt->netdev;
int ret = 0;
/* get mac address */
if (device_get_ethdev_address(&pdev->dev, netdev))
eth_hw_addr_random(netdev);
/* Core 0 interrupt */
ret = platform_get_irq(pdev, 0);
if (ret < 0)
return ret;
adpt->irq.irq = ret;
/* base register address */
adpt->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(adpt->base))
return PTR_ERR(adpt->base);
/* CSR register address */
adpt->csr = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(adpt->csr))
return PTR_ERR(adpt->csr);
netdev->base_addr = (unsigned long)adpt->base;
return 0;
}
static const struct of_device_id emac_dt_match[] = {
{
.compatible = "qcom,fsm9900-emac",
},
{}
};
MODULE_DEVICE_TABLE(of, emac_dt_match);
#if IS_ENABLED(CONFIG_ACPI)
static const struct acpi_device_id emac_acpi_match[] = {
{
.id = "QCOM8070",
},
{}
};
MODULE_DEVICE_TABLE(acpi, emac_acpi_match);
#endif
static int emac_probe(struct platform_device *pdev)
{
struct net_device *netdev;
struct emac_adapter *adpt;
struct emac_sgmii *phy;
u16 devid, revid;
u32 reg;
int ret;
/* The TPD buffer address is limited to:
* 1. PTP: 45bits. (Driver doesn't support yet.)
* 2. NON-PTP: 46bits.
*/
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(46));
if (ret) {
dev_err(&pdev->dev, "could not set DMA mask\n");
return ret;
}
netdev = alloc_etherdev(sizeof(struct emac_adapter));
if (!netdev)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, netdev);
SET_NETDEV_DEV(netdev, &pdev->dev);
emac_set_ethtool_ops(netdev);
adpt = netdev_priv(netdev);
adpt->netdev = netdev;
adpt->msg_enable = EMAC_MSG_DEFAULT;
phy = &adpt->phy;
atomic_set(&phy->decode_error_count, 0);
mutex_init(&adpt->reset_lock);
spin_lock_init(&adpt->stats.lock);
adpt->irq.mask = RX_PKT_INT0 | IMR_NORMAL_MASK;
ret = emac_probe_resources(pdev, adpt);
if (ret)
goto err_undo_netdev;
/* initialize clocks */
ret = emac_clks_phase1_init(pdev, adpt);
if (ret) {
dev_err(&pdev->dev, "could not initialize clocks\n");
goto err_undo_netdev;
}
netdev->watchdog_timeo = EMAC_WATCHDOG_TIME;
netdev->irq = adpt->irq.irq;
netdev->netdev_ops = &emac_netdev_ops;
emac_init_adapter(adpt);
/* init external phy */
ret = emac_phy_config(pdev, adpt);
if (ret)
goto err_undo_clocks;
/* init internal sgmii phy */
ret = emac_sgmii_config(pdev, adpt);
if (ret)
goto err_undo_mdiobus;
/* enable clocks */
ret = emac_clks_phase2_init(pdev, adpt);
if (ret) {
dev_err(&pdev->dev, "could not initialize clocks\n");
goto err_undo_mdiobus;
}
/* set hw features */
netdev->features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_RXCSUM |
NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_HW_VLAN_CTAG_RX |
NETIF_F_HW_VLAN_CTAG_TX;
netdev->hw_features = netdev->features;
netdev->vlan_features |= NETIF_F_SG | NETIF_F_HW_CSUM |
NETIF_F_TSO | NETIF_F_TSO6;
/* MTU range: 46 - 9194 */
netdev->min_mtu = EMAC_MIN_ETH_FRAME_SIZE -
(ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN);
netdev->max_mtu = EMAC_MAX_ETH_FRAME_SIZE -
(ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN);
INIT_WORK(&adpt->work_thread, emac_work_thread);
/* Initialize queues */
emac_mac_rx_tx_ring_init_all(pdev, adpt);
netif_napi_add(netdev, &adpt->rx_q.napi, emac_napi_rtx);
ret = register_netdev(netdev);
if (ret) {
dev_err(&pdev->dev, "could not register net device\n");
goto err_undo_napi;
}
reg = readl_relaxed(adpt->base + EMAC_DMA_MAS_CTRL);
devid = (reg & DEV_ID_NUM_BMSK) >> DEV_ID_NUM_SHFT;
revid = (reg & DEV_REV_NUM_BMSK) >> DEV_REV_NUM_SHFT;
reg = readl_relaxed(adpt->base + EMAC_CORE_HW_VERSION);
netif_info(adpt, probe, netdev,
"hardware id %d.%d, hardware version %d.%d.%d\n",
devid, revid,
(reg & MAJOR_BMSK) >> MAJOR_SHFT,
(reg & MINOR_BMSK) >> MINOR_SHFT,
(reg & STEP_BMSK) >> STEP_SHFT);
return 0;
err_undo_napi:
netif_napi_del(&adpt->rx_q.napi);
err_undo_mdiobus:
put_device(&adpt->phydev->mdio.dev);
mdiobus_unregister(adpt->mii_bus);
err_undo_clocks:
emac_clks_teardown(adpt);
err_undo_netdev:
free_netdev(netdev);
return ret;
}
static void emac_remove(struct platform_device *pdev)
{
struct net_device *netdev = dev_get_drvdata(&pdev->dev);
struct emac_adapter *adpt = netdev_priv(netdev);
netif_carrier_off(netdev);
netif_tx_disable(netdev);
unregister_netdev(netdev);
netif_napi_del(&adpt->rx_q.napi);
free_irq(adpt->irq.irq, &adpt->irq);
cancel_work_sync(&adpt->work_thread);
emac_clks_teardown(adpt);
put_device(&adpt->phydev->mdio.dev);
mdiobus_unregister(adpt->mii_bus);
if (adpt->phy.digital)
iounmap(adpt->phy.digital);
iounmap(adpt->phy.base);
free_netdev(netdev);
}
static void emac_shutdown(struct platform_device *pdev)
{
struct net_device *netdev = dev_get_drvdata(&pdev->dev);
struct emac_adapter *adpt = netdev_priv(netdev);
if (netdev->flags & IFF_UP) {
/* Closing the SGMII turns off its interrupts */
emac_sgmii_close(adpt);
/* Resetting the MAC turns off all DMA and its interrupts */
emac_mac_reset(adpt);
}
}
static struct platform_driver emac_platform_driver = {
.probe = emac_probe,
.remove = emac_remove,
.driver = {
.name = "qcom-emac",
.of_match_table = emac_dt_match,
.acpi_match_table = ACPI_PTR(emac_acpi_match),
},
.shutdown = emac_shutdown,
};
module_platform_driver(emac_platform_driver);
MODULE_DESCRIPTION("Qualcomm EMAC Gigabit Ethernet driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:qcom-emac");
@@ -0,0 +1,386 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
*/
#ifndef _EMAC_H_
#define _EMAC_H_
#include <linux/irqreturn.h>
#include <linux/netdevice.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include "emac-mac.h"
#include "emac-phy.h"
#include "emac-sgmii.h"
/* EMAC base register offsets */
#define EMAC_DMA_MAS_CTRL 0x1400
#define EMAC_IRQ_MOD_TIM_INIT 0x1408
#define EMAC_BLK_IDLE_STS 0x140c
#define EMAC_PHY_LINK_DELAY 0x141c
#define EMAC_SYS_ALIV_CTRL 0x1434
#define EMAC_MAC_CTRL 0x1480
#define EMAC_MAC_IPGIFG_CTRL 0x1484
#define EMAC_MAC_STA_ADDR0 0x1488
#define EMAC_MAC_STA_ADDR1 0x148c
#define EMAC_HASH_TAB_REG0 0x1490
#define EMAC_HASH_TAB_REG1 0x1494
#define EMAC_MAC_HALF_DPLX_CTRL 0x1498
#define EMAC_MAX_FRAM_LEN_CTRL 0x149c
#define EMAC_WOL_CTRL0 0x14a0
#define EMAC_RSS_KEY0 0x14b0
#define EMAC_H1TPD_BASE_ADDR_LO 0x14e0
#define EMAC_H2TPD_BASE_ADDR_LO 0x14e4
#define EMAC_H3TPD_BASE_ADDR_LO 0x14e8
#define EMAC_INTER_SRAM_PART9 0x1534
#define EMAC_DESC_CTRL_0 0x1540
#define EMAC_DESC_CTRL_1 0x1544
#define EMAC_DESC_CTRL_2 0x1550
#define EMAC_DESC_CTRL_10 0x1554
#define EMAC_DESC_CTRL_12 0x1558
#define EMAC_DESC_CTRL_13 0x155c
#define EMAC_DESC_CTRL_3 0x1560
#define EMAC_DESC_CTRL_4 0x1564
#define EMAC_DESC_CTRL_5 0x1568
#define EMAC_DESC_CTRL_14 0x156c
#define EMAC_DESC_CTRL_15 0x1570
#define EMAC_DESC_CTRL_16 0x1574
#define EMAC_DESC_CTRL_6 0x1578
#define EMAC_DESC_CTRL_8 0x1580
#define EMAC_DESC_CTRL_9 0x1584
#define EMAC_DESC_CTRL_11 0x1588
#define EMAC_TXQ_CTRL_0 0x1590
#define EMAC_TXQ_CTRL_1 0x1594
#define EMAC_TXQ_CTRL_2 0x1598
#define EMAC_RXQ_CTRL_0 0x15a0
#define EMAC_RXQ_CTRL_1 0x15a4
#define EMAC_RXQ_CTRL_2 0x15a8
#define EMAC_RXQ_CTRL_3 0x15ac
#define EMAC_BASE_CPU_NUMBER 0x15b8
#define EMAC_DMA_CTRL 0x15c0
#define EMAC_MAILBOX_0 0x15e0
#define EMAC_MAILBOX_5 0x15e4
#define EMAC_MAILBOX_6 0x15e8
#define EMAC_MAILBOX_13 0x15ec
#define EMAC_MAILBOX_2 0x15f4
#define EMAC_MAILBOX_3 0x15f8
#define EMAC_INT_STATUS 0x1600
#define EMAC_INT_MASK 0x1604
#define EMAC_MAILBOX_11 0x160c
#define EMAC_AXI_MAST_CTRL 0x1610
#define EMAC_MAILBOX_12 0x1614
#define EMAC_MAILBOX_9 0x1618
#define EMAC_MAILBOX_10 0x161c
#define EMAC_ATHR_HEADER_CTRL 0x1620
#define EMAC_RXMAC_STATC_REG0 0x1700
#define EMAC_RXMAC_STATC_REG22 0x1758
#define EMAC_TXMAC_STATC_REG0 0x1760
#define EMAC_TXMAC_STATC_REG24 0x17c0
#define EMAC_CLK_GATE_CTRL 0x1814
#define EMAC_CORE_HW_VERSION 0x1974
#define EMAC_MISC_CTRL 0x1990
#define EMAC_MAILBOX_7 0x19e0
#define EMAC_MAILBOX_8 0x19e4
#define EMAC_IDT_TABLE0 0x1b00
#define EMAC_RXMAC_STATC_REG23 0x1bc8
#define EMAC_RXMAC_STATC_REG24 0x1bcc
#define EMAC_TXMAC_STATC_REG25 0x1bd0
#define EMAC_MAILBOX_15 0x1bd4
#define EMAC_MAILBOX_16 0x1bd8
#define EMAC_INT1_MASK 0x1bf0
#define EMAC_INT1_STATUS 0x1bf4
#define EMAC_INT2_MASK 0x1bf8
#define EMAC_INT2_STATUS 0x1bfc
#define EMAC_INT3_MASK 0x1c00
#define EMAC_INT3_STATUS 0x1c04
/* EMAC_DMA_MAS_CTRL */
#define DEV_ID_NUM_BMSK 0x7f000000
#define DEV_ID_NUM_SHFT 24
#define DEV_REV_NUM_BMSK 0xff0000
#define DEV_REV_NUM_SHFT 16
#define INT_RD_CLR_EN 0x4000
#define IRQ_MODERATOR2_EN 0x800
#define IRQ_MODERATOR_EN 0x400
#define LPW_CLK_SEL 0x80
#define LPW_STATE 0x20
#define LPW_MODE 0x10
#define SOFT_RST 0x1
/* EMAC_IRQ_MOD_TIM_INIT */
#define IRQ_MODERATOR2_INIT_BMSK 0xffff0000
#define IRQ_MODERATOR2_INIT_SHFT 16
#define IRQ_MODERATOR_INIT_BMSK 0xffff
#define IRQ_MODERATOR_INIT_SHFT 0
/* EMAC_INT_STATUS */
#define DIS_INT BIT(31)
#define PTP_INT BIT(30)
#define RFD4_UR_INT BIT(29)
#define TX_PKT_INT3 BIT(26)
#define TX_PKT_INT2 BIT(25)
#define TX_PKT_INT1 BIT(24)
#define RX_PKT_INT3 BIT(19)
#define RX_PKT_INT2 BIT(18)
#define RX_PKT_INT1 BIT(17)
#define RX_PKT_INT0 BIT(16)
#define TX_PKT_INT BIT(15)
#define TXQ_TO_INT BIT(14)
#define GPHY_WAKEUP_INT BIT(13)
#define GPHY_LINK_DOWN_INT BIT(12)
#define GPHY_LINK_UP_INT BIT(11)
#define DMAW_TO_INT BIT(10)
#define DMAR_TO_INT BIT(9)
#define TXF_UR_INT BIT(8)
#define RFD3_UR_INT BIT(7)
#define RFD2_UR_INT BIT(6)
#define RFD1_UR_INT BIT(5)
#define RFD0_UR_INT BIT(4)
#define RXF_OF_INT BIT(3)
#define SW_MAN_INT BIT(2)
/* EMAC_MAILBOX_6 */
#define RFD2_PROC_IDX_BMSK 0xfff0000
#define RFD2_PROC_IDX_SHFT 16
#define RFD2_PROD_IDX_BMSK 0xfff
#define RFD2_PROD_IDX_SHFT 0
/* EMAC_CORE_HW_VERSION */
#define MAJOR_BMSK 0xf0000000
#define MAJOR_SHFT 28
#define MINOR_BMSK 0xfff0000
#define MINOR_SHFT 16
#define STEP_BMSK 0xffff
#define STEP_SHFT 0
/* EMAC_EMAC_WRAPPER_CSR1 */
#define TX_INDX_FIFO_SYNC_RST BIT(23)
#define TX_TS_FIFO_SYNC_RST BIT(22)
#define RX_TS_FIFO2_SYNC_RST BIT(21)
#define RX_TS_FIFO1_SYNC_RST BIT(20)
#define TX_TS_ENABLE BIT(16)
#define DIS_1588_CLKS BIT(11)
#define FREQ_MODE BIT(9)
#define ENABLE_RRD_TIMESTAMP BIT(3)
/* EMAC_EMAC_WRAPPER_CSR2 */
#define HDRIVE_BMSK 0x3000
#define HDRIVE_SHFT 12
#define SLB_EN BIT(9)
#define PLB_EN BIT(8)
#define WOL_EN BIT(3)
#define PHY_RESET BIT(0)
#define EMAC_DEV_ID 0x0040
/* SGMII v2 per lane registers */
#define SGMII_LN_RSM_START 0x029C
/* SGMII v2 PHY common registers */
#define SGMII_PHY_CMN_CTRL 0x0408
#define SGMII_PHY_CMN_RESET_CTRL 0x0410
/* SGMII v2 PHY registers per lane */
#define SGMII_PHY_LN_OFFSET 0x0400
#define SGMII_PHY_LN_LANE_STATUS 0x00DC
#define SGMII_PHY_LN_BIST_GEN0 0x008C
#define SGMII_PHY_LN_BIST_GEN1 0x0090
#define SGMII_PHY_LN_BIST_GEN2 0x0094
#define SGMII_PHY_LN_BIST_GEN3 0x0098
#define SGMII_PHY_LN_CDR_CTRL1 0x005C
enum emac_clk_id {
EMAC_CLK_AXI,
EMAC_CLK_CFG_AHB,
EMAC_CLK_HIGH_SPEED,
EMAC_CLK_MDIO,
EMAC_CLK_TX,
EMAC_CLK_RX,
EMAC_CLK_SYS,
EMAC_CLK_CNT
};
#define EMAC_LINK_SPEED_UNKNOWN 0x0
#define EMAC_LINK_SPEED_10_HALF BIT(0)
#define EMAC_LINK_SPEED_10_FULL BIT(1)
#define EMAC_LINK_SPEED_100_HALF BIT(2)
#define EMAC_LINK_SPEED_100_FULL BIT(3)
#define EMAC_LINK_SPEED_1GB_FULL BIT(5)
#define EMAC_MAX_SETUP_LNK_CYCLE 100
struct emac_stats {
/* rx */
u64 rx_ok; /* good packets */
u64 rx_bcast; /* good broadcast packets */
u64 rx_mcast; /* good multicast packets */
u64 rx_pause; /* pause packet */
u64 rx_ctrl; /* control packets other than pause frame. */
u64 rx_fcs_err; /* packets with bad FCS. */
u64 rx_len_err; /* packets with length mismatch */
u64 rx_byte_cnt; /* good bytes count (without FCS) */
u64 rx_runt; /* runt packets */
u64 rx_frag; /* fragment count */
u64 rx_sz_64; /* packets that are 64 bytes */
u64 rx_sz_65_127; /* packets that are 65-127 bytes */
u64 rx_sz_128_255; /* packets that are 128-255 bytes */
u64 rx_sz_256_511; /* packets that are 256-511 bytes */
u64 rx_sz_512_1023; /* packets that are 512-1023 bytes */
u64 rx_sz_1024_1518; /* packets that are 1024-1518 bytes */
u64 rx_sz_1519_max; /* packets that are 1519-MTU bytes*/
u64 rx_sz_ov; /* packets that are >MTU bytes (truncated) */
u64 rx_rxf_ov; /* packets dropped due to RX FIFO overflow */
u64 rx_align_err; /* alignment errors */
u64 rx_bcast_byte_cnt; /* broadcast packets byte count (without FCS) */
u64 rx_mcast_byte_cnt; /* multicast packets byte count (without FCS) */
u64 rx_err_addr; /* packets dropped due to address filtering */
u64 rx_crc_align; /* CRC align errors */
u64 rx_jabbers; /* jabbers */
/* tx */
u64 tx_ok; /* good packets */
u64 tx_bcast; /* good broadcast packets */
u64 tx_mcast; /* good multicast packets */
u64 tx_pause; /* pause packets */
u64 tx_exc_defer; /* packets with excessive deferral */
u64 tx_ctrl; /* control packets other than pause frame */
u64 tx_defer; /* packets that are deferred. */
u64 tx_byte_cnt; /* good bytes count (without FCS) */
u64 tx_sz_64; /* packets that are 64 bytes */
u64 tx_sz_65_127; /* packets that are 65-127 bytes */
u64 tx_sz_128_255; /* packets that are 128-255 bytes */
u64 tx_sz_256_511; /* packets that are 256-511 bytes */
u64 tx_sz_512_1023; /* packets that are 512-1023 bytes */
u64 tx_sz_1024_1518; /* packets that are 1024-1518 bytes */
u64 tx_sz_1519_max; /* packets that are 1519-MTU bytes */
u64 tx_1_col; /* packets single prior collision */
u64 tx_2_col; /* packets with multiple prior collisions */
u64 tx_late_col; /* packets with late collisions */
u64 tx_abort_col; /* packets aborted due to excess collisions */
u64 tx_underrun; /* packets aborted due to FIFO underrun */
u64 tx_rd_eop; /* count of reads beyond EOP */
u64 tx_len_err; /* packets with length mismatch */
u64 tx_trunc; /* packets truncated due to size >MTU */
u64 tx_bcast_byte; /* broadcast packets byte count (without FCS) */
u64 tx_mcast_byte; /* multicast packets byte count (without FCS) */
u64 tx_col; /* collisions */
spinlock_t lock; /* prevent multiple simultaneous readers */
};
/* RSS hstype Definitions */
#define EMAC_RSS_HSTYP_IPV4_EN 0x00000001
#define EMAC_RSS_HSTYP_TCP4_EN 0x00000002
#define EMAC_RSS_HSTYP_IPV6_EN 0x00000004
#define EMAC_RSS_HSTYP_TCP6_EN 0x00000008
#define EMAC_RSS_HSTYP_ALL_EN (\
EMAC_RSS_HSTYP_IPV4_EN |\
EMAC_RSS_HSTYP_TCP4_EN |\
EMAC_RSS_HSTYP_IPV6_EN |\
EMAC_RSS_HSTYP_TCP6_EN)
#define EMAC_VLAN_TO_TAG(_vlan, _tag) \
(_tag = ((((_vlan) >> 8) & 0xFF) | (((_vlan) & 0xFF) << 8)))
#define EMAC_TAG_TO_VLAN(_tag, _vlan) \
(_vlan = ((((_tag) >> 8) & 0xFF) | (((_tag) & 0xFF) << 8)))
#define EMAC_DEF_RX_BUF_SIZE 1536
#define EMAC_MAX_JUMBO_PKT_SIZE (9 * 1024)
#define EMAC_MAX_TX_OFFLOAD_THRESH (9 * 1024)
#define EMAC_MAX_ETH_FRAME_SIZE EMAC_MAX_JUMBO_PKT_SIZE
#define EMAC_MIN_ETH_FRAME_SIZE 68
#define EMAC_DEF_TX_QUEUES 1
#define EMAC_DEF_RX_QUEUES 1
#define EMAC_MIN_TX_DESCS 128
#define EMAC_MIN_RX_DESCS 128
#define EMAC_MAX_TX_DESCS 16383
#define EMAC_MAX_RX_DESCS 2047
#define EMAC_DEF_TX_DESCS 512
#define EMAC_DEF_RX_DESCS 256
#define EMAC_DEF_RX_IRQ_MOD 250
#define EMAC_DEF_TX_IRQ_MOD 250
#define EMAC_WATCHDOG_TIME (5 * HZ)
/* by default check link every 4 seconds */
#define EMAC_TRY_LINK_TIMEOUT (4 * HZ)
/* emac_irq per-device (per-adapter) irq properties.
* @irq: irq number.
* @mask mask to use over status register.
*/
struct emac_irq {
unsigned int irq;
u32 mask;
};
/* The device's main data structure */
struct emac_adapter {
struct net_device *netdev;
struct mii_bus *mii_bus;
struct phy_device *phydev;
void __iomem *base;
void __iomem *csr;
struct emac_sgmii phy;
struct emac_stats stats;
struct emac_irq irq;
struct clk *clk[EMAC_CLK_CNT];
/* All Descriptor memory */
struct emac_ring_header ring_header;
struct emac_tx_queue tx_q;
struct emac_rx_queue rx_q;
unsigned int tx_desc_cnt;
unsigned int rx_desc_cnt;
unsigned int rrd_size; /* in quad words */
unsigned int rfd_size; /* in quad words */
unsigned int tpd_size; /* in quad words */
unsigned int rxbuf_size;
/* Flow control / pause frames support. If automatic=True, do whatever
* the PHY does. Otherwise, use tx_flow_control and rx_flow_control.
*/
bool automatic;
bool tx_flow_control;
bool rx_flow_control;
/* True == use single-pause-frame mode. */
bool single_pause_mode;
/* Ring parameter */
u8 tpd_burst;
u8 rfd_burst;
unsigned int dmaw_dly_cnt;
unsigned int dmar_dly_cnt;
enum emac_dma_req_block dmar_block;
enum emac_dma_req_block dmaw_block;
enum emac_dma_order dma_order;
u32 irq_mod;
u32 preamble;
struct work_struct work_thread;
u16 msg_enable;
struct mutex reset_lock;
};
int emac_reinit_locked(struct emac_adapter *adpt);
void emac_reg_update32(void __iomem *addr, u32 mask, u32 val);
void emac_set_ethtool_ops(struct net_device *netdev);
void emac_update_hw_stats(struct emac_adapter *adpt);
#endif /* _EMAC_H_ */
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the device driver of PPE (Packet Process Engine) in IPQ SoC
#
obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
/* PPE platform device probe, DTSI parser and PPE clock initializations. */
#include <linux/clk.h>
#include <linux/interconnect.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include "ppe.h"
#include "ppe_config.h"
#include "ppe_debugfs.h"
#define PPE_PORT_MAX 8
#define PPE_CLK_RATE 353000000
/* ICC clocks for enabling PPE device. The avg_bw and peak_bw with value 0
* will be updated by the clock rate of PPE.
*/
static const struct icc_bulk_data ppe_icc_data[] = {
{
.name = "ppe",
.avg_bw = 0,
.peak_bw = 0,
},
{
.name = "ppe_cfg",
.avg_bw = 0,
.peak_bw = 0,
},
{
.name = "qos_gen",
.avg_bw = 6000,
.peak_bw = 6000,
},
{
.name = "timeout_ref",
.avg_bw = 6000,
.peak_bw = 6000,
},
{
.name = "nssnoc_memnoc",
.avg_bw = 533333,
.peak_bw = 533333,
},
{
.name = "memnoc_nssnoc",
.avg_bw = 533333,
.peak_bw = 533333,
},
{
.name = "memnoc_nssnoc_1",
.avg_bw = 533333,
.peak_bw = 533333,
},
};
static const struct regmap_range ppe_readable_ranges[] = {
regmap_reg_range(0x0, 0x1ff), /* Global */
regmap_reg_range(0x400, 0x5ff), /* LPI CSR */
regmap_reg_range(0x1000, 0x11ff), /* GMAC0 */
regmap_reg_range(0x1200, 0x13ff), /* GMAC1 */
regmap_reg_range(0x1400, 0x15ff), /* GMAC2 */
regmap_reg_range(0x1600, 0x17ff), /* GMAC3 */
regmap_reg_range(0x1800, 0x19ff), /* GMAC4 */
regmap_reg_range(0x1a00, 0x1bff), /* GMAC5 */
regmap_reg_range(0xb000, 0xefff), /* PRX CSR */
regmap_reg_range(0xf000, 0x1efff), /* IPE */
regmap_reg_range(0x20000, 0x5ffff), /* PTX CSR */
regmap_reg_range(0x60000, 0x9ffff), /* IPE L2 CSR */
regmap_reg_range(0xb0000, 0xeffff), /* IPO CSR */
regmap_reg_range(0x100000, 0x17ffff), /* IPE PC */
regmap_reg_range(0x180000, 0x1bffff), /* PRE IPO CSR */
regmap_reg_range(0x1d0000, 0x1dffff), /* Tunnel parser */
regmap_reg_range(0x1e0000, 0x1effff), /* Ingress parse */
regmap_reg_range(0x200000, 0x2fffff), /* IPE L3 */
regmap_reg_range(0x300000, 0x3fffff), /* IPE tunnel */
regmap_reg_range(0x400000, 0x4fffff), /* Scheduler */
regmap_reg_range(0x500000, 0x503fff), /* XGMAC0 */
regmap_reg_range(0x504000, 0x507fff), /* XGMAC1 */
regmap_reg_range(0x508000, 0x50bfff), /* XGMAC2 */
regmap_reg_range(0x50c000, 0x50ffff), /* XGMAC3 */
regmap_reg_range(0x510000, 0x513fff), /* XGMAC4 */
regmap_reg_range(0x514000, 0x517fff), /* XGMAC5 */
regmap_reg_range(0x600000, 0x6fffff), /* BM */
regmap_reg_range(0x800000, 0x9fffff), /* QM */
regmap_reg_range(0xb00000, 0xbef800), /* EDMA */
};
static const struct regmap_access_table ppe_reg_table = {
.yes_ranges = ppe_readable_ranges,
.n_yes_ranges = ARRAY_SIZE(ppe_readable_ranges),
};
static const struct regmap_config regmap_config_ipq9574 = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.rd_table = &ppe_reg_table,
.wr_table = &ppe_reg_table,
.max_register = 0xbef800,
.fast_io = true,
};
static int ppe_clock_init_and_reset(struct ppe_device *ppe_dev)
{
unsigned long ppe_rate = ppe_dev->clk_rate;
struct device *dev = ppe_dev->dev;
struct reset_control *rstc;
struct clk_bulk_data *clks;
struct clk *clk;
int ret, i;
for (i = 0; i < ppe_dev->num_icc_paths; i++) {
ppe_dev->icc_paths[i].name = ppe_icc_data[i].name;
ppe_dev->icc_paths[i].avg_bw = ppe_icc_data[i].avg_bw ? :
Bps_to_icc(ppe_rate);
/* PPE does not have an explicit peak bandwidth requirement,
* so set the peak bandwidth to be equal to the average
* bandwidth.
*/
ppe_dev->icc_paths[i].peak_bw = ppe_icc_data[i].peak_bw ? :
Bps_to_icc(ppe_rate);
}
ret = devm_of_icc_bulk_get(dev, ppe_dev->num_icc_paths,
ppe_dev->icc_paths);
if (ret)
return ret;
ret = icc_bulk_set_bw(ppe_dev->num_icc_paths, ppe_dev->icc_paths);
if (ret)
return ret;
/* The PPE clocks have a common parent clock. Setting the clock
* rate of "ppe" ensures the clock rate of all PPE clocks is
* configured to the same rate.
*/
clk = devm_clk_get(dev, "ppe");
if (IS_ERR(clk))
return PTR_ERR(clk);
ret = clk_set_rate(clk, ppe_rate);
if (ret)
return ret;
ret = devm_clk_bulk_get_all_enabled(dev, &clks);
if (ret < 0)
return ret;
/* Reset the PPE. */
rstc = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(rstc))
return PTR_ERR(rstc);
ret = reset_control_assert(rstc);
if (ret)
return ret;
/* The delay 10 ms of assert is necessary for resetting PPE. */
usleep_range(10000, 11000);
return reset_control_deassert(rstc);
}
static int qcom_ppe_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ppe_device *ppe_dev;
void __iomem *base;
int ret, num_icc;
num_icc = ARRAY_SIZE(ppe_icc_data);
ppe_dev = devm_kzalloc(dev, struct_size(ppe_dev, icc_paths, num_icc),
GFP_KERNEL);
if (!ppe_dev)
return -ENOMEM;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return dev_err_probe(dev, PTR_ERR(base), "PPE ioremap failed\n");
ppe_dev->regmap = devm_regmap_init_mmio(dev, base, &regmap_config_ipq9574);
if (IS_ERR(ppe_dev->regmap))
return dev_err_probe(dev, PTR_ERR(ppe_dev->regmap),
"PPE initialize regmap failed\n");
ppe_dev->dev = dev;
ppe_dev->clk_rate = PPE_CLK_RATE;
ppe_dev->num_ports = PPE_PORT_MAX;
ppe_dev->num_icc_paths = num_icc;
ret = ppe_clock_init_and_reset(ppe_dev);
if (ret)
return dev_err_probe(dev, ret, "PPE clock config failed\n");
ret = ppe_hw_config(ppe_dev);
if (ret)
return dev_err_probe(dev, ret, "PPE HW config failed\n");
ppe_debugfs_setup(ppe_dev);
platform_set_drvdata(pdev, ppe_dev);
return 0;
}
static void qcom_ppe_remove(struct platform_device *pdev)
{
struct ppe_device *ppe_dev;
ppe_dev = platform_get_drvdata(pdev);
ppe_debugfs_teardown(ppe_dev);
}
static const struct of_device_id qcom_ppe_of_match[] = {
{ .compatible = "qcom,ipq9574-ppe" },
{}
};
MODULE_DEVICE_TABLE(of, qcom_ppe_of_match);
static struct platform_driver qcom_ppe_driver = {
.driver = {
.name = "qcom_ppe",
.of_match_table = qcom_ppe_of_match,
},
.probe = qcom_ppe_probe,
.remove = qcom_ppe_remove,
};
module_platform_driver(qcom_ppe_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. IPQ PPE driver");
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#ifndef __PPE_H__
#define __PPE_H__
#include <linux/compiler.h>
#include <linux/interconnect.h>
struct device;
struct regmap;
struct dentry;
/**
* struct ppe_device - PPE device private data.
* @dev: PPE device structure.
* @regmap: PPE register map.
* @clk_rate: PPE clock rate.
* @num_ports: Number of PPE ports.
* @debugfs_root: Debugfs root entry.
* @num_icc_paths: Number of interconnect paths.
* @icc_paths: Interconnect path array.
*
* PPE device is the instance of PPE hardware, which is used to
* configure PPE packet process modules such as BM (buffer management),
* QM (queue management), and scheduler.
*/
struct ppe_device {
struct device *dev;
struct regmap *regmap;
unsigned long clk_rate;
unsigned int num_ports;
struct dentry *debugfs_root;
unsigned int num_icc_paths;
struct icc_bulk_data icc_paths[] __counted_by(num_icc_paths);
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,317 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#ifndef __PPE_CONFIG_H__
#define __PPE_CONFIG_H__
#include <linux/types.h>
#include "ppe.h"
/* There are different table index ranges for configuring queue base ID of
* the destination port, CPU code and service code.
*/
#define PPE_QUEUE_BASE_DEST_PORT 0
#define PPE_QUEUE_BASE_CPU_CODE 1024
#define PPE_QUEUE_BASE_SERVICE_CODE 2048
#define PPE_QUEUE_INTER_PRI_NUM 16
#define PPE_QUEUE_HASH_NUM 256
/* The service code is used by EDMA port to transmit packet to PPE. */
#define PPE_EDMA_SC_BYPASS_ID 1
/* The PPE RSS hash configured for IPv4 and IPv6 packet separately. */
#define PPE_RSS_HASH_MODE_IPV4 BIT(0)
#define PPE_RSS_HASH_MODE_IPV6 BIT(1)
#define PPE_RSS_HASH_IP_LENGTH 4
#define PPE_RSS_HASH_TUPLES 5
/* PPE supports 300 queues, each bit presents as one queue. */
#define PPE_RING_TO_QUEUE_BITMAP_WORD_CNT 10
/**
* enum ppe_scheduler_frame_mode - PPE scheduler frame mode.
* @PPE_SCH_WITH_IPG_PREAMBLE_FRAME_CRC: The scheduled frame includes IPG,
* preamble, Ethernet packet and CRC.
* @PPE_SCH_WITH_FRAME_CRC: The scheduled frame includes Ethernet frame and CRC
* excluding IPG and preamble.
* @PPE_SCH_WITH_L3_PAYLOAD: The scheduled frame includes layer 3 packet data.
*/
enum ppe_scheduler_frame_mode {
PPE_SCH_WITH_IPG_PREAMBLE_FRAME_CRC = 0,
PPE_SCH_WITH_FRAME_CRC = 1,
PPE_SCH_WITH_L3_PAYLOAD = 2,
};
/**
* struct ppe_scheduler_cfg - PPE scheduler configuration.
* @flow_id: PPE flow ID.
* @pri: Scheduler priority.
* @drr_node_id: Node ID for scheduled traffic.
* @drr_node_wt: Weight for scheduled traffic.
* @unit_is_packet: Packet based or byte based unit for scheduled traffic.
* @frame_mode: Packet mode to be scheduled.
*
* PPE scheduler supports commit rate and exceed rate configurations.
*/
struct ppe_scheduler_cfg {
int flow_id;
int pri;
int drr_node_id;
int drr_node_wt;
bool unit_is_packet;
enum ppe_scheduler_frame_mode frame_mode;
};
/**
* enum ppe_resource_type - PPE resource type.
* @PPE_RES_UCAST: Unicast queue resource.
* @PPE_RES_MCAST: Multicast queue resource.
* @PPE_RES_L0_NODE: Level 0 for queue based node resource.
* @PPE_RES_L1_NODE: Level 1 for flow based node resource.
* @PPE_RES_FLOW_ID: Flow based node resource.
*/
enum ppe_resource_type {
PPE_RES_UCAST,
PPE_RES_MCAST,
PPE_RES_L0_NODE,
PPE_RES_L1_NODE,
PPE_RES_FLOW_ID,
};
/**
* struct ppe_queue_ucast_dest - PPE unicast queue destination.
* @src_profile: Source profile.
* @service_code_en: Enable service code to map the queue base ID.
* @service_code: Service code.
* @cpu_code_en: Enable CPU code to map the queue base ID.
* @cpu_code: CPU code.
* @dest_port: destination port.
*
* PPE egress queue ID is decided by the service code if enabled, otherwise
* by the CPU code if enabled, or by destination port if both service code
* and CPU code are disabled.
*/
struct ppe_queue_ucast_dest {
int src_profile;
bool service_code_en;
int service_code;
bool cpu_code_en;
int cpu_code;
int dest_port;
};
/* Hardware bitmaps for bypassing features of the ingress packet. */
enum ppe_sc_ingress_type {
PPE_SC_BYPASS_INGRESS_VLAN_TAG_FMT_CHECK = 0,
PPE_SC_BYPASS_INGRESS_VLAN_MEMBER_CHECK = 1,
PPE_SC_BYPASS_INGRESS_VLAN_TRANSLATE = 2,
PPE_SC_BYPASS_INGRESS_MY_MAC_CHECK = 3,
PPE_SC_BYPASS_INGRESS_DIP_LOOKUP = 4,
PPE_SC_BYPASS_INGRESS_FLOW_LOOKUP = 5,
PPE_SC_BYPASS_INGRESS_FLOW_ACTION = 6,
PPE_SC_BYPASS_INGRESS_ACL = 7,
PPE_SC_BYPASS_INGRESS_FAKE_MAC_HEADER = 8,
PPE_SC_BYPASS_INGRESS_SERVICE_CODE = 9,
PPE_SC_BYPASS_INGRESS_WRONG_PKT_FMT_L2 = 10,
PPE_SC_BYPASS_INGRESS_WRONG_PKT_FMT_L3_IPV4 = 11,
PPE_SC_BYPASS_INGRESS_WRONG_PKT_FMT_L3_IPV6 = 12,
PPE_SC_BYPASS_INGRESS_WRONG_PKT_FMT_L4 = 13,
PPE_SC_BYPASS_INGRESS_FLOW_SERVICE_CODE = 14,
PPE_SC_BYPASS_INGRESS_ACL_SERVICE_CODE = 15,
PPE_SC_BYPASS_INGRESS_FAKE_L2_PROTO = 16,
PPE_SC_BYPASS_INGRESS_PPPOE_TERMINATION = 17,
PPE_SC_BYPASS_INGRESS_DEFAULT_VLAN = 18,
PPE_SC_BYPASS_INGRESS_DEFAULT_PCP = 19,
PPE_SC_BYPASS_INGRESS_VSI_ASSIGN = 20,
/* Values 21-23 are not specified by hardware. */
PPE_SC_BYPASS_INGRESS_VLAN_ASSIGN_FAIL = 24,
PPE_SC_BYPASS_INGRESS_SOURCE_GUARD = 25,
PPE_SC_BYPASS_INGRESS_MRU_MTU_CHECK = 26,
PPE_SC_BYPASS_INGRESS_FLOW_SRC_CHECK = 27,
PPE_SC_BYPASS_INGRESS_FLOW_QOS = 28,
/* This must be last as it determines the size of the BITMAP. */
PPE_SC_BYPASS_INGRESS_SIZE,
};
/* Hardware bitmaps for bypassing features of the egress packet. */
enum ppe_sc_egress_type {
PPE_SC_BYPASS_EGRESS_VLAN_MEMBER_CHECK = 0,
PPE_SC_BYPASS_EGRESS_VLAN_TRANSLATE = 1,
PPE_SC_BYPASS_EGRESS_VLAN_TAG_FMT_CTRL = 2,
PPE_SC_BYPASS_EGRESS_FDB_LEARN = 3,
PPE_SC_BYPASS_EGRESS_FDB_REFRESH = 4,
PPE_SC_BYPASS_EGRESS_L2_SOURCE_SECURITY = 5,
PPE_SC_BYPASS_EGRESS_MANAGEMENT_FWD = 6,
PPE_SC_BYPASS_EGRESS_BRIDGING_FWD = 7,
PPE_SC_BYPASS_EGRESS_IN_STP_FLTR = 8,
PPE_SC_BYPASS_EGRESS_EG_STP_FLTR = 9,
PPE_SC_BYPASS_EGRESS_SOURCE_FLTR = 10,
PPE_SC_BYPASS_EGRESS_POLICER = 11,
PPE_SC_BYPASS_EGRESS_L2_PKT_EDIT = 12,
PPE_SC_BYPASS_EGRESS_L3_PKT_EDIT = 13,
PPE_SC_BYPASS_EGRESS_ACL_POST_ROUTING_CHECK = 14,
PPE_SC_BYPASS_EGRESS_PORT_ISOLATION = 15,
PPE_SC_BYPASS_EGRESS_PRE_ACL_QOS = 16,
PPE_SC_BYPASS_EGRESS_POST_ACL_QOS = 17,
PPE_SC_BYPASS_EGRESS_DSCP_QOS = 18,
PPE_SC_BYPASS_EGRESS_PCP_QOS = 19,
PPE_SC_BYPASS_EGRESS_PREHEADER_QOS = 20,
PPE_SC_BYPASS_EGRESS_FAKE_MAC_DROP = 21,
PPE_SC_BYPASS_EGRESS_TUNL_CONTEXT = 22,
PPE_SC_BYPASS_EGRESS_FLOW_POLICER = 23,
/* This must be last as it determines the size of the BITMAP. */
PPE_SC_BYPASS_EGRESS_SIZE,
};
/* Hardware bitmaps for bypassing counter of packet. */
enum ppe_sc_counter_type {
PPE_SC_BYPASS_COUNTER_RX_VLAN = 0,
PPE_SC_BYPASS_COUNTER_RX = 1,
PPE_SC_BYPASS_COUNTER_TX_VLAN = 2,
PPE_SC_BYPASS_COUNTER_TX = 3,
/* This must be last as it determines the size of the BITMAP. */
PPE_SC_BYPASS_COUNTER_SIZE,
};
/* Hardware bitmaps for bypassing features of tunnel packet. */
enum ppe_sc_tunnel_type {
PPE_SC_BYPASS_TUNNEL_SERVICE_CODE = 0,
PPE_SC_BYPASS_TUNNEL_TUNNEL_HANDLE = 1,
PPE_SC_BYPASS_TUNNEL_L3_IF_CHECK = 2,
PPE_SC_BYPASS_TUNNEL_VLAN_CHECK = 3,
PPE_SC_BYPASS_TUNNEL_DMAC_CHECK = 4,
PPE_SC_BYPASS_TUNNEL_UDP_CSUM_0_CHECK = 5,
PPE_SC_BYPASS_TUNNEL_TBL_DE_ACCE_CHECK = 6,
PPE_SC_BYPASS_TUNNEL_PPPOE_MC_TERM_CHECK = 7,
PPE_SC_BYPASS_TUNNEL_TTL_EXCEED_CHECK = 8,
PPE_SC_BYPASS_TUNNEL_MAP_SRC_CHECK = 9,
PPE_SC_BYPASS_TUNNEL_MAP_DST_CHECK = 10,
PPE_SC_BYPASS_TUNNEL_LPM_DST_LOOKUP = 11,
PPE_SC_BYPASS_TUNNEL_LPM_LOOKUP = 12,
PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_L2 = 13,
PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_L3_IPV4 = 14,
PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_L3_IPV6 = 15,
PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_L4 = 16,
PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_TUNNEL = 17,
/* Values 18-19 are not specified by hardware. */
PPE_SC_BYPASS_TUNNEL_PRE_IPO = 20,
/* This must be last as it determines the size of the BITMAP. */
PPE_SC_BYPASS_TUNNEL_SIZE,
};
/**
* struct ppe_sc_bypass - PPE service bypass bitmaps
* @ingress: Bitmap of features that can be bypassed on the ingress packet.
* @egress: Bitmap of features that can be bypassed on the egress packet.
* @counter: Bitmap of features that can be bypassed on the counter type.
* @tunnel: Bitmap of features that can be bypassed on the tunnel packet.
*/
struct ppe_sc_bypass {
DECLARE_BITMAP(ingress, PPE_SC_BYPASS_INGRESS_SIZE);
DECLARE_BITMAP(egress, PPE_SC_BYPASS_EGRESS_SIZE);
DECLARE_BITMAP(counter, PPE_SC_BYPASS_COUNTER_SIZE);
DECLARE_BITMAP(tunnel, PPE_SC_BYPASS_TUNNEL_SIZE);
};
/**
* struct ppe_sc_cfg - PPE service code configuration.
* @dest_port_valid: Generate destination port or not.
* @dest_port: Destination port ID.
* @bitmaps: Bitmap of bypass features.
* @is_src: Destination port acts as source port, packet sent to CPU.
* @next_service_code: New service code generated.
* @eip_field_update_bitmap: Fields updated as actions taken for EIP.
* @eip_hw_service: Selected hardware functions for EIP.
* @eip_offset_sel: Packet offset selection, using packet's layer 4 offset
* or using packet's layer 3 offset for EIP.
*
* Service code is generated during the packet passing through PPE.
*/
struct ppe_sc_cfg {
bool dest_port_valid;
int dest_port;
struct ppe_sc_bypass bitmaps;
bool is_src;
int next_service_code;
int eip_field_update_bitmap;
int eip_hw_service;
int eip_offset_sel;
};
/**
* enum ppe_action_type - PPE action of the received packet.
* @PPE_ACTION_FORWARD: Packet forwarded per L2/L3 process.
* @PPE_ACTION_DROP: Packet dropped by PPE.
* @PPE_ACTION_COPY_TO_CPU: Packet copied to CPU port per multicast queue.
* @PPE_ACTION_REDIRECT_TO_CPU: Packet redirected to CPU port per unicast queue.
*/
enum ppe_action_type {
PPE_ACTION_FORWARD = 0,
PPE_ACTION_DROP = 1,
PPE_ACTION_COPY_TO_CPU = 2,
PPE_ACTION_REDIRECT_TO_CPU = 3,
};
/**
* struct ppe_rss_hash_cfg - PPE RSS hash configuration.
* @hash_mask: Mask of the generated hash value.
* @hash_fragment_mode: Hash generation mode for the first fragment of TCP,
* UDP and UDP-Lite packets, to use either 3 tuple or 5 tuple for RSS hash
* key computation.
* @hash_seed: Seed to generate RSS hash.
* @hash_sip_mix: Source IP selection.
* @hash_dip_mix: Destination IP selection.
* @hash_protocol_mix: Protocol selection.
* @hash_sport_mix: Source L4 port selection.
* @hash_dport_mix: Destination L4 port selection.
* @hash_fin_inner: RSS hash value first selection.
* @hash_fin_outer: RSS hash value second selection.
*
* PPE RSS hash value is generated for the packet based on the RSS hash
* configured.
*/
struct ppe_rss_hash_cfg {
u32 hash_mask;
bool hash_fragment_mode;
u32 hash_seed;
u8 hash_sip_mix[PPE_RSS_HASH_IP_LENGTH];
u8 hash_dip_mix[PPE_RSS_HASH_IP_LENGTH];
u8 hash_protocol_mix;
u8 hash_sport_mix;
u8 hash_dport_mix;
u8 hash_fin_inner[PPE_RSS_HASH_TUPLES];
u8 hash_fin_outer[PPE_RSS_HASH_TUPLES];
};
int ppe_hw_config(struct ppe_device *ppe_dev);
int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
int node_id, bool flow_level, int port,
struct ppe_scheduler_cfg scheduler_cfg);
int ppe_queue_ucast_base_set(struct ppe_device *ppe_dev,
struct ppe_queue_ucast_dest queue_dst,
int queue_base,
int profile_id);
int ppe_queue_ucast_offset_pri_set(struct ppe_device *ppe_dev,
int profile_id,
int priority,
int queue_offset);
int ppe_queue_ucast_offset_hash_set(struct ppe_device *ppe_dev,
int profile_id,
int rss_hash,
int queue_offset);
int ppe_port_resource_get(struct ppe_device *ppe_dev, int port,
enum ppe_resource_type type,
int *res_start, int *res_end);
int ppe_sc_config_set(struct ppe_device *ppe_dev, int sc,
struct ppe_sc_cfg cfg);
int ppe_counter_enable_set(struct ppe_device *ppe_dev, int port);
int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode,
struct ppe_rss_hash_cfg hash_cfg);
int ppe_ring_queue_map_set(struct ppe_device *ppe_dev,
int ring_id,
u32 *queue_map);
#endif
@@ -0,0 +1,847 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
/* PPE debugfs routines for display of PPE counters useful for debug. */
#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/dev_printk.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/seq_file.h>
#include "ppe.h"
#include "ppe_config.h"
#include "ppe_debugfs.h"
#include "ppe_regs.h"
#define PPE_PKT_CNT_TBL_SIZE 3
#define PPE_DROP_PKT_CNT_TBL_SIZE 5
#define PPE_W0_PKT_CNT GENMASK(31, 0)
#define PPE_W2_DROP_PKT_CNT_LOW GENMASK(31, 8)
#define PPE_W3_DROP_PKT_CNT_HIGH GENMASK(7, 0)
#define PPE_GET_PKT_CNT(tbl_cnt) \
FIELD_GET(PPE_W0_PKT_CNT, *(tbl_cnt))
#define PPE_GET_DROP_PKT_CNT_LOW(tbl_cnt) \
FIELD_GET(PPE_W2_DROP_PKT_CNT_LOW, *((tbl_cnt) + 0x2))
#define PPE_GET_DROP_PKT_CNT_HIGH(tbl_cnt) \
FIELD_GET(PPE_W3_DROP_PKT_CNT_HIGH, *((tbl_cnt) + 0x3))
/**
* enum ppe_cnt_size_type - PPE counter size type
* @PPE_PKT_CNT_SIZE_1WORD: Counter size with single register
* @PPE_PKT_CNT_SIZE_3WORD: Counter size with table of 3 words
* @PPE_PKT_CNT_SIZE_5WORD: Counter size with table of 5 words
*
* PPE takes the different register size to record the packet counters.
* It uses single register, or register table with 3 words or 5 words.
* The counter with table size 5 words also records the drop counter.
* There are also some other counter types occupying sizes less than 32
* bits, which is not covered by this enumeration type.
*/
enum ppe_cnt_size_type {
PPE_PKT_CNT_SIZE_1WORD,
PPE_PKT_CNT_SIZE_3WORD,
PPE_PKT_CNT_SIZE_5WORD,
};
/**
* enum ppe_cnt_type - PPE counter type.
* @PPE_CNT_BM: Packet counter processed by BM.
* @PPE_CNT_PARSE: Packet counter parsed on ingress.
* @PPE_CNT_PORT_RX: Packet counter on the ingress port.
* @PPE_CNT_VLAN_RX: VLAN packet counter received.
* @PPE_CNT_L2_FWD: Packet counter processed by L2 forwarding.
* @PPE_CNT_CPU_CODE: Packet counter marked with various CPU codes.
* @PPE_CNT_VLAN_TX: VLAN packet counter transmitted.
* @PPE_CNT_PORT_TX: Packet counter on the egress port.
* @PPE_CNT_QM: Packet counter processed by QM.
*/
enum ppe_cnt_type {
PPE_CNT_BM,
PPE_CNT_PARSE,
PPE_CNT_PORT_RX,
PPE_CNT_VLAN_RX,
PPE_CNT_L2_FWD,
PPE_CNT_CPU_CODE,
PPE_CNT_VLAN_TX,
PPE_CNT_PORT_TX,
PPE_CNT_QM,
};
/**
* struct ppe_debugfs_entry - PPE debugfs entry.
* @name: Debugfs file name.
* @counter_type: PPE packet counter type.
* @ppe: PPE device.
*
* The PPE debugfs entry is used to create the debugfs file and passed
* to debugfs_create_file() as private data.
*/
struct ppe_debugfs_entry {
const char *name;
enum ppe_cnt_type counter_type;
struct ppe_device *ppe;
};
static const struct ppe_debugfs_entry debugfs_files[] = {
{
.name = "bm",
.counter_type = PPE_CNT_BM,
},
{
.name = "parse",
.counter_type = PPE_CNT_PARSE,
},
{
.name = "port_rx",
.counter_type = PPE_CNT_PORT_RX,
},
{
.name = "vlan_rx",
.counter_type = PPE_CNT_VLAN_RX,
},
{
.name = "l2_forward",
.counter_type = PPE_CNT_L2_FWD,
},
{
.name = "cpu_code",
.counter_type = PPE_CNT_CPU_CODE,
},
{
.name = "vlan_tx",
.counter_type = PPE_CNT_VLAN_TX,
},
{
.name = "port_tx",
.counter_type = PPE_CNT_PORT_TX,
},
{
.name = "qm",
.counter_type = PPE_CNT_QM,
},
};
static int ppe_pkt_cnt_get(struct ppe_device *ppe_dev, u32 reg,
enum ppe_cnt_size_type cnt_type,
u32 *cnt, u32 *drop_cnt)
{
u32 drop_pkt_cnt[PPE_DROP_PKT_CNT_TBL_SIZE];
u32 pkt_cnt[PPE_PKT_CNT_TBL_SIZE];
u32 value;
int ret;
switch (cnt_type) {
case PPE_PKT_CNT_SIZE_1WORD:
ret = regmap_read(ppe_dev->regmap, reg, &value);
if (ret)
return ret;
*cnt = value;
break;
case PPE_PKT_CNT_SIZE_3WORD:
ret = regmap_bulk_read(ppe_dev->regmap, reg,
pkt_cnt, ARRAY_SIZE(pkt_cnt));
if (ret)
return ret;
*cnt = PPE_GET_PKT_CNT(pkt_cnt);
break;
case PPE_PKT_CNT_SIZE_5WORD:
ret = regmap_bulk_read(ppe_dev->regmap, reg,
drop_pkt_cnt, ARRAY_SIZE(drop_pkt_cnt));
if (ret)
return ret;
*cnt = PPE_GET_PKT_CNT(drop_pkt_cnt);
/* Drop counter with low 24 bits. */
value = PPE_GET_DROP_PKT_CNT_LOW(drop_pkt_cnt);
*drop_cnt = FIELD_PREP(GENMASK(23, 0), value);
/* Drop counter with high 8 bits. */
value = PPE_GET_DROP_PKT_CNT_HIGH(drop_pkt_cnt);
*drop_cnt |= FIELD_PREP(GENMASK(31, 24), value);
break;
}
return 0;
}
static void ppe_tbl_pkt_cnt_clear(struct ppe_device *ppe_dev, u32 reg,
enum ppe_cnt_size_type cnt_type)
{
u32 drop_pkt_cnt[PPE_DROP_PKT_CNT_TBL_SIZE] = {};
u32 pkt_cnt[PPE_PKT_CNT_TBL_SIZE] = {};
switch (cnt_type) {
case PPE_PKT_CNT_SIZE_1WORD:
regmap_write(ppe_dev->regmap, reg, 0);
break;
case PPE_PKT_CNT_SIZE_3WORD:
regmap_bulk_write(ppe_dev->regmap, reg,
pkt_cnt, ARRAY_SIZE(pkt_cnt));
break;
case PPE_PKT_CNT_SIZE_5WORD:
regmap_bulk_write(ppe_dev->regmap, reg,
drop_pkt_cnt, ARRAY_SIZE(drop_pkt_cnt));
break;
}
}
static int ppe_bm_counter_get(struct ppe_device *ppe_dev, struct seq_file *seq)
{
u32 reg, val, pkt_cnt, pkt_cnt1;
int ret, i, tag;
seq_printf(seq, "%-24s", "BM SILENT_DROP:");
tag = 0;
for (i = 0; i < PPE_DROP_CNT_TBL_ENTRIES; i++) {
reg = PPE_DROP_CNT_TBL_ADDR + i * PPE_DROP_CNT_TBL_INC;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD,
&pkt_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u(%s=%04d)", pkt_cnt, "port", i);
}
}
seq_putc(seq, '\n');
/* The number of packets dropped because hardware buffers were
* available only partially for the packet.
*/
seq_printf(seq, "%-24s", "BM OVERFLOW_DROP:");
tag = 0;
for (i = 0; i < PPE_DROP_STAT_TBL_ENTRIES; i++) {
reg = PPE_DROP_STAT_TBL_ADDR + PPE_DROP_STAT_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&pkt_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u(%s=%04d)", pkt_cnt, "port", i);
}
}
seq_putc(seq, '\n');
/* The number of currently occupied buffers, that can't be flushed. */
seq_printf(seq, "%-24s", "BM USED/REACT:");
tag = 0;
for (i = 0; i < PPE_BM_USED_CNT_TBL_ENTRIES; i++) {
reg = PPE_BM_USED_CNT_TBL_ADDR + i * PPE_BM_USED_CNT_TBL_INC;
ret = regmap_read(ppe_dev->regmap, reg, &val);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
/* The number of PPE buffers used for caching the received
* packets before the pause frame sent.
*/
pkt_cnt = FIELD_GET(PPE_BM_USED_CNT_VAL, val);
reg = PPE_BM_REACT_CNT_TBL_ADDR + i * PPE_BM_REACT_CNT_TBL_INC;
ret = regmap_read(ppe_dev->regmap, reg, &val);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
/* The number of PPE buffers used for caching the received
* packets after pause frame sent out.
*/
pkt_cnt1 = FIELD_GET(PPE_BM_REACT_CNT_VAL, val);
if (pkt_cnt > 0 || pkt_cnt1 > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u/%u(%s=%04d)", pkt_cnt, pkt_cnt1,
"port", i);
}
}
seq_putc(seq, '\n');
return 0;
}
/* The number of packets processed by the ingress parser module of PPE. */
static int ppe_parse_pkt_counter_get(struct ppe_device *ppe_dev,
struct seq_file *seq)
{
u32 reg, cnt = 0, tunnel_cnt = 0;
int i, ret, tag = 0;
seq_printf(seq, "%-24s", "PARSE TPRX/IPRX:");
for (i = 0; i < PPE_IPR_PKT_CNT_TBL_ENTRIES; i++) {
reg = PPE_TPR_PKT_CNT_TBL_ADDR + i * PPE_TPR_PKT_CNT_TBL_INC;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD,
&tunnel_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
reg = PPE_IPR_PKT_CNT_TBL_ADDR + i * PPE_IPR_PKT_CNT_TBL_INC;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD,
&cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (tunnel_cnt > 0 || cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u/%u(%s=%04d)", tunnel_cnt, cnt,
"port", i);
}
}
seq_putc(seq, '\n');
return 0;
}
/* The number of packets received or dropped on the ingress port. */
static int ppe_port_rx_counter_get(struct ppe_device *ppe_dev,
struct seq_file *seq)
{
u32 reg, pkt_cnt = 0, drop_cnt = 0;
int ret, i, tag;
seq_printf(seq, "%-24s", "PORT RX/RX_DROP:");
tag = 0;
for (i = 0; i < PPE_PHY_PORT_RX_CNT_TBL_ENTRIES; i++) {
reg = PPE_PHY_PORT_RX_CNT_TBL_ADDR + PPE_PHY_PORT_RX_CNT_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD,
&pkt_cnt, &drop_cnt);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u/%u(%s=%04d)", pkt_cnt, drop_cnt,
"port", i);
}
}
seq_putc(seq, '\n');
seq_printf(seq, "%-24s", "VPORT RX/RX_DROP:");
tag = 0;
for (i = 0; i < PPE_PORT_RX_CNT_TBL_ENTRIES; i++) {
reg = PPE_PORT_RX_CNT_TBL_ADDR + PPE_PORT_RX_CNT_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD,
&pkt_cnt, &drop_cnt);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u/%u(%s=%04d)", pkt_cnt, drop_cnt,
"port", i);
}
}
seq_putc(seq, '\n');
return 0;
}
/* The number of packets received or dropped by layer 2 processing. */
static int ppe_l2_counter_get(struct ppe_device *ppe_dev,
struct seq_file *seq)
{
u32 reg, pkt_cnt = 0, drop_cnt = 0;
int ret, i, tag = 0;
seq_printf(seq, "%-24s", "L2 RX/RX_DROP:");
for (i = 0; i < PPE_PRE_L2_CNT_TBL_ENTRIES; i++) {
reg = PPE_PRE_L2_CNT_TBL_ADDR + PPE_PRE_L2_CNT_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD,
&pkt_cnt, &drop_cnt);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u/%u(%s=%04d)", pkt_cnt, drop_cnt,
"vsi", i);
}
}
seq_putc(seq, '\n');
return 0;
}
/* The number of VLAN packets received by PPE. */
static int ppe_vlan_rx_counter_get(struct ppe_device *ppe_dev,
struct seq_file *seq)
{
u32 reg, pkt_cnt = 0;
int ret, i, tag = 0;
seq_printf(seq, "%-24s", "VLAN RX:");
for (i = 0; i < PPE_VLAN_CNT_TBL_ENTRIES; i++) {
reg = PPE_VLAN_CNT_TBL_ADDR + PPE_VLAN_CNT_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&pkt_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u(%s=%04d)", pkt_cnt, "vsi", i);
}
}
seq_putc(seq, '\n');
return 0;
}
/* The number of packets handed to CPU by PPE. */
static int ppe_cpu_code_counter_get(struct ppe_device *ppe_dev,
struct seq_file *seq)
{
u32 reg, pkt_cnt = 0;
int ret, i;
seq_printf(seq, "%-24s", "CPU CODE:");
for (i = 0; i < PPE_DROP_CPU_CNT_TBL_ENTRIES; i++) {
reg = PPE_DROP_CPU_CNT_TBL_ADDR + PPE_DROP_CPU_CNT_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&pkt_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (!pkt_cnt)
continue;
/* There are 256 CPU codes saved in the first 256 entries
* of register table, and 128 drop codes for each PPE port
* (0-7), the total entries is 256 + 8 * 128.
*/
if (i < 256)
seq_printf(seq, "%10u(cpucode:%d)", pkt_cnt, i);
else
seq_printf(seq, "%10u(port=%04d),dropcode:%d", pkt_cnt,
(i - 256) % 8, (i - 256) / 8);
seq_putc(seq, '\n');
seq_printf(seq, "%-24s", "");
}
seq_putc(seq, '\n');
return 0;
}
/* The number of packets forwarded by VLAN on the egress direction. */
static int ppe_vlan_tx_counter_get(struct ppe_device *ppe_dev,
struct seq_file *seq)
{
u32 reg, pkt_cnt = 0;
int ret, i, tag = 0;
seq_printf(seq, "%-24s", "VLAN TX:");
for (i = 0; i < PPE_EG_VSI_COUNTER_TBL_ENTRIES; i++) {
reg = PPE_EG_VSI_COUNTER_TBL_ADDR + PPE_EG_VSI_COUNTER_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&pkt_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u(%s=%04d)", pkt_cnt, "vsi", i);
}
}
seq_putc(seq, '\n');
return 0;
}
/* The number of packets transmitted or dropped on the egress port. */
static int ppe_port_tx_counter_get(struct ppe_device *ppe_dev,
struct seq_file *seq)
{
u32 reg, pkt_cnt = 0, drop_cnt = 0;
int ret, i, tag;
seq_printf(seq, "%-24s", "VPORT TX/TX_DROP:");
tag = 0;
for (i = 0; i < PPE_VPORT_TX_COUNTER_TBL_ENTRIES; i++) {
reg = PPE_VPORT_TX_COUNTER_TBL_ADDR + PPE_VPORT_TX_COUNTER_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&pkt_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
reg = PPE_VPORT_TX_DROP_CNT_TBL_ADDR + PPE_VPORT_TX_DROP_CNT_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&drop_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0 || drop_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u/%u(%s=%04d)", pkt_cnt, drop_cnt,
"port", i);
}
}
seq_putc(seq, '\n');
seq_printf(seq, "%-24s", "PORT TX/TX_DROP:");
tag = 0;
for (i = 0; i < PPE_PORT_TX_COUNTER_TBL_ENTRIES; i++) {
reg = PPE_PORT_TX_COUNTER_TBL_ADDR + PPE_PORT_TX_COUNTER_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&pkt_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
reg = PPE_PORT_TX_DROP_CNT_TBL_ADDR + PPE_PORT_TX_DROP_CNT_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&drop_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (pkt_cnt > 0 || drop_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u/%u(%s=%04d)", pkt_cnt, drop_cnt,
"port", i);
}
}
seq_putc(seq, '\n');
return 0;
}
/* The number of packets transmitted or pending by the PPE queue. */
static int ppe_queue_counter_get(struct ppe_device *ppe_dev,
struct seq_file *seq)
{
u32 reg, val, pkt_cnt = 0, pend_cnt = 0, drop_cnt = 0;
int ret, i, tag = 0;
seq_printf(seq, "%-24s", "QUEUE TX/PEND/DROP:");
for (i = 0; i < PPE_QUEUE_TX_COUNTER_TBL_ENTRIES; i++) {
reg = PPE_QUEUE_TX_COUNTER_TBL_ADDR + PPE_QUEUE_TX_COUNTER_TBL_INC * i;
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&pkt_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
if (i < PPE_AC_UNICAST_QUEUE_CFG_TBL_ENTRIES) {
reg = PPE_AC_UNICAST_QUEUE_CNT_TBL_ADDR +
PPE_AC_UNICAST_QUEUE_CNT_TBL_INC * i;
ret = regmap_read(ppe_dev->regmap, reg, &val);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
pend_cnt = FIELD_GET(PPE_AC_UNICAST_QUEUE_CNT_TBL_PEND_CNT, val);
reg = PPE_UNICAST_DROP_CNT_TBL_ADDR +
PPE_AC_UNICAST_QUEUE_CNT_TBL_INC *
(i * PPE_UNICAST_DROP_TYPES + PPE_UNICAST_DROP_FORCE_OFFSET);
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&drop_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
} else {
int mq_offset = i - PPE_AC_UNICAST_QUEUE_CFG_TBL_ENTRIES;
reg = PPE_AC_MULTICAST_QUEUE_CNT_TBL_ADDR +
PPE_AC_MULTICAST_QUEUE_CNT_TBL_INC * mq_offset;
ret = regmap_read(ppe_dev->regmap, reg, &val);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
pend_cnt = FIELD_GET(PPE_AC_MULTICAST_QUEUE_CNT_TBL_PEND_CNT, val);
if (mq_offset < PPE_P0_MULTICAST_QUEUE_NUM) {
reg = PPE_CPU_PORT_MULTICAST_FORCE_DROP_CNT_TBL_ADDR(mq_offset);
} else {
mq_offset -= PPE_P0_MULTICAST_QUEUE_NUM;
reg = PPE_P1_MULTICAST_DROP_CNT_TBL_ADDR;
reg += (mq_offset / PPE_MULTICAST_QUEUE_NUM) *
PPE_MULTICAST_QUEUE_PORT_ADDR_INC;
reg += (mq_offset % PPE_MULTICAST_QUEUE_NUM) *
PPE_MULTICAST_DROP_CNT_TBL_INC *
PPE_MULTICAST_DROP_TYPES;
}
ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
&drop_cnt, NULL);
if (ret) {
dev_err(ppe_dev->dev, "CNT ERROR %d\n", ret);
return ret;
}
}
if (pkt_cnt > 0 || pend_cnt > 0 || drop_cnt > 0) {
if (!((++tag) % 4))
seq_printf(seq, "\n%-24s", "");
seq_printf(seq, "%10u/%u/%u(%s=%04d)",
pkt_cnt, pend_cnt, drop_cnt, "queue", i);
}
}
seq_putc(seq, '\n');
return 0;
}
/* Display the various packet counters of PPE. */
static int ppe_packet_counter_show(struct seq_file *seq, void *v)
{
struct ppe_debugfs_entry *entry = seq->private;
struct ppe_device *ppe_dev = entry->ppe;
int ret;
switch (entry->counter_type) {
case PPE_CNT_BM:
ret = ppe_bm_counter_get(ppe_dev, seq);
break;
case PPE_CNT_PARSE:
ret = ppe_parse_pkt_counter_get(ppe_dev, seq);
break;
case PPE_CNT_PORT_RX:
ret = ppe_port_rx_counter_get(ppe_dev, seq);
break;
case PPE_CNT_VLAN_RX:
ret = ppe_vlan_rx_counter_get(ppe_dev, seq);
break;
case PPE_CNT_L2_FWD:
ret = ppe_l2_counter_get(ppe_dev, seq);
break;
case PPE_CNT_CPU_CODE:
ret = ppe_cpu_code_counter_get(ppe_dev, seq);
break;
case PPE_CNT_VLAN_TX:
ret = ppe_vlan_tx_counter_get(ppe_dev, seq);
break;
case PPE_CNT_PORT_TX:
ret = ppe_port_tx_counter_get(ppe_dev, seq);
break;
case PPE_CNT_QM:
ret = ppe_queue_counter_get(ppe_dev, seq);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
/* Flush the various packet counters of PPE. */
static ssize_t ppe_packet_counter_write(struct file *file,
const char __user *buf,
size_t count, loff_t *pos)
{
struct ppe_debugfs_entry *entry = file_inode(file)->i_private;
struct ppe_device *ppe_dev = entry->ppe;
u32 reg;
int i;
switch (entry->counter_type) {
case PPE_CNT_BM:
for (i = 0; i < PPE_DROP_CNT_TBL_ENTRIES; i++) {
reg = PPE_DROP_CNT_TBL_ADDR + i * PPE_DROP_CNT_TBL_INC;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD);
}
for (i = 0; i < PPE_DROP_STAT_TBL_ENTRIES; i++) {
reg = PPE_DROP_STAT_TBL_ADDR + PPE_DROP_STAT_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
}
break;
case PPE_CNT_PARSE:
for (i = 0; i < PPE_IPR_PKT_CNT_TBL_ENTRIES; i++) {
reg = PPE_IPR_PKT_CNT_TBL_ADDR + i * PPE_IPR_PKT_CNT_TBL_INC;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD);
reg = PPE_TPR_PKT_CNT_TBL_ADDR + i * PPE_TPR_PKT_CNT_TBL_INC;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD);
}
break;
case PPE_CNT_PORT_RX:
for (i = 0; i < PPE_PORT_RX_CNT_TBL_ENTRIES; i++) {
reg = PPE_PORT_RX_CNT_TBL_ADDR + PPE_PORT_RX_CNT_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD);
}
for (i = 0; i < PPE_PHY_PORT_RX_CNT_TBL_ENTRIES; i++) {
reg = PPE_PHY_PORT_RX_CNT_TBL_ADDR + PPE_PHY_PORT_RX_CNT_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD);
}
break;
case PPE_CNT_VLAN_RX:
for (i = 0; i < PPE_VLAN_CNT_TBL_ENTRIES; i++) {
reg = PPE_VLAN_CNT_TBL_ADDR + PPE_VLAN_CNT_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
}
break;
case PPE_CNT_L2_FWD:
for (i = 0; i < PPE_PRE_L2_CNT_TBL_ENTRIES; i++) {
reg = PPE_PRE_L2_CNT_TBL_ADDR + PPE_PRE_L2_CNT_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD);
}
break;
case PPE_CNT_CPU_CODE:
for (i = 0; i < PPE_DROP_CPU_CNT_TBL_ENTRIES; i++) {
reg = PPE_DROP_CPU_CNT_TBL_ADDR + PPE_DROP_CPU_CNT_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
}
break;
case PPE_CNT_VLAN_TX:
for (i = 0; i < PPE_EG_VSI_COUNTER_TBL_ENTRIES; i++) {
reg = PPE_EG_VSI_COUNTER_TBL_ADDR + PPE_EG_VSI_COUNTER_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
}
break;
case PPE_CNT_PORT_TX:
for (i = 0; i < PPE_PORT_TX_COUNTER_TBL_ENTRIES; i++) {
reg = PPE_PORT_TX_DROP_CNT_TBL_ADDR + PPE_PORT_TX_DROP_CNT_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
reg = PPE_PORT_TX_COUNTER_TBL_ADDR + PPE_PORT_TX_COUNTER_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
}
for (i = 0; i < PPE_VPORT_TX_COUNTER_TBL_ENTRIES; i++) {
reg = PPE_VPORT_TX_COUNTER_TBL_ADDR + PPE_VPORT_TX_COUNTER_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
reg = PPE_VPORT_TX_DROP_CNT_TBL_ADDR + PPE_VPORT_TX_DROP_CNT_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
}
break;
case PPE_CNT_QM:
for (i = 0; i < PPE_QUEUE_TX_COUNTER_TBL_ENTRIES; i++) {
reg = PPE_QUEUE_TX_COUNTER_TBL_ADDR + PPE_QUEUE_TX_COUNTER_TBL_INC * i;
ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
}
break;
default:
break;
}
return count;
}
DEFINE_SHOW_STORE_ATTRIBUTE(ppe_packet_counter);
void ppe_debugfs_setup(struct ppe_device *ppe_dev)
{
struct ppe_debugfs_entry *entry;
int i;
ppe_dev->debugfs_root = debugfs_create_dir("ppe", NULL);
if (IS_ERR(ppe_dev->debugfs_root))
return;
for (i = 0; i < ARRAY_SIZE(debugfs_files); i++) {
entry = devm_kzalloc(ppe_dev->dev, sizeof(*entry), GFP_KERNEL);
if (!entry)
return;
entry->ppe = ppe_dev;
entry->counter_type = debugfs_files[i].counter_type;
debugfs_create_file(debugfs_files[i].name, 0444,
ppe_dev->debugfs_root, entry,
&ppe_packet_counter_fops);
}
}
void ppe_debugfs_teardown(struct ppe_device *ppe_dev)
{
debugfs_remove_recursive(ppe_dev->debugfs_root);
ppe_dev->debugfs_root = NULL;
}
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
/* PPE debugfs counters setup. */
#ifndef __PPE_DEBUGFS_H__
#define __PPE_DEBUGFS_H__
#include "ppe.h"
void ppe_debugfs_setup(struct ppe_device *ppe_dev);
void ppe_debugfs_teardown(struct ppe_device *ppe_dev);
#endif
@@ -0,0 +1,591 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
/* PPE hardware register and table declarations. */
#ifndef __PPE_REGS_H__
#define __PPE_REGS_H__
#include <linux/bitfield.h>
/* PPE scheduler configurations for buffer manager block. */
#define PPE_BM_SCH_CTRL_ADDR 0xb000
#define PPE_BM_SCH_CTRL_INC 4
#define PPE_BM_SCH_CTRL_SCH_DEPTH GENMASK(7, 0)
#define PPE_BM_SCH_CTRL_SCH_OFFSET GENMASK(14, 8)
#define PPE_BM_SCH_CTRL_SCH_EN BIT(31)
/* PPE drop counters. */
#define PPE_DROP_CNT_TBL_ADDR 0xb024
#define PPE_DROP_CNT_TBL_ENTRIES 8
#define PPE_DROP_CNT_TBL_INC 4
/* BM port drop counters. */
#define PPE_DROP_STAT_TBL_ADDR 0xe000
#define PPE_DROP_STAT_TBL_ENTRIES 30
#define PPE_DROP_STAT_TBL_INC 0x10
/* Egress VLAN counters. */
#define PPE_EG_VSI_COUNTER_TBL_ADDR 0x41000
#define PPE_EG_VSI_COUNTER_TBL_ENTRIES 64
#define PPE_EG_VSI_COUNTER_TBL_INC 0x10
/* Port TX counters. */
#define PPE_PORT_TX_COUNTER_TBL_ADDR 0x45000
#define PPE_PORT_TX_COUNTER_TBL_ENTRIES 8
#define PPE_PORT_TX_COUNTER_TBL_INC 0x10
/* Virtual port TX counters. */
#define PPE_VPORT_TX_COUNTER_TBL_ADDR 0x47000
#define PPE_VPORT_TX_COUNTER_TBL_ENTRIES 256
#define PPE_VPORT_TX_COUNTER_TBL_INC 0x10
/* Queue counters. */
#define PPE_QUEUE_TX_COUNTER_TBL_ADDR 0x4a000
#define PPE_QUEUE_TX_COUNTER_TBL_ENTRIES 300
#define PPE_QUEUE_TX_COUNTER_TBL_INC 0x10
/* RSS settings are to calculate the random RSS hash value generated during
* packet receive to ARM cores. This hash is then used to generate the queue
* offset used to determine the queue used to transmit the packet to ARM cores.
*/
#define PPE_RSS_HASH_MASK_ADDR 0xb4318
#define PPE_RSS_HASH_MASK_HASH_MASK GENMASK(20, 0)
#define PPE_RSS_HASH_MASK_FRAGMENT BIT(28)
#define PPE_RSS_HASH_SEED_ADDR 0xb431c
#define PPE_RSS_HASH_SEED_VAL GENMASK(31, 0)
#define PPE_RSS_HASH_MIX_ADDR 0xb4320
#define PPE_RSS_HASH_MIX_ENTRIES 11
#define PPE_RSS_HASH_MIX_INC 4
#define PPE_RSS_HASH_MIX_VAL GENMASK(4, 0)
#define PPE_RSS_HASH_FIN_ADDR 0xb4350
#define PPE_RSS_HASH_FIN_ENTRIES 5
#define PPE_RSS_HASH_FIN_INC 4
#define PPE_RSS_HASH_FIN_INNER GENMASK(4, 0)
#define PPE_RSS_HASH_FIN_OUTER GENMASK(9, 5)
#define PPE_RSS_HASH_MASK_IPV4_ADDR 0xb4380
#define PPE_RSS_HASH_MASK_IPV4_HASH_MASK GENMASK(20, 0)
#define PPE_RSS_HASH_MASK_IPV4_FRAGMENT BIT(28)
#define PPE_RSS_HASH_SEED_IPV4_ADDR 0xb4384
#define PPE_RSS_HASH_SEED_IPV4_VAL GENMASK(31, 0)
#define PPE_RSS_HASH_MIX_IPV4_ADDR 0xb4390
#define PPE_RSS_HASH_MIX_IPV4_ENTRIES 5
#define PPE_RSS_HASH_MIX_IPV4_INC 4
#define PPE_RSS_HASH_MIX_IPV4_VAL GENMASK(4, 0)
#define PPE_RSS_HASH_FIN_IPV4_ADDR 0xb43b0
#define PPE_RSS_HASH_FIN_IPV4_ENTRIES 5
#define PPE_RSS_HASH_FIN_IPV4_INC 4
#define PPE_RSS_HASH_FIN_IPV4_INNER GENMASK(4, 0)
#define PPE_RSS_HASH_FIN_IPV4_OUTER GENMASK(9, 5)
#define PPE_BM_SCH_CFG_TBL_ADDR 0xc000
#define PPE_BM_SCH_CFG_TBL_ENTRIES 128
#define PPE_BM_SCH_CFG_TBL_INC 0x10
#define PPE_BM_SCH_CFG_TBL_PORT_NUM GENMASK(3, 0)
#define PPE_BM_SCH_CFG_TBL_DIR BIT(4)
#define PPE_BM_SCH_CFG_TBL_VALID BIT(5)
#define PPE_BM_SCH_CFG_TBL_SECOND_PORT_VALID BIT(6)
#define PPE_BM_SCH_CFG_TBL_SECOND_PORT GENMASK(11, 8)
/* PPE service code configuration for the ingress direction functions,
* including bypass configuration for relevant PPE switch core functions
* such as flow entry lookup bypass.
*/
#define PPE_SERVICE_TBL_ADDR 0x15000
#define PPE_SERVICE_TBL_ENTRIES 256
#define PPE_SERVICE_TBL_INC 0x10
#define PPE_SERVICE_W0_BYPASS_BITMAP GENMASK(31, 0)
#define PPE_SERVICE_W1_RX_CNT_EN BIT(0)
#define PPE_SERVICE_SET_BYPASS_BITMAP(tbl_cfg, value) \
FIELD_MODIFY(PPE_SERVICE_W0_BYPASS_BITMAP, tbl_cfg, value)
#define PPE_SERVICE_SET_RX_CNT_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_SERVICE_W1_RX_CNT_EN, (tbl_cfg) + 0x1, value)
/* PPE port egress VLAN configurations. */
#define PPE_PORT_EG_VLAN_TBL_ADDR 0x20020
#define PPE_PORT_EG_VLAN_TBL_ENTRIES 8
#define PPE_PORT_EG_VLAN_TBL_INC 4
#define PPE_PORT_EG_VLAN_TBL_VLAN_TYPE BIT(0)
#define PPE_PORT_EG_VLAN_TBL_CTAG_MODE GENMASK(2, 1)
#define PPE_PORT_EG_VLAN_TBL_STAG_MODE GENMASK(4, 3)
#define PPE_PORT_EG_VLAN_TBL_VSI_TAG_MODE_EN BIT(5)
#define PPE_PORT_EG_VLAN_TBL_PCP_PROP_CMD BIT(6)
#define PPE_PORT_EG_VLAN_TBL_DEI_PROP_CMD BIT(7)
#define PPE_PORT_EG_VLAN_TBL_TX_COUNTING_EN BIT(8)
/* PPE queue counters enable/disable control. */
#define PPE_EG_BRIDGE_CONFIG_ADDR 0x20044
#define PPE_EG_BRIDGE_CONFIG_QUEUE_CNT_EN BIT(2)
/* PPE service code configuration on the egress direction. */
#define PPE_EG_SERVICE_TBL_ADDR 0x43000
#define PPE_EG_SERVICE_TBL_ENTRIES 256
#define PPE_EG_SERVICE_TBL_INC 0x10
#define PPE_EG_SERVICE_W0_UPDATE_ACTION GENMASK(31, 0)
#define PPE_EG_SERVICE_W1_NEXT_SERVCODE GENMASK(7, 0)
#define PPE_EG_SERVICE_W1_HW_SERVICE GENMASK(13, 8)
#define PPE_EG_SERVICE_W1_OFFSET_SEL BIT(14)
#define PPE_EG_SERVICE_W1_TX_CNT_EN BIT(15)
#define PPE_EG_SERVICE_SET_UPDATE_ACTION(tbl_cfg, value) \
FIELD_MODIFY(PPE_EG_SERVICE_W0_UPDATE_ACTION, tbl_cfg, value)
#define PPE_EG_SERVICE_SET_NEXT_SERVCODE(tbl_cfg, value) \
FIELD_MODIFY(PPE_EG_SERVICE_W1_NEXT_SERVCODE, (tbl_cfg) + 0x1, value)
#define PPE_EG_SERVICE_SET_HW_SERVICE(tbl_cfg, value) \
FIELD_MODIFY(PPE_EG_SERVICE_W1_HW_SERVICE, (tbl_cfg) + 0x1, value)
#define PPE_EG_SERVICE_SET_OFFSET_SEL(tbl_cfg, value) \
FIELD_MODIFY(PPE_EG_SERVICE_W1_OFFSET_SEL, (tbl_cfg) + 0x1, value)
#define PPE_EG_SERVICE_SET_TX_CNT_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_EG_SERVICE_W1_TX_CNT_EN, (tbl_cfg) + 0x1, value)
/* PPE port bridge configuration */
#define PPE_PORT_BRIDGE_CTRL_ADDR 0x60300
#define PPE_PORT_BRIDGE_CTRL_ENTRIES 8
#define PPE_PORT_BRIDGE_CTRL_INC 4
#define PPE_PORT_BRIDGE_NEW_LRN_EN BIT(0)
#define PPE_PORT_BRIDGE_STA_MOVE_LRN_EN BIT(3)
#define PPE_PORT_BRIDGE_TXMAC_EN BIT(16)
/* PPE port control configurations for the traffic to the multicast queues. */
#define PPE_MC_MTU_CTRL_TBL_ADDR 0x60a00
#define PPE_MC_MTU_CTRL_TBL_ENTRIES 8
#define PPE_MC_MTU_CTRL_TBL_INC 4
#define PPE_MC_MTU_CTRL_TBL_MTU GENMASK(13, 0)
#define PPE_MC_MTU_CTRL_TBL_MTU_CMD GENMASK(15, 14)
#define PPE_MC_MTU_CTRL_TBL_TX_CNT_EN BIT(16)
/* PPE VSI configurations */
#define PPE_VSI_TBL_ADDR 0x63800
#define PPE_VSI_TBL_ENTRIES 64
#define PPE_VSI_TBL_INC 0x10
#define PPE_VSI_W0_MEMBER_PORT_BITMAP GENMASK(7, 0)
#define PPE_VSI_W0_UUC_BITMAP GENMASK(15, 8)
#define PPE_VSI_W0_UMC_BITMAP GENMASK(23, 16)
#define PPE_VSI_W0_BC_BITMAP GENMASK(31, 24)
#define PPE_VSI_W1_NEW_ADDR_LRN_EN BIT(0)
#define PPE_VSI_W1_NEW_ADDR_FWD_CMD GENMASK(2, 1)
#define PPE_VSI_W1_STATION_MOVE_LRN_EN BIT(3)
#define PPE_VSI_W1_STATION_MOVE_FWD_CMD GENMASK(5, 4)
#define PPE_VSI_SET_MEMBER_PORT_BITMAP(tbl_cfg, value) \
FIELD_MODIFY(PPE_VSI_W0_MEMBER_PORT_BITMAP, tbl_cfg, value)
#define PPE_VSI_SET_UUC_BITMAP(tbl_cfg, value) \
FIELD_MODIFY(PPE_VSI_W0_UUC_BITMAP, tbl_cfg, value)
#define PPE_VSI_SET_UMC_BITMAP(tbl_cfg, value) \
FIELD_MODIFY(PPE_VSI_W0_UMC_BITMAP, tbl_cfg, value)
#define PPE_VSI_SET_BC_BITMAP(tbl_cfg, value) \
FIELD_MODIFY(PPE_VSI_W0_BC_BITMAP, tbl_cfg, value)
#define PPE_VSI_SET_NEW_ADDR_LRN_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_VSI_W1_NEW_ADDR_LRN_EN, (tbl_cfg) + 0x1, value)
#define PPE_VSI_SET_NEW_ADDR_FWD_CMD(tbl_cfg, value) \
FIELD_MODIFY(PPE_VSI_W1_NEW_ADDR_FWD_CMD, (tbl_cfg) + 0x1, value)
#define PPE_VSI_SET_STATION_MOVE_LRN_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_VSI_W1_STATION_MOVE_LRN_EN, (tbl_cfg) + 0x1, value)
#define PPE_VSI_SET_STATION_MOVE_FWD_CMD(tbl_cfg, value) \
FIELD_MODIFY(PPE_VSI_W1_STATION_MOVE_FWD_CMD, (tbl_cfg) + 0x1, value)
/* PPE port control configurations for the traffic to the unicast queues. */
#define PPE_MRU_MTU_CTRL_TBL_ADDR 0x65000
#define PPE_MRU_MTU_CTRL_TBL_ENTRIES 256
#define PPE_MRU_MTU_CTRL_TBL_INC 0x10
#define PPE_MRU_MTU_CTRL_W0_MRU GENMASK(13, 0)
#define PPE_MRU_MTU_CTRL_W0_MRU_CMD GENMASK(15, 14)
#define PPE_MRU_MTU_CTRL_W0_MTU GENMASK(29, 16)
#define PPE_MRU_MTU_CTRL_W0_MTU_CMD GENMASK(31, 30)
#define PPE_MRU_MTU_CTRL_W1_RX_CNT_EN BIT(0)
#define PPE_MRU_MTU_CTRL_W1_TX_CNT_EN BIT(1)
#define PPE_MRU_MTU_CTRL_W1_SRC_PROFILE GENMASK(3, 2)
#define PPE_MRU_MTU_CTRL_W1_INNER_PREC_LOW BIT(31)
#define PPE_MRU_MTU_CTRL_W2_INNER_PREC_HIGH GENMASK(1, 0)
#define PPE_MRU_MTU_CTRL_SET_MRU(tbl_cfg, value) \
FIELD_MODIFY(PPE_MRU_MTU_CTRL_W0_MRU, tbl_cfg, value)
#define PPE_MRU_MTU_CTRL_SET_MRU_CMD(tbl_cfg, value) \
FIELD_MODIFY(PPE_MRU_MTU_CTRL_W0_MRU_CMD, tbl_cfg, value)
#define PPE_MRU_MTU_CTRL_SET_MTU(tbl_cfg, value) \
FIELD_MODIFY(PPE_MRU_MTU_CTRL_W0_MTU, tbl_cfg, value)
#define PPE_MRU_MTU_CTRL_SET_MTU_CMD(tbl_cfg, value) \
FIELD_MODIFY(PPE_MRU_MTU_CTRL_W0_MTU_CMD, tbl_cfg, value)
#define PPE_MRU_MTU_CTRL_SET_RX_CNT_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_MRU_MTU_CTRL_W1_RX_CNT_EN, (tbl_cfg) + 0x1, value)
#define PPE_MRU_MTU_CTRL_SET_TX_CNT_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_MRU_MTU_CTRL_W1_TX_CNT_EN, (tbl_cfg) + 0x1, value)
/* PPE service code configuration for destination port and counter. */
#define PPE_IN_L2_SERVICE_TBL_ADDR 0x66000
#define PPE_IN_L2_SERVICE_TBL_ENTRIES 256
#define PPE_IN_L2_SERVICE_TBL_INC 0x10
#define PPE_IN_L2_SERVICE_TBL_DST_PORT_ID_VALID BIT(0)
#define PPE_IN_L2_SERVICE_TBL_DST_PORT_ID GENMASK(4, 1)
#define PPE_IN_L2_SERVICE_TBL_DST_DIRECTION BIT(5)
#define PPE_IN_L2_SERVICE_TBL_DST_BYPASS_BITMAP GENMASK(29, 6)
#define PPE_IN_L2_SERVICE_TBL_RX_CNT_EN BIT(30)
#define PPE_IN_L2_SERVICE_TBL_TX_CNT_EN BIT(31)
/* L2 Port configurations */
#define PPE_L2_VP_PORT_TBL_ADDR 0x98000
#define PPE_L2_VP_PORT_TBL_ENTRIES 256
#define PPE_L2_VP_PORT_TBL_INC 0x10
#define PPE_L2_VP_PORT_W0_INVALID_VSI_FWD_EN BIT(0)
#define PPE_L2_VP_PORT_W0_DST_INFO GENMASK(9, 2)
#define PPE_L2_PORT_SET_INVALID_VSI_FWD_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_L2_VP_PORT_W0_INVALID_VSI_FWD_EN, tbl_cfg, value)
#define PPE_L2_PORT_SET_DST_INFO(tbl_cfg, value) \
FIELD_MODIFY(PPE_L2_VP_PORT_W0_DST_INFO, tbl_cfg, value)
/* Port RX and RX drop counters. */
#define PPE_PORT_RX_CNT_TBL_ADDR 0x150000
#define PPE_PORT_RX_CNT_TBL_ENTRIES 256
#define PPE_PORT_RX_CNT_TBL_INC 0x20
/* Physical port RX and RX drop counters. */
#define PPE_PHY_PORT_RX_CNT_TBL_ADDR 0x156000
#define PPE_PHY_PORT_RX_CNT_TBL_ENTRIES 8
#define PPE_PHY_PORT_RX_CNT_TBL_INC 0x20
/* Counters for the packet to CPU port. */
#define PPE_DROP_CPU_CNT_TBL_ADDR 0x160000
#define PPE_DROP_CPU_CNT_TBL_ENTRIES 1280
#define PPE_DROP_CPU_CNT_TBL_INC 0x10
/* VLAN counters. */
#define PPE_VLAN_CNT_TBL_ADDR 0x178000
#define PPE_VLAN_CNT_TBL_ENTRIES 64
#define PPE_VLAN_CNT_TBL_INC 0x10
/* PPE L2 counters. */
#define PPE_PRE_L2_CNT_TBL_ADDR 0x17c000
#define PPE_PRE_L2_CNT_TBL_ENTRIES 64
#define PPE_PRE_L2_CNT_TBL_INC 0x20
/* Port TX drop counters. */
#define PPE_PORT_TX_DROP_CNT_TBL_ADDR 0x17d000
#define PPE_PORT_TX_DROP_CNT_TBL_ENTRIES 8
#define PPE_PORT_TX_DROP_CNT_TBL_INC 0x10
/* Virtual port TX counters. */
#define PPE_VPORT_TX_DROP_CNT_TBL_ADDR 0x17e000
#define PPE_VPORT_TX_DROP_CNT_TBL_ENTRIES 256
#define PPE_VPORT_TX_DROP_CNT_TBL_INC 0x10
/* Counters for the tunnel packet. */
#define PPE_TPR_PKT_CNT_TBL_ADDR 0x1d0080
#define PPE_TPR_PKT_CNT_TBL_ENTRIES 8
#define PPE_TPR_PKT_CNT_TBL_INC 4
/* Counters for the all packet received. */
#define PPE_IPR_PKT_CNT_TBL_ADDR 0x1e0080
#define PPE_IPR_PKT_CNT_TBL_ENTRIES 8
#define PPE_IPR_PKT_CNT_TBL_INC 4
/* PPE service code configuration for the tunnel packet. */
#define PPE_TL_SERVICE_TBL_ADDR 0x306000
#define PPE_TL_SERVICE_TBL_ENTRIES 256
#define PPE_TL_SERVICE_TBL_INC 4
#define PPE_TL_SERVICE_TBL_BYPASS_BITMAP GENMASK(31, 0)
/* Port scheduler global config. */
#define PPE_PSCH_SCH_DEPTH_CFG_ADDR 0x400000
#define PPE_PSCH_SCH_DEPTH_CFG_INC 4
#define PPE_PSCH_SCH_DEPTH_CFG_SCH_DEPTH GENMASK(7, 0)
/* PPE queue level scheduler configurations. */
#define PPE_L0_FLOW_MAP_TBL_ADDR 0x402000
#define PPE_L0_FLOW_MAP_TBL_ENTRIES 300
#define PPE_L0_FLOW_MAP_TBL_INC 0x10
#define PPE_L0_FLOW_MAP_TBL_FLOW_ID GENMASK(5, 0)
#define PPE_L0_FLOW_MAP_TBL_C_PRI GENMASK(8, 6)
#define PPE_L0_FLOW_MAP_TBL_E_PRI GENMASK(11, 9)
#define PPE_L0_FLOW_MAP_TBL_C_NODE_WT GENMASK(21, 12)
#define PPE_L0_FLOW_MAP_TBL_E_NODE_WT GENMASK(31, 22)
#define PPE_L0_C_FLOW_CFG_TBL_ADDR 0x404000
#define PPE_L0_C_FLOW_CFG_TBL_ENTRIES 512
#define PPE_L0_C_FLOW_CFG_TBL_INC 0x10
#define PPE_L0_C_FLOW_CFG_TBL_NODE_ID GENMASK(7, 0)
#define PPE_L0_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT BIT(8)
#define PPE_L0_E_FLOW_CFG_TBL_ADDR 0x406000
#define PPE_L0_E_FLOW_CFG_TBL_ENTRIES 512
#define PPE_L0_E_FLOW_CFG_TBL_INC 0x10
#define PPE_L0_E_FLOW_CFG_TBL_NODE_ID GENMASK(7, 0)
#define PPE_L0_E_FLOW_CFG_TBL_NODE_CREDIT_UNIT BIT(8)
#define PPE_L0_FLOW_PORT_MAP_TBL_ADDR 0x408000
#define PPE_L0_FLOW_PORT_MAP_TBL_ENTRIES 300
#define PPE_L0_FLOW_PORT_MAP_TBL_INC 0x10
#define PPE_L0_FLOW_PORT_MAP_TBL_PORT_NUM GENMASK(3, 0)
#define PPE_L0_COMP_CFG_TBL_ADDR 0x428000
#define PPE_L0_COMP_CFG_TBL_ENTRIES 300
#define PPE_L0_COMP_CFG_TBL_INC 0x10
#define PPE_L0_COMP_CFG_TBL_SHAPER_METER_LEN GENMASK(1, 0)
#define PPE_L0_COMP_CFG_TBL_NODE_METER_LEN GENMASK(3, 2)
/* PPE queue to Ethernet DMA ring mapping table. */
#define PPE_RING_Q_MAP_TBL_ADDR 0x42a000
#define PPE_RING_Q_MAP_TBL_ENTRIES 24
#define PPE_RING_Q_MAP_TBL_INC 0x40
/* Table addresses for per-queue dequeue setting. */
#define PPE_DEQ_OPR_TBL_ADDR 0x430000
#define PPE_DEQ_OPR_TBL_ENTRIES 300
#define PPE_DEQ_OPR_TBL_INC 0x10
#define PPE_DEQ_OPR_TBL_DEQ_DISABLE BIT(0)
/* PPE flow level scheduler configurations. */
#define PPE_L1_FLOW_MAP_TBL_ADDR 0x440000
#define PPE_L1_FLOW_MAP_TBL_ENTRIES 64
#define PPE_L1_FLOW_MAP_TBL_INC 0x10
#define PPE_L1_FLOW_MAP_TBL_FLOW_ID GENMASK(3, 0)
#define PPE_L1_FLOW_MAP_TBL_C_PRI GENMASK(6, 4)
#define PPE_L1_FLOW_MAP_TBL_E_PRI GENMASK(9, 7)
#define PPE_L1_FLOW_MAP_TBL_C_NODE_WT GENMASK(19, 10)
#define PPE_L1_FLOW_MAP_TBL_E_NODE_WT GENMASK(29, 20)
#define PPE_L1_C_FLOW_CFG_TBL_ADDR 0x442000
#define PPE_L1_C_FLOW_CFG_TBL_ENTRIES 64
#define PPE_L1_C_FLOW_CFG_TBL_INC 0x10
#define PPE_L1_C_FLOW_CFG_TBL_NODE_ID GENMASK(5, 0)
#define PPE_L1_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT BIT(6)
#define PPE_L1_E_FLOW_CFG_TBL_ADDR 0x444000
#define PPE_L1_E_FLOW_CFG_TBL_ENTRIES 64
#define PPE_L1_E_FLOW_CFG_TBL_INC 0x10
#define PPE_L1_E_FLOW_CFG_TBL_NODE_ID GENMASK(5, 0)
#define PPE_L1_E_FLOW_CFG_TBL_NODE_CREDIT_UNIT BIT(6)
#define PPE_L1_FLOW_PORT_MAP_TBL_ADDR 0x446000
#define PPE_L1_FLOW_PORT_MAP_TBL_ENTRIES 64
#define PPE_L1_FLOW_PORT_MAP_TBL_INC 0x10
#define PPE_L1_FLOW_PORT_MAP_TBL_PORT_NUM GENMASK(3, 0)
#define PPE_L1_COMP_CFG_TBL_ADDR 0x46a000
#define PPE_L1_COMP_CFG_TBL_ENTRIES 64
#define PPE_L1_COMP_CFG_TBL_INC 0x10
#define PPE_L1_COMP_CFG_TBL_SHAPER_METER_LEN GENMASK(1, 0)
#define PPE_L1_COMP_CFG_TBL_NODE_METER_LEN GENMASK(3, 2)
/* PPE port scheduler configurations for egress. */
#define PPE_PSCH_SCH_CFG_TBL_ADDR 0x47a000
#define PPE_PSCH_SCH_CFG_TBL_ENTRIES 128
#define PPE_PSCH_SCH_CFG_TBL_INC 0x10
#define PPE_PSCH_SCH_CFG_TBL_DES_PORT GENMASK(3, 0)
#define PPE_PSCH_SCH_CFG_TBL_ENS_PORT GENMASK(7, 4)
#define PPE_PSCH_SCH_CFG_TBL_ENS_PORT_BITMAP GENMASK(15, 8)
#define PPE_PSCH_SCH_CFG_TBL_DES_SECOND_PORT_EN BIT(16)
#define PPE_PSCH_SCH_CFG_TBL_DES_SECOND_PORT GENMASK(20, 17)
/* There are 15 BM ports and 4 BM groups supported by PPE.
* BM port (0-7) is for EDMA port 0, BM port (8-13) is for
* PPE physical port 1-6 and BM port 14 is for EIP port.
*/
#define PPE_BM_PORT_FC_MODE_ADDR 0x600100
#define PPE_BM_PORT_FC_MODE_ENTRIES 15
#define PPE_BM_PORT_FC_MODE_INC 0x4
#define PPE_BM_PORT_FC_MODE_EN BIT(0)
#define PPE_BM_PORT_GROUP_ID_ADDR 0x600180
#define PPE_BM_PORT_GROUP_ID_ENTRIES 15
#define PPE_BM_PORT_GROUP_ID_INC 0x4
#define PPE_BM_PORT_GROUP_ID_SHARED_GROUP_ID GENMASK(1, 0)
/* Counters for PPE buffers used for packets cached. */
#define PPE_BM_USED_CNT_TBL_ADDR 0x6001c0
#define PPE_BM_USED_CNT_TBL_ENTRIES 15
#define PPE_BM_USED_CNT_TBL_INC 0x4
#define PPE_BM_USED_CNT_VAL GENMASK(10, 0)
/* Counters for PPE buffers used for packets received after pause frame sent. */
#define PPE_BM_REACT_CNT_TBL_ADDR 0x600240
#define PPE_BM_REACT_CNT_TBL_ENTRIES 15
#define PPE_BM_REACT_CNT_TBL_INC 0x4
#define PPE_BM_REACT_CNT_VAL GENMASK(8, 0)
#define PPE_BM_SHARED_GROUP_CFG_ADDR 0x600290
#define PPE_BM_SHARED_GROUP_CFG_ENTRIES 4
#define PPE_BM_SHARED_GROUP_CFG_INC 0x4
#define PPE_BM_SHARED_GROUP_CFG_SHARED_LIMIT GENMASK(10, 0)
#define PPE_BM_PORT_FC_CFG_TBL_ADDR 0x601000
#define PPE_BM_PORT_FC_CFG_TBL_ENTRIES 15
#define PPE_BM_PORT_FC_CFG_TBL_INC 0x10
#define PPE_BM_PORT_FC_W0_REACT_LIMIT GENMASK(8, 0)
#define PPE_BM_PORT_FC_W0_RESUME_THRESHOLD GENMASK(17, 9)
#define PPE_BM_PORT_FC_W0_RESUME_OFFSET GENMASK(28, 18)
#define PPE_BM_PORT_FC_W0_CEILING_LOW GENMASK(31, 29)
#define PPE_BM_PORT_FC_W1_CEILING_HIGH GENMASK(7, 0)
#define PPE_BM_PORT_FC_W1_WEIGHT GENMASK(10, 8)
#define PPE_BM_PORT_FC_W1_DYNAMIC BIT(11)
#define PPE_BM_PORT_FC_W1_PRE_ALLOC GENMASK(22, 12)
#define PPE_BM_PORT_FC_SET_REACT_LIMIT(tbl_cfg, value) \
FIELD_MODIFY(PPE_BM_PORT_FC_W0_REACT_LIMIT, tbl_cfg, value)
#define PPE_BM_PORT_FC_SET_RESUME_THRESHOLD(tbl_cfg, value) \
FIELD_MODIFY(PPE_BM_PORT_FC_W0_RESUME_THRESHOLD, tbl_cfg, value)
#define PPE_BM_PORT_FC_SET_RESUME_OFFSET(tbl_cfg, value) \
FIELD_MODIFY(PPE_BM_PORT_FC_W0_RESUME_OFFSET, tbl_cfg, value)
#define PPE_BM_PORT_FC_SET_CEILING_LOW(tbl_cfg, value) \
FIELD_MODIFY(PPE_BM_PORT_FC_W0_CEILING_LOW, tbl_cfg, value)
#define PPE_BM_PORT_FC_SET_CEILING_HIGH(tbl_cfg, value) \
FIELD_MODIFY(PPE_BM_PORT_FC_W1_CEILING_HIGH, (tbl_cfg) + 0x1, value)
#define PPE_BM_PORT_FC_SET_WEIGHT(tbl_cfg, value) \
FIELD_MODIFY(PPE_BM_PORT_FC_W1_WEIGHT, (tbl_cfg) + 0x1, value)
#define PPE_BM_PORT_FC_SET_DYNAMIC(tbl_cfg, value) \
FIELD_MODIFY(PPE_BM_PORT_FC_W1_DYNAMIC, (tbl_cfg) + 0x1, value)
#define PPE_BM_PORT_FC_SET_PRE_ALLOC(tbl_cfg, value) \
FIELD_MODIFY(PPE_BM_PORT_FC_W1_PRE_ALLOC, (tbl_cfg) + 0x1, value)
/* The queue base configurations based on destination port,
* service code or CPU code.
*/
#define PPE_UCAST_QUEUE_MAP_TBL_ADDR 0x810000
#define PPE_UCAST_QUEUE_MAP_TBL_ENTRIES 3072
#define PPE_UCAST_QUEUE_MAP_TBL_INC 0x10
#define PPE_UCAST_QUEUE_MAP_TBL_PROFILE_ID GENMASK(3, 0)
#define PPE_UCAST_QUEUE_MAP_TBL_QUEUE_ID GENMASK(11, 4)
/* The queue offset configurations based on RSS hash value. */
#define PPE_UCAST_HASH_MAP_TBL_ADDR 0x830000
#define PPE_UCAST_HASH_MAP_TBL_ENTRIES 4096
#define PPE_UCAST_HASH_MAP_TBL_INC 0x10
#define PPE_UCAST_HASH_MAP_TBL_HASH GENMASK(7, 0)
/* The queue offset configurations based on PPE internal priority. */
#define PPE_UCAST_PRIORITY_MAP_TBL_ADDR 0x842000
#define PPE_UCAST_PRIORITY_MAP_TBL_ENTRIES 256
#define PPE_UCAST_PRIORITY_MAP_TBL_INC 0x10
#define PPE_UCAST_PRIORITY_MAP_TBL_CLASS GENMASK(3, 0)
/* PPE unicast queue (0-255) configurations. */
#define PPE_AC_UNICAST_QUEUE_CFG_TBL_ADDR 0x848000
#define PPE_AC_UNICAST_QUEUE_CFG_TBL_ENTRIES 256
#define PPE_AC_UNICAST_QUEUE_CFG_TBL_INC 0x10
#define PPE_AC_UNICAST_QUEUE_CFG_W0_EN BIT(0)
#define PPE_AC_UNICAST_QUEUE_CFG_W0_WRED_EN BIT(1)
#define PPE_AC_UNICAST_QUEUE_CFG_W0_FC_EN BIT(2)
#define PPE_AC_UNICAST_QUEUE_CFG_W0_CLR_AWARE BIT(3)
#define PPE_AC_UNICAST_QUEUE_CFG_W0_GRP_ID GENMASK(5, 4)
#define PPE_AC_UNICAST_QUEUE_CFG_W0_PRE_LIMIT GENMASK(16, 6)
#define PPE_AC_UNICAST_QUEUE_CFG_W0_DYNAMIC BIT(17)
#define PPE_AC_UNICAST_QUEUE_CFG_W0_WEIGHT GENMASK(20, 18)
#define PPE_AC_UNICAST_QUEUE_CFG_W0_THRESHOLD GENMASK(31, 21)
#define PPE_AC_UNICAST_QUEUE_CFG_W3_GRN_RESUME GENMASK(23, 13)
#define PPE_AC_UNICAST_QUEUE_SET_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_UNICAST_QUEUE_CFG_W0_EN, tbl_cfg, value)
#define PPE_AC_UNICAST_QUEUE_SET_GRP_ID(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_UNICAST_QUEUE_CFG_W0_GRP_ID, tbl_cfg, value)
#define PPE_AC_UNICAST_QUEUE_SET_PRE_LIMIT(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_UNICAST_QUEUE_CFG_W0_PRE_LIMIT, tbl_cfg, value)
#define PPE_AC_UNICAST_QUEUE_SET_DYNAMIC(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_UNICAST_QUEUE_CFG_W0_DYNAMIC, tbl_cfg, value)
#define PPE_AC_UNICAST_QUEUE_SET_WEIGHT(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_UNICAST_QUEUE_CFG_W0_WEIGHT, tbl_cfg, value)
#define PPE_AC_UNICAST_QUEUE_SET_THRESHOLD(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_UNICAST_QUEUE_CFG_W0_THRESHOLD, tbl_cfg, value)
#define PPE_AC_UNICAST_QUEUE_SET_GRN_RESUME(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_UNICAST_QUEUE_CFG_W3_GRN_RESUME, (tbl_cfg) + 0x3, value)
/* PPE multicast queue (256-299) configurations. */
#define PPE_AC_MULTICAST_QUEUE_CFG_TBL_ADDR 0x84a000
#define PPE_AC_MULTICAST_QUEUE_CFG_TBL_ENTRIES 44
#define PPE_AC_MULTICAST_QUEUE_CFG_TBL_INC 0x10
#define PPE_AC_MULTICAST_QUEUE_CFG_W0_EN BIT(0)
#define PPE_AC_MULTICAST_QUEUE_CFG_W0_FC_EN BIT(1)
#define PPE_AC_MULTICAST_QUEUE_CFG_W0_CLR_AWARE BIT(2)
#define PPE_AC_MULTICAST_QUEUE_CFG_W0_GRP_ID GENMASK(4, 3)
#define PPE_AC_MULTICAST_QUEUE_CFG_W0_PRE_LIMIT GENMASK(15, 5)
#define PPE_AC_MULTICAST_QUEUE_CFG_W0_THRESHOLD GENMASK(26, 16)
#define PPE_AC_MULTICAST_QUEUE_CFG_W2_RESUME GENMASK(17, 7)
#define PPE_AC_MULTICAST_QUEUE_SET_EN(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_MULTICAST_QUEUE_CFG_W0_EN, tbl_cfg, value)
#define PPE_AC_MULTICAST_QUEUE_SET_GRN_GRP_ID(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_MULTICAST_QUEUE_CFG_W0_GRP_ID, tbl_cfg, value)
#define PPE_AC_MULTICAST_QUEUE_SET_GRN_PRE_LIMIT(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_MULTICAST_QUEUE_CFG_W0_PRE_LIMIT, tbl_cfg, value)
#define PPE_AC_MULTICAST_QUEUE_SET_GRN_THRESHOLD(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_MULTICAST_QUEUE_CFG_W0_THRESHOLD, tbl_cfg, value)
#define PPE_AC_MULTICAST_QUEUE_SET_GRN_RESUME(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_MULTICAST_QUEUE_CFG_W2_RESUME, (tbl_cfg) + 0x2, value)
/* PPE admission control group (0-3) configurations */
#define PPE_AC_GRP_CFG_TBL_ADDR 0x84c000
#define PPE_AC_GRP_CFG_TBL_ENTRIES 0x4
#define PPE_AC_GRP_CFG_TBL_INC 0x10
#define PPE_AC_GRP_W0_AC_EN BIT(0)
#define PPE_AC_GRP_W0_AC_FC_EN BIT(1)
#define PPE_AC_GRP_W0_CLR_AWARE BIT(2)
#define PPE_AC_GRP_W0_THRESHOLD_LOW GENMASK(31, 25)
#define PPE_AC_GRP_W1_THRESHOLD_HIGH GENMASK(3, 0)
#define PPE_AC_GRP_W1_BUF_LIMIT GENMASK(14, 4)
#define PPE_AC_GRP_W2_RESUME_GRN GENMASK(15, 5)
#define PPE_AC_GRP_W2_PRE_ALLOC GENMASK(26, 16)
#define PPE_AC_GRP_SET_BUF_LIMIT(tbl_cfg, value) \
FIELD_MODIFY(PPE_AC_GRP_W1_BUF_LIMIT, (tbl_cfg) + 0x1, value)
/* Counters for packets handled by unicast queues (0-255). */
#define PPE_AC_UNICAST_QUEUE_CNT_TBL_ADDR 0x84e000
#define PPE_AC_UNICAST_QUEUE_CNT_TBL_ENTRIES 256
#define PPE_AC_UNICAST_QUEUE_CNT_TBL_INC 0x10
#define PPE_AC_UNICAST_QUEUE_CNT_TBL_PEND_CNT GENMASK(12, 0)
/* Counters for packets handled by multicast queues (256-299). */
#define PPE_AC_MULTICAST_QUEUE_CNT_TBL_ADDR 0x852000
#define PPE_AC_MULTICAST_QUEUE_CNT_TBL_ENTRIES 44
#define PPE_AC_MULTICAST_QUEUE_CNT_TBL_INC 0x10
#define PPE_AC_MULTICAST_QUEUE_CNT_TBL_PEND_CNT GENMASK(12, 0)
/* Table addresses for per-queue enqueue setting. */
#define PPE_ENQ_OPR_TBL_ADDR 0x85c000
#define PPE_ENQ_OPR_TBL_ENTRIES 300
#define PPE_ENQ_OPR_TBL_INC 0x10
#define PPE_ENQ_OPR_TBL_ENQ_DISABLE BIT(0)
/* Unicast drop count includes the possible drops with WRED for the green,
* yellow and red categories.
*/
#define PPE_UNICAST_DROP_CNT_TBL_ADDR 0x9e0000
#define PPE_UNICAST_DROP_CNT_TBL_ENTRIES 1536
#define PPE_UNICAST_DROP_CNT_TBL_INC 0x10
#define PPE_UNICAST_DROP_TYPES 6
#define PPE_UNICAST_DROP_FORCE_OFFSET 3
/* There are 16 multicast queues dedicated to CPU port 0. Multicast drop
* count includes the force drop for green, yellow and red category packets.
*/
#define PPE_P0_MULTICAST_DROP_CNT_TBL_ADDR 0x9f0000
#define PPE_P0_MULTICAST_DROP_CNT_TBL_ENTRIES 48
#define PPE_P0_MULTICAST_DROP_CNT_TBL_INC 0x10
#define PPE_P0_MULTICAST_QUEUE_NUM 16
/* Each PPE physical port has four dedicated multicast queues, providing
* a total of 12 entries per port. The multicast drop count includes forced
* drops for green, yellow, and red category packets.
*/
#define PPE_MULTICAST_QUEUE_PORT_ADDR_INC 0x1000
#define PPE_MULTICAST_DROP_CNT_TBL_INC 0x10
#define PPE_MULTICAST_DROP_TYPES 3
#define PPE_MULTICAST_QUEUE_NUM 4
#define PPE_MULTICAST_DROP_CNT_TBL_ENTRIES 12
#define PPE_CPU_PORT_MULTICAST_FORCE_DROP_CNT_TBL_ADDR(mq_offset) \
(PPE_P0_MULTICAST_DROP_CNT_TBL_ADDR + \
(mq_offset) * PPE_P0_MULTICAST_DROP_CNT_TBL_INC * \
PPE_MULTICAST_DROP_TYPES)
#define PPE_P1_MULTICAST_DROP_CNT_TBL_ADDR \
(PPE_P0_MULTICAST_DROP_CNT_TBL_ADDR + PPE_MULTICAST_QUEUE_PORT_ADDR_INC)
#endif
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
/*
* Copyright (c) 2011, 2012, Qualcomm Atheros Communications Inc.
* Copyright (c) 2014, I2SE GmbH
*/
/* This module implements the Qualcomm Atheros SPI protocol for
* kernel-based SPI device.
*/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/spi/spi.h>
#include "qca_7k.h"
void
qcaspi_spi_error(struct qcaspi *qca)
{
if (qca->sync != QCASPI_SYNC_READY)
return;
netdev_err(qca->net_dev, "spi error\n");
qca->sync = QCASPI_SYNC_UNKNOWN;
qca->stats.spi_err++;
}
int
qcaspi_read_register(struct qcaspi *qca, u16 reg, u16 *result)
{
__be16 rx_data;
__be16 tx_data;
struct spi_transfer transfer[2];
struct spi_message msg;
int ret;
memset(transfer, 0, sizeof(transfer));
spi_message_init(&msg);
tx_data = cpu_to_be16(QCA7K_SPI_READ | QCA7K_SPI_INTERNAL | reg);
*result = 0;
transfer[0].tx_buf = &tx_data;
transfer[0].len = QCASPI_CMD_LEN;
transfer[1].rx_buf = &rx_data;
transfer[1].len = QCASPI_CMD_LEN;
spi_message_add_tail(&transfer[0], &msg);
if (qca->legacy_mode) {
spi_sync(qca->spi_dev, &msg);
spi_message_init(&msg);
}
spi_message_add_tail(&transfer[1], &msg);
ret = spi_sync(qca->spi_dev, &msg);
if (!ret)
ret = msg.status;
if (ret)
qcaspi_spi_error(qca);
else
*result = be16_to_cpu(rx_data);
return ret;
}
static int
__qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value)
{
__be16 tx_data[2];
struct spi_transfer transfer[2];
struct spi_message msg;
int ret;
memset(&transfer, 0, sizeof(transfer));
spi_message_init(&msg);
tx_data[0] = cpu_to_be16(QCA7K_SPI_WRITE | QCA7K_SPI_INTERNAL | reg);
tx_data[1] = cpu_to_be16(value);
transfer[0].tx_buf = &tx_data[0];
transfer[0].len = QCASPI_CMD_LEN;
transfer[1].tx_buf = &tx_data[1];
transfer[1].len = QCASPI_CMD_LEN;
spi_message_add_tail(&transfer[0], &msg);
if (qca->legacy_mode) {
spi_sync(qca->spi_dev, &msg);
spi_message_init(&msg);
}
spi_message_add_tail(&transfer[1], &msg);
ret = spi_sync(qca->spi_dev, &msg);
if (!ret)
ret = msg.status;
if (ret)
qcaspi_spi_error(qca);
return ret;
}
int
qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value, int retry)
{
int ret, i = 0;
u16 confirmed;
do {
ret = __qcaspi_write_register(qca, reg, value);
if (ret)
return ret;
if (!retry)
return 0;
ret = qcaspi_read_register(qca, reg, &confirmed);
if (ret)
return ret;
ret = confirmed != value;
if (!ret)
return 0;
i++;
qca->stats.write_verify_failed++;
} while (i <= retry);
return ret;
}
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
/*
* Copyright (c) 2011, 2012, Qualcomm Atheros Communications Inc.
* Copyright (c) 2014, I2SE GmbH
*/
/* Qualcomm Atheros SPI register definition.
*
* This module is designed to define the Qualcomm Atheros SPI
* register placeholders.
*/
#ifndef _QCA_7K_H
#define _QCA_7K_H
#include <linux/types.h>
#include "qca_spi.h"
#define QCA7K_SPI_READ (1 << 15)
#define QCA7K_SPI_WRITE (0 << 15)
#define QCA7K_SPI_INTERNAL (1 << 14)
#define QCA7K_SPI_EXTERNAL (0 << 14)
#define QCASPI_CMD_LEN 2
#define QCASPI_HW_PKT_LEN 4
#define QCASPI_HW_BUF_LEN 0xC5B
/* SPI registers; */
#define SPI_REG_BFR_SIZE 0x0100
#define SPI_REG_WRBUF_SPC_AVA 0x0200
#define SPI_REG_RDBUF_BYTE_AVA 0x0300
#define SPI_REG_SPI_CONFIG 0x0400
#define SPI_REG_SPI_STATUS 0x0500
#define SPI_REG_INTR_CAUSE 0x0C00
#define SPI_REG_INTR_ENABLE 0x0D00
#define SPI_REG_RDBUF_WATERMARK 0x1200
#define SPI_REG_WRBUF_WATERMARK 0x1300
#define SPI_REG_SIGNATURE 0x1A00
#define SPI_REG_ACTION_CTRL 0x1B00
/* SPI_CONFIG register definition; */
#define QCASPI_SLAVE_RESET_BIT BIT(6)
/* INTR_CAUSE/ENABLE register definition. */
#define SPI_INT_WRBUF_BELOW_WM BIT(10)
#define SPI_INT_CPU_ON BIT(6)
#define SPI_INT_ADDR_ERR BIT(3)
#define SPI_INT_WRBUF_ERR BIT(2)
#define SPI_INT_RDBUF_ERR BIT(1)
#define SPI_INT_PKT_AVLBL BIT(0)
void qcaspi_spi_error(struct qcaspi *qca);
int qcaspi_read_register(struct qcaspi *qca, u16 reg, u16 *result);
int qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value, int retry);
#endif /* _QCA_7K_H */
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
/*
* Copyright (c) 2011, 2012, Atheros Communications Inc.
* Copyright (c) 2014, I2SE GmbH
*/
/* Atheros ethernet framing. Every Ethernet frame is surrounded
* by an atheros frame while transmitted over a serial channel;
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "qca_7k_common.h"
u16
qcafrm_create_header(u8 *buf, u16 length)
{
__le16 len;
if (!buf)
return 0;
len = cpu_to_le16(length);
buf[0] = 0xAA;
buf[1] = 0xAA;
buf[2] = 0xAA;
buf[3] = 0xAA;
buf[4] = len & 0xff;
buf[5] = (len >> 8) & 0xff;
buf[6] = 0;
buf[7] = 0;
return QCAFRM_HEADER_LEN;
}
EXPORT_SYMBOL_GPL(qcafrm_create_header);
u16
qcafrm_create_footer(u8 *buf)
{
if (!buf)
return 0;
buf[0] = 0x55;
buf[1] = 0x55;
return QCAFRM_FOOTER_LEN;
}
EXPORT_SYMBOL_GPL(qcafrm_create_footer);
/* Gather received bytes and try to extract a full ethernet frame by
* following a simple state machine.
*
* Return: QCAFRM_GATHER No ethernet frame fully received yet.
* QCAFRM_NOHEAD Header expected but not found.
* QCAFRM_INVLEN Atheros frame length is invalid
* QCAFRM_NOTAIL Footer expected but not found.
* > 0 Number of byte in the fully received
* Ethernet frame
*/
s32
qcafrm_fsm_decode(struct qcafrm_handle *handle, u8 *buf, u16 buf_len, u8 recv_byte)
{
s32 ret = QCAFRM_GATHER;
u16 len;
switch (handle->state) {
case QCAFRM_HW_LEN0:
case QCAFRM_HW_LEN1:
/* by default, just go to next state */
handle->state--;
if (recv_byte != 0x00) {
/* first two bytes of length must be 0 */
handle->state = handle->init;
}
break;
case QCAFRM_HW_LEN2:
case QCAFRM_HW_LEN3:
handle->state--;
break;
/* 4 bytes header pattern */
case QCAFRM_WAIT_AA1:
case QCAFRM_WAIT_AA2:
case QCAFRM_WAIT_AA3:
case QCAFRM_WAIT_AA4:
if (recv_byte != 0xAA) {
ret = QCAFRM_NOHEAD;
handle->state = handle->init;
} else {
handle->state--;
}
break;
/* 2 bytes length. */
/* Borrow offset field to hold length for now. */
case QCAFRM_WAIT_LEN_BYTE0:
handle->offset = recv_byte;
handle->state = QCAFRM_WAIT_LEN_BYTE1;
break;
case QCAFRM_WAIT_LEN_BYTE1:
handle->offset = handle->offset | (recv_byte << 8);
handle->state = QCAFRM_WAIT_RSVD_BYTE1;
break;
case QCAFRM_WAIT_RSVD_BYTE1:
handle->state = QCAFRM_WAIT_RSVD_BYTE2;
break;
case QCAFRM_WAIT_RSVD_BYTE2:
len = handle->offset;
if (len > buf_len || len < QCAFRM_MIN_LEN) {
ret = QCAFRM_INVLEN;
handle->state = handle->init;
} else {
handle->state = (enum qcafrm_state)(len + 1);
/* Remaining number of bytes. */
handle->offset = 0;
}
break;
default:
/* Receiving Ethernet frame itself. */
buf[handle->offset] = recv_byte;
handle->offset++;
handle->state--;
break;
case QCAFRM_WAIT_551:
if (recv_byte != 0x55) {
ret = QCAFRM_NOTAIL;
handle->state = handle->init;
} else {
handle->state = QCAFRM_WAIT_552;
}
break;
case QCAFRM_WAIT_552:
if (recv_byte != 0x55) {
ret = QCAFRM_NOTAIL;
handle->state = handle->init;
} else {
ret = handle->offset;
/* Frame is fully received. */
handle->state = handle->init;
}
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(qcafrm_fsm_decode);
MODULE_DESCRIPTION("Qualcomm Atheros QCA7000 common");
MODULE_AUTHOR("Qualcomm Atheros Communications");
MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
MODULE_LICENSE("Dual BSD/GPL");
@@ -0,0 +1,117 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
/*
* Copyright (c) 2011, 2012, Atheros Communications Inc.
* Copyright (c) 2014, I2SE GmbH
*/
/* Atheros Ethernet framing. Every Ethernet frame is surrounded by an atheros
* frame while transmitted over a serial channel.
*/
#ifndef _QCA_FRAMING_H
#define _QCA_FRAMING_H
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/types.h>
/* Frame is currently being received */
#define QCAFRM_GATHER 0
/* No header byte while expecting it */
#define QCAFRM_NOHEAD (QCAFRM_ERR_BASE - 1)
/* No tailer byte while expecting it */
#define QCAFRM_NOTAIL (QCAFRM_ERR_BASE - 2)
/* Frame length is invalid */
#define QCAFRM_INVLEN (QCAFRM_ERR_BASE - 3)
/* Frame length is invalid */
#define QCAFRM_INVFRAME (QCAFRM_ERR_BASE - 4)
/* Min/Max Ethernet MTU: 46/1500 */
#define QCAFRM_MIN_MTU (ETH_ZLEN - ETH_HLEN)
#define QCAFRM_MAX_MTU ETH_DATA_LEN
/* Min/Max frame lengths */
#define QCAFRM_MIN_LEN (QCAFRM_MIN_MTU + ETH_HLEN)
#define QCAFRM_MAX_LEN (QCAFRM_MAX_MTU + VLAN_ETH_HLEN)
/* QCA7K header len */
#define QCAFRM_HEADER_LEN 8
/* QCA7K footer len */
#define QCAFRM_FOOTER_LEN 2
/* QCA7K Framing. */
#define QCAFRM_ERR_BASE -1000
enum qcafrm_state {
/* HW length is only available on SPI */
QCAFRM_HW_LEN0 = 0x8000,
QCAFRM_HW_LEN1 = QCAFRM_HW_LEN0 - 1,
QCAFRM_HW_LEN2 = QCAFRM_HW_LEN1 - 1,
QCAFRM_HW_LEN3 = QCAFRM_HW_LEN2 - 1,
/* Waiting first 0xAA of header */
QCAFRM_WAIT_AA1 = QCAFRM_HW_LEN3 - 1,
/* Waiting second 0xAA of header */
QCAFRM_WAIT_AA2 = QCAFRM_WAIT_AA1 - 1,
/* Waiting third 0xAA of header */
QCAFRM_WAIT_AA3 = QCAFRM_WAIT_AA2 - 1,
/* Waiting fourth 0xAA of header */
QCAFRM_WAIT_AA4 = QCAFRM_WAIT_AA3 - 1,
/* Waiting Byte 0-1 of length (litte endian) */
QCAFRM_WAIT_LEN_BYTE0 = QCAFRM_WAIT_AA4 - 1,
QCAFRM_WAIT_LEN_BYTE1 = QCAFRM_WAIT_AA4 - 2,
/* Reserved bytes */
QCAFRM_WAIT_RSVD_BYTE1 = QCAFRM_WAIT_AA4 - 3,
QCAFRM_WAIT_RSVD_BYTE2 = QCAFRM_WAIT_AA4 - 4,
/* The frame length is used as the state until
* the end of the Ethernet frame
* Waiting for first 0x55 of footer
*/
QCAFRM_WAIT_551 = 1,
/* Waiting for second 0x55 of footer */
QCAFRM_WAIT_552 = QCAFRM_WAIT_551 - 1
};
/* Structure to maintain the frame decoding during reception. */
struct qcafrm_handle {
/* Current decoding state */
enum qcafrm_state state;
/* Initial state depends on connection type */
enum qcafrm_state init;
/* Offset in buffer (borrowed for length too) */
u16 offset;
};
u16 qcafrm_create_header(u8 *buf, u16 len);
u16 qcafrm_create_footer(u8 *buf);
static inline void qcafrm_fsm_init_spi(struct qcafrm_handle *handle)
{
handle->init = QCAFRM_HW_LEN0;
handle->state = handle->init;
}
static inline void qcafrm_fsm_init_uart(struct qcafrm_handle *handle)
{
handle->init = QCAFRM_WAIT_AA1;
handle->state = handle->init;
}
s32 qcafrm_fsm_decode(struct qcafrm_handle *handle, u8 *buf, u16 buf_len, u8 recv_byte);
#endif /* _QCA_FRAMING_H */
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
/*
* Copyright (c) 2011, 2012, Qualcomm Atheros Communications Inc.
* Copyright (c) 2014, I2SE GmbH
*/
/* This file contains debugging routines for use in the QCA7K driver.
*/
#include <linux/debugfs.h>
#include <linux/ethtool.h>
#include <linux/seq_file.h>
#include <linux/types.h>
#include "qca_7k.h"
#include "qca_debug.h"
#define QCASPI_MAX_REGS 0x20
#define QCASPI_RX_MAX_FRAMES 4
static const u16 qcaspi_spi_regs[] = {
SPI_REG_BFR_SIZE,
SPI_REG_WRBUF_SPC_AVA,
SPI_REG_RDBUF_BYTE_AVA,
SPI_REG_SPI_CONFIG,
SPI_REG_SPI_STATUS,
SPI_REG_INTR_CAUSE,
SPI_REG_INTR_ENABLE,
SPI_REG_RDBUF_WATERMARK,
SPI_REG_WRBUF_WATERMARK,
SPI_REG_SIGNATURE,
SPI_REG_ACTION_CTRL
};
/* The order of these strings must match the order of the fields in
* struct qcaspi_stats
* See qca_spi.h
*/
static const char qcaspi_gstrings_stats[][ETH_GSTRING_LEN] = {
"Triggered resets",
"Device resets",
"Reset timeouts",
"Read errors",
"Write errors",
"Read buffer errors",
"Write buffer errors",
"Out of memory",
"Write buffer misses",
"Transmit ring full",
"SPI errors",
"Write verify errors",
"Buffer available errors",
"Bad signature",
};
#ifdef CONFIG_DEBUG_FS
static int
qcaspi_info_show(struct seq_file *s, void *what)
{
struct qcaspi *qca = s->private;
seq_printf(s, "RX buffer size : %lu\n",
(unsigned long)qca->buffer_size);
seq_puts(s, "TX ring state : ");
if (qca->txr.skb[qca->txr.head] == NULL)
seq_puts(s, "empty");
else if (qca->txr.skb[qca->txr.tail])
seq_puts(s, "full");
else
seq_puts(s, "in use");
seq_puts(s, "\n");
seq_printf(s, "TX ring size : %u\n",
qca->txr.size);
seq_printf(s, "Sync state : %u (",
(unsigned int)qca->sync);
switch (qca->sync) {
case QCASPI_SYNC_UNKNOWN:
seq_puts(s, "QCASPI_SYNC_UNKNOWN");
break;
case QCASPI_SYNC_RESET:
seq_puts(s, "QCASPI_SYNC_RESET");
break;
case QCASPI_SYNC_READY:
seq_puts(s, "QCASPI_SYNC_READY");
break;
default:
seq_puts(s, "INVALID");
break;
}
seq_puts(s, ")\n");
seq_printf(s, "IRQ : %d\n",
qca->spi_dev->irq);
seq_printf(s, "FLAGS : %lx\n",
qca->flags);
seq_printf(s, "SPI max speed : %lu\n",
(unsigned long)qca->spi_dev->max_speed_hz);
seq_printf(s, "SPI mode : %x\n",
qca->spi_dev->mode);
seq_printf(s, "SPI chip select : %u\n",
(unsigned int)spi_get_chipselect(qca->spi_dev, 0));
seq_printf(s, "SPI legacy mode : %u\n",
(unsigned int)qca->legacy_mode);
seq_printf(s, "SPI burst length : %u\n",
(unsigned int)qca->burst_len);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(qcaspi_info);
void
qcaspi_init_device_debugfs(struct qcaspi *qca)
{
qca->device_root = debugfs_create_dir(dev_name(&qca->net_dev->dev),
NULL);
debugfs_create_file("info", S_IFREG | 0444, qca->device_root, qca,
&qcaspi_info_fops);
}
void
qcaspi_remove_device_debugfs(struct qcaspi *qca)
{
debugfs_remove_recursive(qca->device_root);
}
#else /* CONFIG_DEBUG_FS */
void
qcaspi_init_device_debugfs(struct qcaspi *qca)
{
}
void
qcaspi_remove_device_debugfs(struct qcaspi *qca)
{
}
#endif
static void
qcaspi_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *p)
{
struct qcaspi *qca = netdev_priv(dev);
strscpy(p->driver, QCASPI_DRV_NAME, sizeof(p->driver));
strscpy(p->version, QCASPI_DRV_VERSION, sizeof(p->version));
strscpy(p->fw_version, "QCA7000", sizeof(p->fw_version));
strscpy(p->bus_info, dev_name(&qca->spi_dev->dev),
sizeof(p->bus_info));
}
static int
qcaspi_get_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings *cmd)
{
ethtool_link_ksettings_zero_link_mode(cmd, supported);
ethtool_link_ksettings_add_link_mode(cmd, supported, 10baseT_Half);
cmd->base.speed = SPEED_10;
cmd->base.duplex = DUPLEX_HALF;
cmd->base.port = PORT_OTHER;
cmd->base.autoneg = AUTONEG_DISABLE;
return 0;
}
static void
qcaspi_get_ethtool_stats(struct net_device *dev, struct ethtool_stats *estats, u64 *data)
{
struct qcaspi *qca = netdev_priv(dev);
struct qcaspi_stats *st = &qca->stats;
memcpy(data, st, ARRAY_SIZE(qcaspi_gstrings_stats) * sizeof(u64));
}
static void
qcaspi_get_strings(struct net_device *dev, u32 stringset, u8 *buf)
{
switch (stringset) {
case ETH_SS_STATS:
memcpy(buf, &qcaspi_gstrings_stats,
sizeof(qcaspi_gstrings_stats));
break;
default:
WARN_ON(1);
break;
}
}
static int
qcaspi_get_sset_count(struct net_device *dev, int sset)
{
switch (sset) {
case ETH_SS_STATS:
return ARRAY_SIZE(qcaspi_gstrings_stats);
default:
return -EINVAL;
}
}
static int
qcaspi_get_regs_len(struct net_device *dev)
{
return sizeof(u32) * QCASPI_MAX_REGS;
}
static void
qcaspi_get_regs(struct net_device *dev, struct ethtool_regs *regs, void *p)
{
struct qcaspi *qca = netdev_priv(dev);
u32 *regs_buff = p;
unsigned int i;
regs->version = 1;
memset(regs_buff, 0, sizeof(u32) * QCASPI_MAX_REGS);
for (i = 0; i < ARRAY_SIZE(qcaspi_spi_regs); i++) {
u16 offset, value;
qcaspi_read_register(qca, qcaspi_spi_regs[i], &value);
offset = qcaspi_spi_regs[i] >> 8;
regs_buff[offset] = value;
}
}
static void
qcaspi_get_ringparam(struct net_device *dev, struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct qcaspi *qca = netdev_priv(dev);
ring->rx_max_pending = QCASPI_RX_MAX_FRAMES;
ring->tx_max_pending = QCASPI_TX_RING_MAX_LEN;
ring->rx_pending = QCASPI_RX_MAX_FRAMES;
ring->tx_pending = qca->txr.count;
}
static int
qcaspi_set_ringparam(struct net_device *dev, struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct qcaspi *qca = netdev_priv(dev);
if (ring->rx_pending != QCASPI_RX_MAX_FRAMES ||
(ring->rx_mini_pending) ||
(ring->rx_jumbo_pending))
return -EINVAL;
if (qca->spi_thread)
kthread_park(qca->spi_thread);
qca->txr.count = max_t(u32, ring->tx_pending, QCASPI_TX_RING_MIN_LEN);
qca->txr.count = min_t(u16, qca->txr.count, QCASPI_TX_RING_MAX_LEN);
if (qca->spi_thread)
kthread_unpark(qca->spi_thread);
return 0;
}
static const struct ethtool_ops qcaspi_ethtool_ops = {
.get_drvinfo = qcaspi_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_ethtool_stats = qcaspi_get_ethtool_stats,
.get_strings = qcaspi_get_strings,
.get_sset_count = qcaspi_get_sset_count,
.get_regs_len = qcaspi_get_regs_len,
.get_regs = qcaspi_get_regs,
.get_ringparam = qcaspi_get_ringparam,
.set_ringparam = qcaspi_set_ringparam,
.get_link_ksettings = qcaspi_get_link_ksettings,
};
void qcaspi_set_ethtool_ops(struct net_device *dev)
{
dev->ethtool_ops = &qcaspi_ethtool_ops;
}
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
/*
* Copyright (c) 2011, 2012, Qualcomm Atheros Communications Inc.
* Copyright (c) 2014, I2SE GmbH
*/
/* This file contains debugging routines for use in the QCA7K driver.
*/
#ifndef _QCA_DEBUG_H
#define _QCA_DEBUG_H
#include "qca_spi.h"
void qcaspi_init_device_debugfs(struct qcaspi *qca);
void qcaspi_remove_device_debugfs(struct qcaspi *qca);
void qcaspi_set_ethtool_ops(struct net_device *dev);
#endif /* _QCA_DEBUG_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
/*
* Copyright (c) 2011, 2012, Qualcomm Atheros Communications Inc.
* Copyright (c) 2014, I2SE GmbH
*/
/* Qualcomm Atheros SPI register definition.
*
* This module is designed to define the Qualcomm Atheros SPI register
* placeholders;
*/
#ifndef _QCA_SPI_H
#define _QCA_SPI_H
#include <linux/netdevice.h>
#include <linux/sched.h>
#include <linux/skbuff.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
#include "qca_7k_common.h"
#define QCASPI_DRV_VERSION "0.2.7-i"
#define QCASPI_DRV_NAME "qcaspi"
#define QCASPI_GOOD_SIGNATURE 0xAA55
#define QCASPI_TX_RING_MAX_LEN 10
#define QCASPI_TX_RING_MIN_LEN 2
#define QCASPI_RX_MAX_FRAMES 4
/* sync related constants */
#define QCASPI_SYNC_UNKNOWN 0
#define QCASPI_SYNC_RESET 1
#define QCASPI_SYNC_READY 2
#define QCASPI_RESET_TIMEOUT 10
/* sync events */
#define QCASPI_EVENT_UPDATE 0
#define QCASPI_EVENT_CPUON 1
struct tx_ring {
struct sk_buff *skb[QCASPI_TX_RING_MAX_LEN];
u16 head;
u16 tail;
u16 size;
u16 count;
};
struct qcaspi_stats {
u64 trig_reset;
u64 device_reset;
u64 reset_timeout;
u64 read_err;
u64 write_err;
u64 read_buf_err;
u64 write_buf_err;
u64 out_of_mem;
u64 write_buf_miss;
u64 ring_full;
u64 spi_err;
u64 write_verify_failed;
u64 buf_avail_err;
u64 bad_signature;
};
struct qcaspi {
struct net_device *net_dev;
struct spi_device *spi_dev;
struct task_struct *spi_thread;
struct tx_ring txr;
struct qcaspi_stats stats;
u8 *rx_buffer;
u32 buffer_size;
u8 sync;
struct qcafrm_handle frm_handle;
struct sk_buff *rx_skb;
unsigned long flags;
u16 reset_count;
#ifdef CONFIG_DEBUG_FS
struct dentry *device_root;
#endif
/* user configurable options */
u8 legacy_mode;
u16 burst_len;
};
#endif /* _QCA_SPI_H */
@@ -0,0 +1,402 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
/*
* Copyright (c) 2011, 2012, Qualcomm Atheros Communications Inc.
* Copyright (c) 2017, I2SE GmbH
*/
/* This module implements the Qualcomm Atheros UART protocol for
* kernel-based UART device; it is essentially an Ethernet-to-UART
* serial converter;
*/
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/sched.h>
#include <linux/serdev.h>
#include <linux/skbuff.h>
#include <linux/types.h>
#include "qca_7k_common.h"
#define QCAUART_DRV_VERSION "0.1.0"
#define QCAUART_DRV_NAME "qcauart"
#define QCAUART_TX_TIMEOUT (1 * HZ)
struct qcauart {
struct net_device *net_dev;
spinlock_t lock; /* transmit lock */
struct work_struct tx_work; /* Flushes transmit buffer */
struct serdev_device *serdev;
struct qcafrm_handle frm_handle;
struct sk_buff *rx_skb;
unsigned char *tx_head; /* pointer to next XMIT byte */
int tx_left; /* bytes left in XMIT queue */
unsigned char *tx_buffer;
};
static size_t
qca_tty_receive(struct serdev_device *serdev, const u8 *data, size_t count)
{
struct qcauart *qca = serdev_device_get_drvdata(serdev);
struct net_device *netdev = qca->net_dev;
struct net_device_stats *n_stats = &netdev->stats;
size_t i;
if (!qca->rx_skb) {
qca->rx_skb = netdev_alloc_skb_ip_align(netdev,
netdev->mtu +
VLAN_ETH_HLEN);
if (!qca->rx_skb) {
n_stats->rx_errors++;
n_stats->rx_dropped++;
return 0;
}
}
for (i = 0; i < count; i++) {
s32 retcode;
retcode = qcafrm_fsm_decode(&qca->frm_handle,
qca->rx_skb->data,
skb_tailroom(qca->rx_skb),
data[i]);
switch (retcode) {
case QCAFRM_GATHER:
case QCAFRM_NOHEAD:
break;
case QCAFRM_NOTAIL:
netdev_dbg(netdev, "recv: no RX tail\n");
n_stats->rx_errors++;
n_stats->rx_dropped++;
break;
case QCAFRM_INVLEN:
netdev_dbg(netdev, "recv: invalid RX length\n");
n_stats->rx_errors++;
n_stats->rx_dropped++;
break;
default:
n_stats->rx_packets++;
n_stats->rx_bytes += retcode;
skb_put(qca->rx_skb, retcode);
qca->rx_skb->protocol = eth_type_trans(
qca->rx_skb, qca->rx_skb->dev);
skb_checksum_none_assert(qca->rx_skb);
netif_rx(qca->rx_skb);
qca->rx_skb = netdev_alloc_skb_ip_align(netdev,
netdev->mtu +
VLAN_ETH_HLEN);
if (!qca->rx_skb) {
netdev_dbg(netdev, "recv: out of RX resources\n");
n_stats->rx_errors++;
return i + 1;
}
}
}
return i;
}
/* Write out any remaining transmit buffer. Scheduled when tty is writable */
static void qcauart_transmit(struct work_struct *work)
{
struct qcauart *qca = container_of(work, struct qcauart, tx_work);
struct net_device_stats *n_stats = &qca->net_dev->stats;
int written;
spin_lock_bh(&qca->lock);
/* First make sure we're connected. */
if (!netif_running(qca->net_dev)) {
spin_unlock_bh(&qca->lock);
return;
}
if (qca->tx_left <= 0) {
/* Now serial buffer is almost free & we can start
* transmission of another packet
*/
n_stats->tx_packets++;
spin_unlock_bh(&qca->lock);
netif_wake_queue(qca->net_dev);
return;
}
written = serdev_device_write_buf(qca->serdev, qca->tx_head,
qca->tx_left);
if (written > 0) {
qca->tx_left -= written;
qca->tx_head += written;
}
spin_unlock_bh(&qca->lock);
}
/* Called by the driver when there's room for more data.
* Schedule the transmit.
*/
static void qca_tty_wakeup(struct serdev_device *serdev)
{
struct qcauart *qca = serdev_device_get_drvdata(serdev);
schedule_work(&qca->tx_work);
}
static const struct serdev_device_ops qca_serdev_ops = {
.receive_buf = qca_tty_receive,
.write_wakeup = qca_tty_wakeup,
};
static int qcauart_netdev_open(struct net_device *dev)
{
struct qcauart *qca = netdev_priv(dev);
netif_start_queue(qca->net_dev);
return 0;
}
static int qcauart_netdev_close(struct net_device *dev)
{
struct qcauart *qca = netdev_priv(dev);
netif_stop_queue(dev);
flush_work(&qca->tx_work);
spin_lock_bh(&qca->lock);
qca->tx_left = 0;
spin_unlock_bh(&qca->lock);
return 0;
}
static netdev_tx_t
qcauart_netdev_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct net_device_stats *n_stats = &dev->stats;
struct qcauart *qca = netdev_priv(dev);
u8 pad_len = 0;
int written;
u8 *pos;
spin_lock(&qca->lock);
WARN_ON(qca->tx_left);
if (!netif_running(dev)) {
spin_unlock(&qca->lock);
netdev_warn(qca->net_dev, "xmit: iface is down\n");
goto out;
}
pos = qca->tx_buffer;
if (skb->len < QCAFRM_MIN_LEN)
pad_len = QCAFRM_MIN_LEN - skb->len;
pos += qcafrm_create_header(pos, skb->len + pad_len);
memcpy(pos, skb->data, skb->len);
pos += skb->len;
if (pad_len) {
memset(pos, 0, pad_len);
pos += pad_len;
}
pos += qcafrm_create_footer(pos);
netif_stop_queue(qca->net_dev);
written = serdev_device_write_buf(qca->serdev, qca->tx_buffer,
pos - qca->tx_buffer);
if (written > 0) {
qca->tx_left = (pos - qca->tx_buffer) - written;
qca->tx_head = qca->tx_buffer + written;
n_stats->tx_bytes += written;
}
spin_unlock(&qca->lock);
netif_trans_update(dev);
out:
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
}
static void qcauart_netdev_tx_timeout(struct net_device *dev, unsigned int txqueue)
{
struct qcauart *qca = netdev_priv(dev);
netdev_info(qca->net_dev, "Transmit timeout at %ld, latency %ld\n",
jiffies, dev_trans_start(dev));
dev->stats.tx_errors++;
dev->stats.tx_dropped++;
}
static int qcauart_netdev_init(struct net_device *dev)
{
struct qcauart *qca = netdev_priv(dev);
size_t len;
/* Finish setting up the device info. */
dev->mtu = QCAFRM_MAX_MTU;
dev->type = ARPHRD_ETHER;
len = QCAFRM_HEADER_LEN + QCAFRM_MAX_LEN + QCAFRM_FOOTER_LEN;
qca->tx_buffer = devm_kmalloc(&qca->serdev->dev, len, GFP_KERNEL);
if (!qca->tx_buffer)
return -ENOMEM;
qca->rx_skb = netdev_alloc_skb_ip_align(qca->net_dev,
qca->net_dev->mtu +
VLAN_ETH_HLEN);
if (!qca->rx_skb)
return -ENOBUFS;
return 0;
}
static void qcauart_netdev_uninit(struct net_device *dev)
{
struct qcauart *qca = netdev_priv(dev);
dev_kfree_skb(qca->rx_skb);
}
static const struct net_device_ops qcauart_netdev_ops = {
.ndo_init = qcauart_netdev_init,
.ndo_uninit = qcauart_netdev_uninit,
.ndo_open = qcauart_netdev_open,
.ndo_stop = qcauart_netdev_close,
.ndo_start_xmit = qcauart_netdev_xmit,
.ndo_set_mac_address = eth_mac_addr,
.ndo_tx_timeout = qcauart_netdev_tx_timeout,
.ndo_validate_addr = eth_validate_addr,
};
static void qcauart_netdev_setup(struct net_device *dev)
{
dev->netdev_ops = &qcauart_netdev_ops;
dev->watchdog_timeo = QCAUART_TX_TIMEOUT;
dev->priv_flags &= ~IFF_TX_SKB_SHARING;
dev->tx_queue_len = 100;
/* MTU range: 46 - 1500 */
dev->min_mtu = QCAFRM_MIN_MTU;
dev->max_mtu = QCAFRM_MAX_MTU;
}
static const struct of_device_id qca_uart_of_match[] = {
{
.compatible = "qca,qca7000",
},
{}
};
MODULE_DEVICE_TABLE(of, qca_uart_of_match);
static int qca_uart_probe(struct serdev_device *serdev)
{
struct net_device *qcauart_dev = alloc_etherdev(sizeof(struct qcauart));
struct qcauart *qca;
u32 speed = 115200;
int ret;
if (!qcauart_dev)
return -ENOMEM;
qcauart_netdev_setup(qcauart_dev);
SET_NETDEV_DEV(qcauart_dev, &serdev->dev);
qca = netdev_priv(qcauart_dev);
if (!qca) {
pr_err("qca_uart: Fail to retrieve private structure\n");
ret = -ENOMEM;
goto free;
}
qca->net_dev = qcauart_dev;
qca->serdev = serdev;
qcafrm_fsm_init_uart(&qca->frm_handle);
spin_lock_init(&qca->lock);
INIT_WORK(&qca->tx_work, qcauart_transmit);
of_property_read_u32(serdev->dev.of_node, "current-speed", &speed);
ret = of_get_ethdev_address(serdev->dev.of_node, qca->net_dev);
if (ret) {
eth_hw_addr_random(qca->net_dev);
dev_info(&serdev->dev, "Using random MAC address: %pM\n",
qca->net_dev->dev_addr);
}
netif_carrier_on(qca->net_dev);
serdev_device_set_drvdata(serdev, qca);
serdev_device_set_client_ops(serdev, &qca_serdev_ops);
ret = serdev_device_open(serdev);
if (ret) {
dev_err(&serdev->dev, "Unable to open device %s\n",
qcauart_dev->name);
goto free;
}
speed = serdev_device_set_baudrate(serdev, speed);
dev_info(&serdev->dev, "Using baudrate: %u\n", speed);
serdev_device_set_flow_control(serdev, false);
ret = register_netdev(qcauart_dev);
if (ret) {
dev_err(&serdev->dev, "Unable to register net device %s\n",
qcauart_dev->name);
serdev_device_close(serdev);
cancel_work_sync(&qca->tx_work);
goto free;
}
return 0;
free:
free_netdev(qcauart_dev);
return ret;
}
static void qca_uart_remove(struct serdev_device *serdev)
{
struct qcauart *qca = serdev_device_get_drvdata(serdev);
unregister_netdev(qca->net_dev);
/* Flush any pending characters in the driver. */
serdev_device_close(serdev);
cancel_work_sync(&qca->tx_work);
free_netdev(qca->net_dev);
}
static struct serdev_device_driver qca_uart_driver = {
.probe = qca_uart_probe,
.remove = qca_uart_remove,
.driver = {
.name = QCAUART_DRV_NAME,
.of_match_table = qca_uart_of_match,
},
};
module_serdev_device_driver(qca_uart_driver);
MODULE_DESCRIPTION("Qualcomm Atheros QCA7000 UART Driver");
MODULE_AUTHOR("Qualcomm Atheros Communications");
MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION(QCAUART_DRV_VERSION);
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# RMNET MAP driver
#
menuconfig RMNET
tristate "RmNet MAP driver"
default n
select GRO_CELLS
help
If you select this, you will enable the RMNET module which is used
for handling data in the multiplexing and aggregation protocol (MAP)
format in the embedded data path. RMNET devices can be attached to
any IP mode physical device.
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the RMNET module
#
rmnet-y := rmnet_config.o
rmnet-y += rmnet_vnd.o
rmnet-y += rmnet_handlers.o
rmnet-y += rmnet_map_data.o
rmnet-y += rmnet_map_command.o
obj-$(CONFIG_RMNET) += rmnet.o
@@ -0,0 +1,527 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
*
* RMNET configuration engine
*/
#include <net/sock.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/netdevice.h>
#include "rmnet_config.h"
#include "rmnet_handlers.h"
#include "rmnet_vnd.h"
#include "rmnet_private.h"
#include "rmnet_map.h"
/* Local Definitions and Declarations */
static const struct nla_policy rmnet_policy[IFLA_RMNET_MAX + 1] = {
[IFLA_RMNET_MUX_ID] = { .type = NLA_U16 },
[IFLA_RMNET_FLAGS] = { .len = sizeof(struct ifla_rmnet_flags) },
};
static int rmnet_is_real_dev_registered(const struct net_device *real_dev)
{
return rcu_access_pointer(real_dev->rx_handler) == rmnet_rx_handler;
}
/* Needs rtnl lock */
struct rmnet_port*
rmnet_get_port_rtnl(const struct net_device *real_dev)
{
return rtnl_dereference(real_dev->rx_handler_data);
}
static int rmnet_unregister_real_device(struct net_device *real_dev)
{
struct rmnet_port *port = rmnet_get_port_rtnl(real_dev);
if (port->nr_rmnet_devs)
return -EINVAL;
rmnet_map_tx_aggregate_exit(port);
netdev_rx_handler_unregister(real_dev);
kfree(port);
netdev_dbg(real_dev, "Removed from rmnet\n");
return 0;
}
static int rmnet_register_real_device(struct net_device *real_dev,
struct netlink_ext_ack *extack)
{
struct rmnet_port *port;
int rc, entry;
ASSERT_RTNL();
if (rmnet_is_real_dev_registered(real_dev)) {
port = rmnet_get_port_rtnl(real_dev);
if (port->rmnet_mode != RMNET_EPMODE_VND) {
NL_SET_ERR_MSG_MOD(extack, "bridge device already exists");
return -EINVAL;
}
return 0;
}
port = kzalloc_obj(*port);
if (!port)
return -ENOMEM;
port->dev = real_dev;
rc = netdev_rx_handler_register(real_dev, rmnet_rx_handler, port);
if (rc) {
kfree(port);
return -EBUSY;
}
for (entry = 0; entry < RMNET_MAX_LOGICAL_EP; entry++)
INIT_HLIST_HEAD(&port->muxed_ep[entry]);
rmnet_map_tx_aggregate_init(port);
netdev_dbg(real_dev, "registered with rmnet\n");
return 0;
}
static void rmnet_unregister_bridge(struct rmnet_port *port)
{
struct net_device *bridge_dev, *real_dev, *rmnet_dev;
struct rmnet_port *real_port;
if (port->rmnet_mode != RMNET_EPMODE_BRIDGE)
return;
rmnet_dev = port->rmnet_dev;
if (!port->nr_rmnet_devs) {
/* bridge device */
real_dev = port->bridge_ep;
bridge_dev = port->dev;
real_port = rmnet_get_port_rtnl(real_dev);
real_port->bridge_ep = NULL;
real_port->rmnet_mode = RMNET_EPMODE_VND;
} else {
/* real device */
bridge_dev = port->bridge_ep;
port->bridge_ep = NULL;
port->rmnet_mode = RMNET_EPMODE_VND;
}
netdev_upper_dev_unlink(bridge_dev, rmnet_dev);
rmnet_unregister_real_device(bridge_dev);
}
static int rmnet_newlink(struct net_device *dev,
struct rtnl_newlink_params *params,
struct netlink_ext_ack *extack)
{
struct net *link_net = rtnl_newlink_link_net(params);
u32 data_format = RMNET_FLAGS_INGRESS_DEAGGREGATION;
struct nlattr **data = params->data;
struct nlattr **tb = params->tb;
struct net_device *real_dev;
int mode = RMNET_EPMODE_VND;
struct rmnet_endpoint *ep;
struct rmnet_port *port;
int err = 0;
u16 mux_id;
if (!tb[IFLA_LINK]) {
NL_SET_ERR_MSG_MOD(extack, "link not specified");
return -EINVAL;
}
real_dev = __dev_get_by_index(link_net, nla_get_u32(tb[IFLA_LINK]));
if (!real_dev) {
NL_SET_ERR_MSG_MOD(extack, "link does not exist");
return -ENODEV;
}
ep = kzalloc_obj(*ep);
if (!ep)
return -ENOMEM;
mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
err = rmnet_register_real_device(real_dev, extack);
if (err)
goto err0;
port = rmnet_get_port_rtnl(real_dev);
err = rmnet_vnd_newlink(mux_id, dev, port, real_dev, ep, extack);
if (err)
goto err1;
err = netdev_upper_dev_link(real_dev, dev, extack);
if (err < 0)
goto err2;
port->rmnet_mode = mode;
port->rmnet_dev = dev;
hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[mux_id]);
if (data[IFLA_RMNET_FLAGS]) {
struct ifla_rmnet_flags *flags;
flags = nla_data(data[IFLA_RMNET_FLAGS]);
data_format &= ~flags->mask;
data_format |= flags->flags & flags->mask;
}
netdev_dbg(dev, "data format [0x%08X]\n", data_format);
port->data_format = data_format;
return 0;
err2:
unregister_netdevice(dev);
rmnet_vnd_dellink(mux_id, port, ep);
err1:
rmnet_unregister_real_device(real_dev);
err0:
kfree(ep);
return err;
}
static void rmnet_dellink(struct net_device *dev, struct list_head *head)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct net_device *real_dev, *bridge_dev;
struct rmnet_port *real_port, *bridge_port;
struct rmnet_endpoint *ep;
u8 mux_id = priv->mux_id;
real_dev = priv->real_dev;
if (!rmnet_is_real_dev_registered(real_dev))
return;
real_port = rmnet_get_port_rtnl(real_dev);
bridge_dev = real_port->bridge_ep;
if (bridge_dev) {
bridge_port = rmnet_get_port_rtnl(bridge_dev);
rmnet_unregister_bridge(bridge_port);
}
ep = rmnet_get_endpoint(real_port, mux_id);
if (ep) {
hlist_del_init_rcu(&ep->hlnode);
real_port->nr_rmnet_devs--;
kfree_rcu(ep, rcu);
}
netdev_upper_dev_unlink(real_dev, dev);
rmnet_unregister_real_device(real_dev);
unregister_netdevice_queue(dev, head);
}
static void rmnet_force_unassociate_device(struct net_device *real_dev)
{
struct hlist_node *tmp_ep;
struct rmnet_endpoint *ep;
struct rmnet_port *port;
unsigned long bkt_ep;
LIST_HEAD(list);
port = rmnet_get_port_rtnl(real_dev);
if (port->nr_rmnet_devs) {
/* real device */
rmnet_unregister_bridge(port);
hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) {
unregister_netdevice_queue(ep->egress_dev, &list);
netdev_upper_dev_unlink(real_dev, ep->egress_dev);
hlist_del_init_rcu(&ep->hlnode);
port->nr_rmnet_devs--;
kfree_rcu(ep, rcu);
}
rmnet_unregister_real_device(real_dev);
unregister_netdevice_many(&list);
} else {
rmnet_unregister_bridge(port);
}
}
static int rmnet_config_notify_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
struct net_device *real_dev = netdev_notifier_info_to_dev(data);
if (!rmnet_is_real_dev_registered(real_dev))
return NOTIFY_DONE;
switch (event) {
case NETDEV_UNREGISTER:
netdev_dbg(real_dev, "Kernel unregister\n");
rmnet_force_unassociate_device(real_dev);
break;
case NETDEV_CHANGEMTU:
if (rmnet_vnd_validate_real_dev_mtu(real_dev))
return NOTIFY_BAD;
break;
default:
break;
}
return NOTIFY_DONE;
}
static struct notifier_block rmnet_dev_notifier __read_mostly = {
.notifier_call = rmnet_config_notify_cb,
};
static int rmnet_rtnl_validate(struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack)
{
u16 mux_id;
if (!data || !data[IFLA_RMNET_MUX_ID]) {
NL_SET_ERR_MSG_MOD(extack, "MUX ID not specified");
return -EINVAL;
}
mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
if (mux_id > (RMNET_MAX_LOGICAL_EP - 1)) {
NL_SET_ERR_MSG_MOD(extack, "invalid MUX ID");
return -ERANGE;
}
return 0;
}
static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
struct nlattr *data[],
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct net_device *real_dev;
struct rmnet_port *port;
u16 mux_id;
if (!dev)
return -ENODEV;
real_dev = priv->real_dev;
if (!rmnet_is_real_dev_registered(real_dev))
return -ENODEV;
port = rmnet_get_port_rtnl(real_dev);
if (data[IFLA_RMNET_MUX_ID]) {
mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
if (mux_id != priv->mux_id) {
struct rmnet_endpoint *ep;
ep = rmnet_get_endpoint(port, priv->mux_id);
if (!ep)
return -ENODEV;
if (rmnet_get_endpoint(port, mux_id)) {
NL_SET_ERR_MSG_MOD(extack,
"MUX ID already exists");
return -EINVAL;
}
hlist_del_init_rcu(&ep->hlnode);
hlist_add_head_rcu(&ep->hlnode,
&port->muxed_ep[mux_id]);
ep->mux_id = mux_id;
priv->mux_id = mux_id;
}
}
if (data[IFLA_RMNET_FLAGS]) {
struct ifla_rmnet_flags *flags;
u32 old_data_format;
old_data_format = port->data_format;
flags = nla_data(data[IFLA_RMNET_FLAGS]);
port->data_format &= ~flags->mask;
port->data_format |= flags->flags & flags->mask;
if (rmnet_vnd_update_dev_mtu(port, real_dev)) {
port->data_format = old_data_format;
NL_SET_ERR_MSG_MOD(extack, "Invalid MTU on real dev");
return -EINVAL;
}
}
return 0;
}
static size_t rmnet_get_size(const struct net_device *dev)
{
return
/* IFLA_RMNET_MUX_ID */
nla_total_size(2) +
/* IFLA_RMNET_FLAGS */
nla_total_size(sizeof(struct ifla_rmnet_flags));
}
static int rmnet_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct net_device *real_dev;
struct ifla_rmnet_flags f;
struct rmnet_port *port;
real_dev = priv->real_dev;
if (nla_put_u16(skb, IFLA_RMNET_MUX_ID, priv->mux_id))
goto nla_put_failure;
if (rmnet_is_real_dev_registered(real_dev)) {
port = rmnet_get_port_rtnl(real_dev);
f.flags = port->data_format;
} else {
f.flags = 0;
}
f.mask = ~0;
if (nla_put(skb, IFLA_RMNET_FLAGS, sizeof(f), &f))
goto nla_put_failure;
return 0;
nla_put_failure:
return -EMSGSIZE;
}
struct rtnl_link_ops rmnet_link_ops __read_mostly = {
.kind = "rmnet",
.maxtype = IFLA_RMNET_MAX,
.priv_size = sizeof(struct rmnet_priv),
.setup = rmnet_vnd_setup,
.validate = rmnet_rtnl_validate,
.newlink = rmnet_newlink,
.dellink = rmnet_dellink,
.get_size = rmnet_get_size,
.changelink = rmnet_changelink,
.policy = rmnet_policy,
.fill_info = rmnet_fill_info,
};
struct rmnet_port *rmnet_get_port_rcu(struct net_device *real_dev)
{
if (rmnet_is_real_dev_registered(real_dev))
return rcu_dereference_bh(real_dev->rx_handler_data);
else
return NULL;
}
struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id)
{
struct rmnet_endpoint *ep;
hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode) {
if (ep->mux_id == mux_id)
return ep;
}
return NULL;
}
int rmnet_add_bridge(struct net_device *rmnet_dev,
struct net_device *slave_dev,
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(rmnet_dev);
struct net_device *real_dev = priv->real_dev;
struct rmnet_port *port, *slave_port;
int err;
port = rmnet_get_port_rtnl(real_dev);
/* If there is more than one rmnet dev attached, its probably being
* used for muxing. Skip the briding in that case
*/
if (port->nr_rmnet_devs > 1) {
NL_SET_ERR_MSG_MOD(extack, "more than one rmnet dev attached");
return -EINVAL;
}
if (port->rmnet_mode != RMNET_EPMODE_VND) {
NL_SET_ERR_MSG_MOD(extack, "more than one bridge dev attached");
return -EINVAL;
}
if (rmnet_is_real_dev_registered(slave_dev)) {
NL_SET_ERR_MSG_MOD(extack,
"slave cannot be another rmnet dev");
return -EBUSY;
}
err = rmnet_register_real_device(slave_dev, extack);
if (err)
return -EBUSY;
err = netdev_master_upper_dev_link(slave_dev, rmnet_dev, NULL, NULL,
extack);
if (err) {
rmnet_unregister_real_device(slave_dev);
return err;
}
slave_port = rmnet_get_port_rtnl(slave_dev);
slave_port->rmnet_mode = RMNET_EPMODE_BRIDGE;
slave_port->bridge_ep = real_dev;
slave_port->rmnet_dev = rmnet_dev;
port->rmnet_mode = RMNET_EPMODE_BRIDGE;
port->bridge_ep = slave_dev;
netdev_dbg(slave_dev, "registered with rmnet as slave\n");
return 0;
}
int rmnet_del_bridge(struct net_device *rmnet_dev,
struct net_device *slave_dev)
{
struct rmnet_port *port = rmnet_get_port_rtnl(slave_dev);
rmnet_unregister_bridge(port);
netdev_dbg(slave_dev, "removed from rmnet as slave\n");
return 0;
}
/* Startup/Shutdown */
static int __init rmnet_init(void)
{
int rc;
rc = register_netdevice_notifier(&rmnet_dev_notifier);
if (rc != 0)
return rc;
rc = rtnl_link_register(&rmnet_link_ops);
if (rc != 0) {
unregister_netdevice_notifier(&rmnet_dev_notifier);
return rc;
}
return rc;
}
static void __exit rmnet_exit(void)
{
rtnl_link_unregister(&rmnet_link_ops);
unregister_netdevice_notifier(&rmnet_dev_notifier);
}
module_init(rmnet_init)
module_exit(rmnet_exit)
MODULE_ALIAS_RTNL_LINK("rmnet");
MODULE_DESCRIPTION("Qualcomm RmNet MAP driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,102 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2013-2014, 2016-2018, 2021 The Linux Foundation.
* All rights reserved.
*
* RMNET Data configuration engine
*/
#include <linux/skbuff.h>
#include <linux/time.h>
#include <net/gro_cells.h>
#ifndef _RMNET_CONFIG_H_
#define _RMNET_CONFIG_H_
#define RMNET_MAX_LOGICAL_EP 255
struct rmnet_endpoint {
u8 mux_id;
struct net_device *egress_dev;
struct hlist_node hlnode;
struct rcu_head rcu;
};
struct rmnet_egress_agg_params {
u32 bytes;
u32 count;
u64 time_nsec;
};
/* One instance of this structure is instantiated for each real_dev associated
* with rmnet.
*/
struct rmnet_port {
struct net_device *dev;
u32 data_format;
u8 nr_rmnet_devs;
u8 rmnet_mode;
struct hlist_head muxed_ep[RMNET_MAX_LOGICAL_EP];
struct net_device *bridge_ep;
struct net_device *rmnet_dev;
/* Egress aggregation information */
struct rmnet_egress_agg_params egress_agg_params;
/* Protect aggregation related elements */
spinlock_t agg_lock;
struct sk_buff *skbagg_head;
struct sk_buff *skbagg_tail;
int agg_state;
u8 agg_count;
struct timespec64 agg_time;
struct timespec64 agg_last;
struct hrtimer hrtimer;
struct work_struct agg_wq;
};
extern struct rtnl_link_ops rmnet_link_ops;
struct rmnet_vnd_stats {
u64 rx_pkts;
u64 rx_bytes;
u64 tx_pkts;
u64 tx_bytes;
u32 tx_drops;
};
struct rmnet_pcpu_stats {
struct rmnet_vnd_stats stats;
struct u64_stats_sync syncp;
};
struct rmnet_priv_stats {
u64 csum_ok;
u64 csum_ip4_header_bad;
u64 csum_valid_unset;
u64 csum_validation_failed;
u64 csum_err_bad_buffer;
u64 csum_err_invalid_ip_version;
u64 csum_err_invalid_transport;
u64 csum_fragmented_pkt;
u64 csum_skipped;
u64 csum_sw;
u64 csum_hw;
};
struct rmnet_priv {
u8 mux_id;
struct net_device *real_dev;
struct rmnet_pcpu_stats __percpu *pcpu_stats;
struct gro_cells gro_cells;
struct rmnet_priv_stats stats;
};
struct rmnet_port *rmnet_get_port_rcu(struct net_device *real_dev);
struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id);
int rmnet_add_bridge(struct net_device *rmnet_dev,
struct net_device *slave_dev,
struct netlink_ext_ack *extack);
int rmnet_del_bridge(struct net_device *rmnet_dev,
struct net_device *slave_dev);
struct rmnet_port*
rmnet_get_port_rtnl(const struct net_device *real_dev);
#endif /* _RMNET_CONFIG_H_ */
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2013-2018, 2021, The Linux Foundation. All rights reserved.
*
* RMNET Data ingress/egress handler
*/
#include <linux/netdevice.h>
#include <linux/netdev_features.h>
#include <linux/if_arp.h>
#include <net/sock.h>
#include "rmnet_private.h"
#include "rmnet_config.h"
#include "rmnet_vnd.h"
#include "rmnet_map.h"
#include "rmnet_handlers.h"
#define RMNET_IP_VERSION_4 0x40
#define RMNET_IP_VERSION_6 0x60
/* Helper Functions */
static void rmnet_set_skb_proto(struct sk_buff *skb)
{
switch (skb->data[0] & 0xF0) {
case RMNET_IP_VERSION_4:
skb->protocol = htons(ETH_P_IP);
break;
case RMNET_IP_VERSION_6:
skb->protocol = htons(ETH_P_IPV6);
break;
default:
skb->protocol = htons(ETH_P_MAP);
break;
}
}
/* Generic handler */
static void
rmnet_deliver_skb(struct sk_buff *skb)
{
struct rmnet_priv *priv = netdev_priv(skb->dev);
skb_reset_transport_header(skb);
skb_reset_network_header(skb);
rmnet_vnd_rx_fixup(skb, skb->dev);
skb->pkt_type = PACKET_HOST;
skb_set_mac_header(skb, 0);
gro_cells_receive(&priv->gro_cells, skb);
}
/* MAP handler */
static void
__rmnet_map_ingress_handler(struct sk_buff *skb,
struct rmnet_port *port)
{
struct rmnet_map_header *map_header = (void *)skb->data;
struct rmnet_endpoint *ep;
u16 len, pad;
u8 mux_id;
if (map_header->flags & MAP_CMD_FLAG) {
/* Packet contains a MAP command (not data) */
if (port->data_format & RMNET_FLAGS_INGRESS_MAP_COMMANDS)
return rmnet_map_command(skb, port);
goto free_skb;
}
mux_id = map_header->mux_id;
pad = map_header->flags & MAP_PAD_LEN_MASK;
len = ntohs(map_header->pkt_len) - pad;
if (mux_id >= RMNET_MAX_LOGICAL_EP)
goto free_skb;
ep = rmnet_get_endpoint(port, mux_id);
if (!ep)
goto free_skb;
skb->dev = ep->egress_dev;
if ((port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
(map_header->flags & MAP_NEXT_HEADER_FLAG)) {
if (rmnet_map_process_next_hdr_packet(skb, len))
goto free_skb;
skb_pull(skb, sizeof(*map_header));
rmnet_set_skb_proto(skb);
} else {
/* Subtract MAP header */
skb_pull(skb, sizeof(*map_header));
rmnet_set_skb_proto(skb);
if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4 &&
!rmnet_map_checksum_downlink_packet(skb, len + pad))
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
skb_trim(skb, len);
rmnet_deliver_skb(skb);
return;
free_skb:
kfree_skb(skb);
}
static void
rmnet_map_ingress_handler(struct sk_buff *skb,
struct rmnet_port *port)
{
struct sk_buff *skbn;
if (skb->dev->type == ARPHRD_ETHER) {
if (pskb_expand_head(skb, ETH_HLEN, 0, GFP_ATOMIC)) {
kfree_skb(skb);
return;
}
skb_push(skb, ETH_HLEN);
}
if (port->data_format & RMNET_FLAGS_INGRESS_DEAGGREGATION) {
while ((skbn = rmnet_map_deaggregate(skb, port)) != NULL)
__rmnet_map_ingress_handler(skbn, port);
consume_skb(skb);
} else {
if (rmnet_map_validate_packet_len(skb, port))
__rmnet_map_ingress_handler(skb, port);
else
kfree_skb(skb);
}
}
static int rmnet_map_egress_handler(struct sk_buff *skb,
struct rmnet_port *port, u8 mux_id,
struct net_device *orig_dev)
{
int required_headroom, additional_header_len, csum_type = 0;
struct rmnet_map_header *map_header;
additional_header_len = 0;
required_headroom = sizeof(struct rmnet_map_header);
if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV4) {
additional_header_len = sizeof(struct rmnet_map_ul_csum_header);
csum_type = RMNET_FLAGS_EGRESS_MAP_CKSUMV4;
} else if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5) {
additional_header_len = sizeof(struct rmnet_map_v5_csum_header);
csum_type = RMNET_FLAGS_EGRESS_MAP_CKSUMV5;
}
required_headroom += additional_header_len;
if (skb_cow_head(skb, required_headroom) < 0)
return -ENOMEM;
if (csum_type)
rmnet_map_checksum_uplink_packet(skb, port, orig_dev,
csum_type);
map_header = rmnet_map_add_map_header(skb, additional_header_len,
port, 0);
if (!map_header)
return -ENOMEM;
map_header->mux_id = mux_id;
if (READ_ONCE(port->egress_agg_params.count) > 1) {
unsigned int len;
len = rmnet_map_tx_aggregate(skb, port, orig_dev);
if (likely(len)) {
rmnet_vnd_tx_fixup_len(len, orig_dev);
return -EINPROGRESS;
}
return -ENOMEM;
}
skb->protocol = htons(ETH_P_MAP);
return 0;
}
static void
rmnet_bridge_handler(struct sk_buff *skb, struct net_device *bridge_dev)
{
if (skb_mac_header_was_set(skb))
skb_push(skb, skb->mac_len);
if (bridge_dev) {
skb->dev = bridge_dev;
dev_queue_xmit(skb);
}
}
/* Ingress / Egress Entry Points */
/* Processes packet as per ingress data format for receiving device. Logical
* endpoint is determined from packet inspection. Packet is then sent to the
* egress device listed in the logical endpoint configuration.
*/
rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb)
{
struct sk_buff *skb = *pskb;
struct rmnet_port *port;
struct net_device *dev;
if (!skb)
goto done;
if (skb_linearize(skb)) {
kfree_skb(skb);
goto done;
}
if (skb->pkt_type == PACKET_LOOPBACK)
return RX_HANDLER_PASS;
dev = skb->dev;
port = rmnet_get_port_rcu(dev);
if (unlikely(!port)) {
dev_core_stats_rx_nohandler_inc(skb->dev);
kfree_skb(skb);
goto done;
}
switch (port->rmnet_mode) {
case RMNET_EPMODE_VND:
rmnet_map_ingress_handler(skb, port);
break;
case RMNET_EPMODE_BRIDGE:
rmnet_bridge_handler(skb, port->bridge_ep);
break;
}
done:
return RX_HANDLER_CONSUMED;
}
/* Modifies packet as per logical endpoint configuration and egress data format
* for egress device configured in logical endpoint. Packet is then transmitted
* on the egress device.
*/
void rmnet_egress_handler(struct sk_buff *skb)
{
struct net_device *orig_dev;
struct rmnet_port *port;
struct rmnet_priv *priv;
u8 mux_id;
int err;
sk_pacing_shift_update(skb->sk, 8);
orig_dev = skb->dev;
priv = netdev_priv(orig_dev);
skb->dev = priv->real_dev;
mux_id = priv->mux_id;
port = rmnet_get_port_rcu(skb->dev);
if (!port)
goto drop;
err = rmnet_map_egress_handler(skb, port, mux_id, orig_dev);
if (err == -ENOMEM)
goto drop;
else if (err == -EINPROGRESS)
return;
rmnet_vnd_tx_fixup(skb, orig_dev);
dev_queue_xmit(skb);
return;
drop:
this_cpu_inc(priv->pcpu_stats->stats.tx_drops);
kfree_skb(skb);
}
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2013, 2016-2017 The Linux Foundation. All rights reserved.
*
* RMNET Data ingress/egress handler
*/
#ifndef _RMNET_HANDLERS_H_
#define _RMNET_HANDLERS_H_
#include "rmnet_config.h"
void rmnet_egress_handler(struct sk_buff *skb);
rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb);
#endif /* _RMNET_HANDLERS_H_ */
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2013-2018, 2021, The Linux Foundation. All rights reserved.
*/
#ifndef _RMNET_MAP_H_
#define _RMNET_MAP_H_
#include <linux/if_rmnet.h>
struct rmnet_map_control_command {
u8 command_name;
u8 cmd_type:2;
u8 reserved:6;
u16 reserved2;
u32 transaction_id;
union {
struct {
u16 ip_family:2;
u16 reserved:14;
__be16 flow_control_seq_num;
__be32 qos_id;
} flow_control;
DECLARE_FLEX_ARRAY(u8, data);
};
} __aligned(1);
enum rmnet_map_commands {
RMNET_MAP_COMMAND_NONE,
RMNET_MAP_COMMAND_FLOW_DISABLE,
RMNET_MAP_COMMAND_FLOW_ENABLE,
/* These should always be the last 2 elements */
RMNET_MAP_COMMAND_UNKNOWN,
RMNET_MAP_COMMAND_ENUM_LENGTH
};
#define RMNET_MAP_COMMAND_REQUEST 0
#define RMNET_MAP_COMMAND_ACK 1
#define RMNET_MAP_COMMAND_UNSUPPORTED 2
#define RMNET_MAP_COMMAND_INVALID 3
#define RMNET_MAP_NO_PAD_BYTES 0
#define RMNET_MAP_ADD_PAD_BYTES 1
struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
struct rmnet_port *port);
struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
int hdrlen,
struct rmnet_port *port,
int pad);
void rmnet_map_command(struct sk_buff *skb, struct rmnet_port *port);
int rmnet_map_checksum_downlink_packet(struct sk_buff *skb, u16 len);
void rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
struct rmnet_port *port,
struct net_device *orig_dev,
int csum_type);
int rmnet_map_process_next_hdr_packet(struct sk_buff *skb, u16 len);
unsigned int rmnet_map_tx_aggregate(struct sk_buff *skb, struct rmnet_port *port,
struct net_device *orig_dev);
void rmnet_map_tx_aggregate_init(struct rmnet_port *port);
void rmnet_map_tx_aggregate_exit(struct rmnet_port *port);
void rmnet_map_update_ul_agg_config(struct rmnet_port *port, u32 size,
u32 count, u32 time);
u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port);
#endif /* _RMNET_MAP_H_ */
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
*/
#include <linux/netdevice.h>
#include "rmnet_config.h"
#include "rmnet_map.h"
#include "rmnet_private.h"
#include "rmnet_vnd.h"
static u8 rmnet_map_do_flow_control(struct sk_buff *skb,
struct rmnet_port *port,
int enable)
{
struct rmnet_map_header *map_header = (void *)skb->data;
struct rmnet_endpoint *ep;
struct net_device *vnd;
u8 mux_id;
int r;
mux_id = map_header->mux_id;
if (mux_id >= RMNET_MAX_LOGICAL_EP) {
kfree_skb(skb);
return RX_HANDLER_CONSUMED;
}
ep = rmnet_get_endpoint(port, mux_id);
if (!ep) {
kfree_skb(skb);
return RX_HANDLER_CONSUMED;
}
vnd = ep->egress_dev;
/* Ignore the ip family and pass the sequence number for both v4 and v6
* sequence. User space does not support creating dedicated flows for
* the 2 protocols
*/
r = rmnet_vnd_do_flow_control(vnd, enable);
if (r) {
kfree_skb(skb);
return RMNET_MAP_COMMAND_UNSUPPORTED;
} else {
return RMNET_MAP_COMMAND_ACK;
}
}
static void rmnet_map_send_ack(struct sk_buff *skb,
unsigned char type,
struct rmnet_port *port)
{
struct rmnet_map_header *map_header = (void *)skb->data;
struct rmnet_map_control_command *cmd;
struct net_device *dev = skb->dev;
if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4)
skb_trim(skb,
skb->len - sizeof(struct rmnet_map_dl_csum_trailer));
skb->protocol = htons(ETH_P_MAP);
/* Command data immediately follows the MAP header */
cmd = (struct rmnet_map_control_command *)(map_header + 1);
cmd->cmd_type = type & 0x03;
netif_tx_lock(dev);
dev->netdev_ops->ndo_start_xmit(skb, dev);
netif_tx_unlock(dev);
}
/* Process MAP command frame and send N/ACK message as appropriate. Message cmd
* name is decoded here and appropriate handler is called.
*/
void rmnet_map_command(struct sk_buff *skb, struct rmnet_port *port)
{
struct rmnet_map_header *map_header = (void *)skb->data;
struct rmnet_map_control_command *cmd;
unsigned char command_name;
unsigned char rc = 0;
/* Command data immediately follows the MAP header */
cmd = (struct rmnet_map_control_command *)(map_header + 1);
command_name = cmd->command_name;
switch (command_name) {
case RMNET_MAP_COMMAND_FLOW_ENABLE:
rc = rmnet_map_do_flow_control(skb, port, 1);
break;
case RMNET_MAP_COMMAND_FLOW_DISABLE:
rc = rmnet_map_do_flow_control(skb, port, 0);
break;
default:
rc = RMNET_MAP_COMMAND_UNSUPPORTED;
kfree_skb(skb);
break;
}
if (rc == RMNET_MAP_COMMAND_ACK)
rmnet_map_send_ack(skb, rc, port);
}
@@ -0,0 +1,719 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2013-2018, 2021, The Linux Foundation. All rights reserved.
*
* RMNET Data MAP protocol
*/
#include <linux/netdevice.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <net/ip6_checksum.h>
#include <linux/bitfield.h>
#include "rmnet_config.h"
#include "rmnet_map.h"
#include "rmnet_private.h"
#include "rmnet_vnd.h"
#define RMNET_MAP_DEAGGR_SPACING 64
#define RMNET_MAP_DEAGGR_HEADROOM (RMNET_MAP_DEAGGR_SPACING / 2)
static __sum16 *rmnet_map_get_csum_field(unsigned char protocol,
const void *txporthdr)
{
if (protocol == IPPROTO_TCP)
return &((struct tcphdr *)txporthdr)->check;
if (protocol == IPPROTO_UDP)
return &((struct udphdr *)txporthdr)->check;
return NULL;
}
static int
rmnet_map_ipv4_dl_csum_trailer(struct sk_buff *skb,
struct rmnet_map_dl_csum_trailer *csum_trailer,
struct rmnet_priv *priv)
{
struct iphdr *ip4h = (struct iphdr *)skb->data;
void *txporthdr = skb->data + ip4h->ihl * 4;
__sum16 *csum_field, pseudo_csum;
__sum16 ip_payload_csum;
/* Computing the checksum over just the IPv4 header--including its
* checksum field--should yield 0. If it doesn't, the IP header
* is bad, so return an error and let the IP layer drop it.
*/
if (ip_fast_csum(ip4h, ip4h->ihl)) {
priv->stats.csum_ip4_header_bad++;
return -EINVAL;
}
/* We don't support checksum offload on IPv4 fragments */
if (ip_is_fragment(ip4h)) {
priv->stats.csum_fragmented_pkt++;
return -EOPNOTSUPP;
}
/* Checksum offload is only supported for UDP and TCP protocols */
csum_field = rmnet_map_get_csum_field(ip4h->protocol, txporthdr);
if (!csum_field) {
priv->stats.csum_err_invalid_transport++;
return -EPROTONOSUPPORT;
}
/* RFC 768: UDP checksum is optional for IPv4, and is 0 if unused */
if (!*csum_field && ip4h->protocol == IPPROTO_UDP) {
priv->stats.csum_skipped++;
return 0;
}
/* The checksum value in the trailer is computed over the entire
* IP packet, including the IP header and payload. To derive the
* transport checksum from this, we first subract the contribution
* of the IP header from the trailer checksum. We then add the
* checksum computed over the pseudo header.
*
* We verified above that the IP header contributes zero to the
* trailer checksum. Therefore the checksum in the trailer is
* just the checksum computed over the IP payload.
* If the IP payload arrives intact, adding the pseudo header
* checksum to the IP payload checksum will yield 0xffff (negative
* zero). This means the trailer checksum and the pseudo checksum
* are additive inverses of each other. Put another way, the
* message passes the checksum test if the trailer checksum value
* is the negated pseudo header checksum.
*
* Knowing this, we don't even need to examine the transport
* header checksum value; it is already accounted for in the
* checksum value found in the trailer.
*/
ip_payload_csum = csum_trailer->csum_value;
pseudo_csum = csum_tcpudp_magic(ip4h->saddr, ip4h->daddr,
ntohs(ip4h->tot_len) - ip4h->ihl * 4,
ip4h->protocol, 0);
/* The cast is required to ensure only the low 16 bits are examined */
if (ip_payload_csum != (__sum16)~pseudo_csum) {
priv->stats.csum_validation_failed++;
return -EINVAL;
}
priv->stats.csum_ok++;
return 0;
}
#if IS_ENABLED(CONFIG_IPV6)
static int
rmnet_map_ipv6_dl_csum_trailer(struct sk_buff *skb,
struct rmnet_map_dl_csum_trailer *csum_trailer,
struct rmnet_priv *priv)
{
struct ipv6hdr *ip6h = (struct ipv6hdr *)skb->data;
void *txporthdr = skb->data + sizeof(*ip6h);
__sum16 *csum_field, pseudo_csum;
__sum16 ip6_payload_csum;
__be16 ip_header_csum;
/* Checksum offload is only supported for UDP and TCP protocols;
* the packet cannot include any IPv6 extension headers
*/
csum_field = rmnet_map_get_csum_field(ip6h->nexthdr, txporthdr);
if (!csum_field) {
priv->stats.csum_err_invalid_transport++;
return -EPROTONOSUPPORT;
}
/* The checksum value in the trailer is computed over the entire
* IP packet, including the IP header and payload. To derive the
* transport checksum from this, we first subract the contribution
* of the IP header from the trailer checksum. We then add the
* checksum computed over the pseudo header.
*/
ip_header_csum = (__force __be16)ip_fast_csum(ip6h, sizeof(*ip6h) / 4);
ip6_payload_csum = csum16_sub(csum_trailer->csum_value, ip_header_csum);
pseudo_csum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
ntohs(ip6h->payload_len),
ip6h->nexthdr, 0);
/* It's sufficient to compare the IP payload checksum with the
* negated pseudo checksum to determine whether the packet
* checksum was good. (See further explanation in comments
* in rmnet_map_ipv4_dl_csum_trailer()).
*
* The cast is required to ensure only the low 16 bits are
* examined.
*/
if (ip6_payload_csum != (__sum16)~pseudo_csum) {
priv->stats.csum_validation_failed++;
return -EINVAL;
}
priv->stats.csum_ok++;
return 0;
}
#else
static int
rmnet_map_ipv6_dl_csum_trailer(struct sk_buff *skb,
struct rmnet_map_dl_csum_trailer *csum_trailer,
struct rmnet_priv *priv)
{
return 0;
}
#endif
static void rmnet_map_complement_ipv4_txporthdr_csum_field(struct iphdr *ip4h)
{
void *txphdr;
u16 *csum;
txphdr = (void *)ip4h + ip4h->ihl * 4;
if (ip4h->protocol == IPPROTO_TCP || ip4h->protocol == IPPROTO_UDP) {
csum = (u16 *)rmnet_map_get_csum_field(ip4h->protocol, txphdr);
*csum = ~(*csum);
}
}
static void
rmnet_map_ipv4_ul_csum_header(struct iphdr *iphdr,
struct rmnet_map_ul_csum_header *ul_header,
struct sk_buff *skb)
{
u16 val;
val = MAP_CSUM_UL_ENABLED_FLAG;
if (iphdr->protocol == IPPROTO_UDP)
val |= MAP_CSUM_UL_UDP_FLAG;
val |= skb->csum_offset & MAP_CSUM_UL_OFFSET_MASK;
ul_header->csum_start_offset = htons(skb_network_header_len(skb));
ul_header->csum_info = htons(val);
skb->ip_summed = CHECKSUM_NONE;
rmnet_map_complement_ipv4_txporthdr_csum_field(iphdr);
}
#if IS_ENABLED(CONFIG_IPV6)
static void
rmnet_map_complement_ipv6_txporthdr_csum_field(struct ipv6hdr *ip6h)
{
void *txphdr;
u16 *csum;
txphdr = ip6h + 1;
if (ip6h->nexthdr == IPPROTO_TCP || ip6h->nexthdr == IPPROTO_UDP) {
csum = (u16 *)rmnet_map_get_csum_field(ip6h->nexthdr, txphdr);
*csum = ~(*csum);
}
}
static void
rmnet_map_ipv6_ul_csum_header(struct ipv6hdr *ipv6hdr,
struct rmnet_map_ul_csum_header *ul_header,
struct sk_buff *skb)
{
u16 val;
val = MAP_CSUM_UL_ENABLED_FLAG;
if (ipv6hdr->nexthdr == IPPROTO_UDP)
val |= MAP_CSUM_UL_UDP_FLAG;
val |= skb->csum_offset & MAP_CSUM_UL_OFFSET_MASK;
ul_header->csum_start_offset = htons(skb_network_header_len(skb));
ul_header->csum_info = htons(val);
skb->ip_summed = CHECKSUM_NONE;
rmnet_map_complement_ipv6_txporthdr_csum_field(ipv6hdr);
}
#else
static void
rmnet_map_ipv6_ul_csum_header(void *ip6hdr,
struct rmnet_map_ul_csum_header *ul_header,
struct sk_buff *skb)
{
}
#endif
static void rmnet_map_v5_checksum_uplink_packet(struct sk_buff *skb,
struct rmnet_port *port,
struct net_device *orig_dev)
{
struct rmnet_priv *priv = netdev_priv(orig_dev);
struct rmnet_map_v5_csum_header *ul_header;
ul_header = skb_push(skb, sizeof(*ul_header));
memset(ul_header, 0, sizeof(*ul_header));
ul_header->header_info = u8_encode_bits(RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD,
MAPV5_HDRINFO_HDR_TYPE_FMASK);
if (skb->ip_summed == CHECKSUM_PARTIAL) {
void *iph = ip_hdr(skb);
__sum16 *check;
void *trans;
u8 proto;
if (skb->protocol == htons(ETH_P_IP)) {
u16 ip_len = ((struct iphdr *)iph)->ihl * 4;
proto = ((struct iphdr *)iph)->protocol;
trans = iph + ip_len;
} else if (IS_ENABLED(CONFIG_IPV6) &&
skb->protocol == htons(ETH_P_IPV6)) {
u16 ip_len = sizeof(struct ipv6hdr);
proto = ((struct ipv6hdr *)iph)->nexthdr;
trans = iph + ip_len;
} else {
priv->stats.csum_err_invalid_ip_version++;
goto sw_csum;
}
check = rmnet_map_get_csum_field(proto, trans);
if (check) {
skb->ip_summed = CHECKSUM_NONE;
/* Ask for checksum offloading */
ul_header->csum_info |= MAPV5_CSUMINFO_VALID_FLAG;
priv->stats.csum_hw++;
return;
}
}
sw_csum:
priv->stats.csum_sw++;
}
/* Adds MAP header to front of skb->data
* Padding is calculated and set appropriately in MAP header. Mux ID is
* initialized to 0.
*/
struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
int hdrlen,
struct rmnet_port *port,
int pad)
{
struct rmnet_map_header *map_header;
u32 padding, map_datalen;
map_datalen = skb->len - hdrlen;
map_header = (struct rmnet_map_header *)
skb_push(skb, sizeof(struct rmnet_map_header));
memset(map_header, 0, sizeof(struct rmnet_map_header));
/* Set next_hdr bit for csum offload packets */
if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5)
map_header->flags |= MAP_NEXT_HEADER_FLAG;
if (pad == RMNET_MAP_NO_PAD_BYTES) {
map_header->pkt_len = htons(map_datalen);
return map_header;
}
BUILD_BUG_ON(MAP_PAD_LEN_MASK < 3);
padding = ALIGN(map_datalen, 4) - map_datalen;
if (padding == 0)
goto done;
if (skb_tailroom(skb) < padding)
return NULL;
skb_put_zero(skb, padding);
done:
map_header->pkt_len = htons(map_datalen + padding);
/* This is a data packet, so the CMD bit is 0 */
map_header->flags = padding & MAP_PAD_LEN_MASK;
return map_header;
}
u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port)
{
struct rmnet_map_v5_csum_header *next_hdr = NULL;
struct rmnet_map_header *maph;
void *data = skb->data;
u32 packet_len;
if (skb->len < sizeof(*maph))
return 0;
maph = (struct rmnet_map_header *)skb->data;
/* Some hardware can send us empty frames. Catch them */
if (!maph->pkt_len)
return 0;
packet_len = ntohs(maph->pkt_len) + sizeof(*maph);
if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4) {
packet_len += sizeof(struct rmnet_map_dl_csum_trailer);
} else if ((port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
!(maph->flags & MAP_CMD_FLAG)) {
/* Mapv5 data pkt without csum hdr is invalid */
if (!(maph->flags & MAP_NEXT_HEADER_FLAG))
return 0;
packet_len += sizeof(*next_hdr);
next_hdr = data + sizeof(*maph);
}
if (skb->len < packet_len)
return 0;
if (next_hdr &&
u8_get_bits(next_hdr->header_info, MAPV5_HDRINFO_HDR_TYPE_FMASK) !=
RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD)
return 0;
return packet_len;
}
/* Deaggregates a single packet
* A whole new buffer is allocated for each portion of an aggregated frame.
* Caller should keep calling deaggregate() on the source skb until 0 is
* returned, indicating that there are no more packets to deaggregate. Caller
* is responsible for freeing the original skb.
*/
struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
struct rmnet_port *port)
{
struct sk_buff *skbn;
u32 packet_len;
packet_len = rmnet_map_validate_packet_len(skb, port);
if (!packet_len)
return NULL;
skbn = alloc_skb(packet_len + RMNET_MAP_DEAGGR_SPACING, GFP_ATOMIC);
if (!skbn)
return NULL;
skb_reserve(skbn, RMNET_MAP_DEAGGR_HEADROOM);
skb_put(skbn, packet_len);
memcpy(skbn->data, skb->data, packet_len);
skb_pull(skb, packet_len);
return skbn;
}
/* Validates packet checksums. Function takes a pointer to
* the beginning of a buffer which contains the IP payload +
* padding + checksum trailer.
* Only IPv4 and IPv6 are supported along with TCP & UDP.
* Fragmented or tunneled packets are not supported.
*/
int rmnet_map_checksum_downlink_packet(struct sk_buff *skb, u16 len)
{
struct rmnet_priv *priv = netdev_priv(skb->dev);
struct rmnet_map_dl_csum_trailer *csum_trailer;
if (unlikely(!(skb->dev->features & NETIF_F_RXCSUM))) {
priv->stats.csum_sw++;
return -EOPNOTSUPP;
}
csum_trailer = (struct rmnet_map_dl_csum_trailer *)(skb->data + len);
if (!(csum_trailer->flags & MAP_CSUM_DL_VALID_FLAG)) {
priv->stats.csum_valid_unset++;
return -EINVAL;
}
if (skb->protocol == htons(ETH_P_IP))
return rmnet_map_ipv4_dl_csum_trailer(skb, csum_trailer, priv);
if (IS_ENABLED(CONFIG_IPV6) && skb->protocol == htons(ETH_P_IPV6))
return rmnet_map_ipv6_dl_csum_trailer(skb, csum_trailer, priv);
priv->stats.csum_err_invalid_ip_version++;
return -EPROTONOSUPPORT;
}
static void rmnet_map_v4_checksum_uplink_packet(struct sk_buff *skb,
struct net_device *orig_dev)
{
struct rmnet_priv *priv = netdev_priv(orig_dev);
struct rmnet_map_ul_csum_header *ul_header;
void *iphdr;
ul_header = (struct rmnet_map_ul_csum_header *)
skb_push(skb, sizeof(struct rmnet_map_ul_csum_header));
if (unlikely(!(orig_dev->features &
(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM))))
goto sw_csum;
if (skb->ip_summed != CHECKSUM_PARTIAL)
goto sw_csum;
iphdr = (char *)ul_header +
sizeof(struct rmnet_map_ul_csum_header);
if (skb->protocol == htons(ETH_P_IP)) {
rmnet_map_ipv4_ul_csum_header(iphdr, ul_header, skb);
priv->stats.csum_hw++;
return;
}
if (IS_ENABLED(CONFIG_IPV6) && skb->protocol == htons(ETH_P_IPV6)) {
rmnet_map_ipv6_ul_csum_header(iphdr, ul_header, skb);
priv->stats.csum_hw++;
return;
}
priv->stats.csum_err_invalid_ip_version++;
sw_csum:
memset(ul_header, 0, sizeof(*ul_header));
priv->stats.csum_sw++;
}
/* Generates UL checksum meta info header for IPv4 and IPv6 over TCP and UDP
* packets that are supported for UL checksum offload.
*/
void rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
struct rmnet_port *port,
struct net_device *orig_dev,
int csum_type)
{
switch (csum_type) {
case RMNET_FLAGS_EGRESS_MAP_CKSUMV4:
rmnet_map_v4_checksum_uplink_packet(skb, orig_dev);
break;
case RMNET_FLAGS_EGRESS_MAP_CKSUMV5:
rmnet_map_v5_checksum_uplink_packet(skb, port, orig_dev);
break;
default:
break;
}
}
/* Process a MAPv5 packet header */
int rmnet_map_process_next_hdr_packet(struct sk_buff *skb,
u16 len)
{
struct rmnet_priv *priv = netdev_priv(skb->dev);
struct rmnet_map_v5_csum_header *next_hdr;
u8 nexthdr_type;
next_hdr = (struct rmnet_map_v5_csum_header *)(skb->data +
sizeof(struct rmnet_map_header));
nexthdr_type = u8_get_bits(next_hdr->header_info,
MAPV5_HDRINFO_HDR_TYPE_FMASK);
if (nexthdr_type != RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD)
return -EINVAL;
if (unlikely(!(skb->dev->features & NETIF_F_RXCSUM))) {
priv->stats.csum_sw++;
} else if (next_hdr->csum_info & MAPV5_CSUMINFO_VALID_FLAG) {
priv->stats.csum_ok++;
skb->ip_summed = CHECKSUM_UNNECESSARY;
} else {
priv->stats.csum_valid_unset++;
}
/* Pull csum v5 header */
skb_pull(skb, sizeof(*next_hdr));
return 0;
}
#define RMNET_AGG_BYPASS_TIME_NSEC 10000000L
static void reset_aggr_params(struct rmnet_port *port)
{
port->skbagg_head = NULL;
port->agg_count = 0;
port->agg_state = 0;
memset(&port->agg_time, 0, sizeof(struct timespec64));
}
static void rmnet_send_skb(struct rmnet_port *port, struct sk_buff *skb)
{
if (skb_needs_linearize(skb, port->dev->features)) {
if (unlikely(__skb_linearize(skb))) {
struct rmnet_priv *priv;
priv = netdev_priv(port->rmnet_dev);
this_cpu_inc(priv->pcpu_stats->stats.tx_drops);
dev_kfree_skb_any(skb);
return;
}
}
dev_queue_xmit(skb);
}
static void rmnet_map_flush_tx_packet_work(struct work_struct *work)
{
struct sk_buff *skb = NULL;
struct rmnet_port *port;
port = container_of(work, struct rmnet_port, agg_wq);
spin_lock_bh(&port->agg_lock);
if (likely(port->agg_state == -EINPROGRESS)) {
/* Buffer may have already been shipped out */
if (likely(port->skbagg_head)) {
skb = port->skbagg_head;
reset_aggr_params(port);
}
port->agg_state = 0;
}
spin_unlock_bh(&port->agg_lock);
if (skb)
rmnet_send_skb(port, skb);
}
static enum hrtimer_restart rmnet_map_flush_tx_packet_queue(struct hrtimer *t)
{
struct rmnet_port *port;
port = container_of(t, struct rmnet_port, hrtimer);
schedule_work(&port->agg_wq);
return HRTIMER_NORESTART;
}
unsigned int rmnet_map_tx_aggregate(struct sk_buff *skb, struct rmnet_port *port,
struct net_device *orig_dev)
{
struct timespec64 diff, last;
unsigned int len = skb->len;
struct sk_buff *agg_skb;
int size;
spin_lock_bh(&port->agg_lock);
memcpy(&last, &port->agg_last, sizeof(struct timespec64));
ktime_get_real_ts64(&port->agg_last);
if (!port->skbagg_head) {
/* Check to see if we should agg first. If the traffic is very
* sparse, don't aggregate.
*/
new_packet:
diff = timespec64_sub(port->agg_last, last);
size = port->egress_agg_params.bytes - skb->len;
if (size < 0) {
/* dropped */
spin_unlock_bh(&port->agg_lock);
return 0;
}
if (diff.tv_sec > 0 || diff.tv_nsec > RMNET_AGG_BYPASS_TIME_NSEC ||
size == 0)
goto no_aggr;
port->skbagg_head = skb_copy_expand(skb, 0, size, GFP_ATOMIC);
if (!port->skbagg_head)
goto no_aggr;
dev_kfree_skb_any(skb);
port->skbagg_head->protocol = htons(ETH_P_MAP);
port->agg_count = 1;
ktime_get_real_ts64(&port->agg_time);
skb_frag_list_init(port->skbagg_head);
goto schedule;
}
diff = timespec64_sub(port->agg_last, port->agg_time);
size = port->egress_agg_params.bytes - port->skbagg_head->len;
if (skb->len > size) {
agg_skb = port->skbagg_head;
reset_aggr_params(port);
spin_unlock_bh(&port->agg_lock);
hrtimer_cancel(&port->hrtimer);
rmnet_send_skb(port, agg_skb);
spin_lock_bh(&port->agg_lock);
goto new_packet;
}
if (skb_has_frag_list(port->skbagg_head))
port->skbagg_tail->next = skb;
else
skb_shinfo(port->skbagg_head)->frag_list = skb;
port->skbagg_head->len += skb->len;
port->skbagg_head->data_len += skb->len;
port->skbagg_head->truesize += skb->truesize;
port->skbagg_tail = skb;
port->agg_count++;
if (diff.tv_sec > 0 || diff.tv_nsec > port->egress_agg_params.time_nsec ||
port->agg_count >= port->egress_agg_params.count ||
port->skbagg_head->len == port->egress_agg_params.bytes) {
agg_skb = port->skbagg_head;
reset_aggr_params(port);
spin_unlock_bh(&port->agg_lock);
hrtimer_cancel(&port->hrtimer);
rmnet_send_skb(port, agg_skb);
return len;
}
schedule:
if (!hrtimer_active(&port->hrtimer) && port->agg_state != -EINPROGRESS) {
port->agg_state = -EINPROGRESS;
hrtimer_start(&port->hrtimer,
ns_to_ktime(port->egress_agg_params.time_nsec),
HRTIMER_MODE_REL);
}
spin_unlock_bh(&port->agg_lock);
return len;
no_aggr:
spin_unlock_bh(&port->agg_lock);
skb->protocol = htons(ETH_P_MAP);
dev_queue_xmit(skb);
return len;
}
void rmnet_map_update_ul_agg_config(struct rmnet_port *port, u32 size,
u32 count, u32 time)
{
spin_lock_bh(&port->agg_lock);
port->egress_agg_params.bytes = size;
WRITE_ONCE(port->egress_agg_params.count, count);
port->egress_agg_params.time_nsec = time * NSEC_PER_USEC;
spin_unlock_bh(&port->agg_lock);
}
void rmnet_map_tx_aggregate_init(struct rmnet_port *port)
{
hrtimer_setup(&port->hrtimer, rmnet_map_flush_tx_packet_queue, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
spin_lock_init(&port->agg_lock);
rmnet_map_update_ul_agg_config(port, 4096, 1, 800);
INIT_WORK(&port->agg_wq, rmnet_map_flush_tx_packet_work);
}
void rmnet_map_tx_aggregate_exit(struct rmnet_port *port)
{
hrtimer_cancel(&port->hrtimer);
cancel_work_sync(&port->agg_wq);
spin_lock_bh(&port->agg_lock);
if (port->agg_state == -EINPROGRESS) {
if (port->skbagg_head) {
dev_kfree_skb_any(port->skbagg_head);
reset_aggr_params(port);
}
port->agg_state = 0;
}
spin_unlock_bh(&port->agg_lock);
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2013-2014, 2016-2018 The Linux Foundation. All rights reserved.
*/
#ifndef _RMNET_PRIVATE_H_
#define _RMNET_PRIVATE_H_
#define RMNET_MAX_PACKET_SIZE 16384
#define RMNET_DFLT_PACKET_SIZE 1500
#define RMNET_NEEDED_HEADROOM 16
#define RMNET_TX_QUEUE_LEN 1000
/* Replace skb->dev to a virtual rmnet device and pass up the stack */
#define RMNET_EPMODE_VND (1)
/* Pass the frame directly to another device with dev_queue_xmit() */
#define RMNET_EPMODE_BRIDGE (2)
#endif /* _RMNET_PRIVATE_H_ */
@@ -0,0 +1,409 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
*
* RMNET Data virtual network driver
*/
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/if_arp.h>
#include <net/pkt_sched.h>
#include "rmnet_config.h"
#include "rmnet_handlers.h"
#include "rmnet_private.h"
#include "rmnet_map.h"
#include "rmnet_vnd.h"
/* RX/TX Fixup */
void rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_pcpu_stats *pcpu_ptr;
pcpu_ptr = this_cpu_ptr(priv->pcpu_stats);
u64_stats_update_begin(&pcpu_ptr->syncp);
pcpu_ptr->stats.rx_pkts++;
pcpu_ptr->stats.rx_bytes += skb->len;
u64_stats_update_end(&pcpu_ptr->syncp);
}
void rmnet_vnd_tx_fixup_len(unsigned int len, struct net_device *dev)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_pcpu_stats *pcpu_ptr;
pcpu_ptr = this_cpu_ptr(priv->pcpu_stats);
u64_stats_update_begin(&pcpu_ptr->syncp);
pcpu_ptr->stats.tx_pkts++;
pcpu_ptr->stats.tx_bytes += len;
u64_stats_update_end(&pcpu_ptr->syncp);
}
void rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev)
{
rmnet_vnd_tx_fixup_len(skb->len, dev);
}
/* Network Device Operations */
static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct rmnet_priv *priv;
priv = netdev_priv(dev);
if (priv->real_dev) {
rmnet_egress_handler(skb);
} else {
this_cpu_inc(priv->pcpu_stats->stats.tx_drops);
kfree_skb(skb);
}
return NETDEV_TX_OK;
}
static int rmnet_vnd_headroom(struct rmnet_port *port)
{
u32 headroom;
headroom = sizeof(struct rmnet_map_header);
if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV4)
headroom += sizeof(struct rmnet_map_ul_csum_header);
return headroom;
}
static int rmnet_vnd_change_mtu(struct net_device *rmnet_dev, int new_mtu)
{
struct rmnet_priv *priv = netdev_priv(rmnet_dev);
struct rmnet_port *port;
u32 headroom;
port = rmnet_get_port_rtnl(priv->real_dev);
headroom = rmnet_vnd_headroom(port);
if (new_mtu < 0 || new_mtu > RMNET_MAX_PACKET_SIZE ||
new_mtu > (priv->real_dev->mtu - headroom))
return -EINVAL;
WRITE_ONCE(rmnet_dev->mtu, new_mtu);
return 0;
}
static int rmnet_vnd_get_iflink(const struct net_device *dev)
{
struct rmnet_priv *priv = netdev_priv(dev);
return READ_ONCE(priv->real_dev->ifindex);
}
static int rmnet_vnd_init(struct net_device *dev)
{
struct rmnet_priv *priv = netdev_priv(dev);
int err;
priv->pcpu_stats = alloc_percpu(struct rmnet_pcpu_stats);
if (!priv->pcpu_stats)
return -ENOMEM;
err = gro_cells_init(&priv->gro_cells, dev);
if (err) {
free_percpu(priv->pcpu_stats);
return err;
}
return 0;
}
static void rmnet_vnd_uninit(struct net_device *dev)
{
struct rmnet_priv *priv = netdev_priv(dev);
gro_cells_destroy(&priv->gro_cells);
free_percpu(priv->pcpu_stats);
}
static void rmnet_get_stats64(struct net_device *dev,
struct rtnl_link_stats64 *s)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_vnd_stats total_stats = { };
struct rmnet_pcpu_stats *pcpu_ptr;
struct rmnet_vnd_stats snapshot;
unsigned int cpu, start;
for_each_possible_cpu(cpu) {
pcpu_ptr = per_cpu_ptr(priv->pcpu_stats, cpu);
do {
start = u64_stats_fetch_begin(&pcpu_ptr->syncp);
snapshot = pcpu_ptr->stats; /* struct assignment */
} while (u64_stats_fetch_retry(&pcpu_ptr->syncp, start));
total_stats.rx_pkts += snapshot.rx_pkts;
total_stats.rx_bytes += snapshot.rx_bytes;
total_stats.tx_pkts += snapshot.tx_pkts;
total_stats.tx_bytes += snapshot.tx_bytes;
total_stats.tx_drops += snapshot.tx_drops;
}
s->rx_packets = total_stats.rx_pkts;
s->rx_bytes = total_stats.rx_bytes;
s->tx_packets = total_stats.tx_pkts;
s->tx_bytes = total_stats.tx_bytes;
s->tx_dropped = total_stats.tx_drops;
}
static const struct net_device_ops rmnet_vnd_ops = {
.ndo_start_xmit = rmnet_vnd_start_xmit,
.ndo_change_mtu = rmnet_vnd_change_mtu,
.ndo_get_iflink = rmnet_vnd_get_iflink,
.ndo_add_slave = rmnet_add_bridge,
.ndo_del_slave = rmnet_del_bridge,
.ndo_init = rmnet_vnd_init,
.ndo_uninit = rmnet_vnd_uninit,
.ndo_get_stats64 = rmnet_get_stats64,
};
static const char rmnet_gstrings_stats[][ETH_GSTRING_LEN] = {
"Checksum ok",
"Bad IPv4 header checksum",
"Checksum valid bit not set",
"Checksum validation failed",
"Checksum error bad buffer",
"Checksum error bad ip version",
"Checksum error bad transport",
"Checksum skipped on ip fragment",
"Checksum skipped",
"Checksum computed in software",
"Checksum computed in hardware",
};
static void rmnet_get_strings(struct net_device *dev, u32 stringset, u8 *buf)
{
switch (stringset) {
case ETH_SS_STATS:
memcpy(buf, &rmnet_gstrings_stats,
sizeof(rmnet_gstrings_stats));
break;
}
}
static int rmnet_get_sset_count(struct net_device *dev, int sset)
{
switch (sset) {
case ETH_SS_STATS:
return ARRAY_SIZE(rmnet_gstrings_stats);
default:
return -EOPNOTSUPP;
}
}
static void rmnet_get_ethtool_stats(struct net_device *dev,
struct ethtool_stats *stats, u64 *data)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_priv_stats *st = &priv->stats;
if (!data)
return;
memcpy(data, st, ARRAY_SIZE(rmnet_gstrings_stats) * sizeof(u64));
}
static int rmnet_get_coalesce(struct net_device *dev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_port *port;
port = rmnet_get_port_rtnl(priv->real_dev);
memset(kernel_coal, 0, sizeof(*kernel_coal));
kernel_coal->tx_aggr_max_bytes = port->egress_agg_params.bytes;
kernel_coal->tx_aggr_max_frames = port->egress_agg_params.count;
kernel_coal->tx_aggr_time_usecs = div_u64(port->egress_agg_params.time_nsec,
NSEC_PER_USEC);
return 0;
}
static int rmnet_set_coalesce(struct net_device *dev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_port *port;
port = rmnet_get_port_rtnl(priv->real_dev);
if (kernel_coal->tx_aggr_max_frames < 1 || kernel_coal->tx_aggr_max_frames > 64)
return -EINVAL;
if (kernel_coal->tx_aggr_max_bytes > 32768)
return -EINVAL;
rmnet_map_update_ul_agg_config(port, kernel_coal->tx_aggr_max_bytes,
kernel_coal->tx_aggr_max_frames,
kernel_coal->tx_aggr_time_usecs);
return 0;
}
static const struct ethtool_ops rmnet_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_TX_AGGR,
.get_coalesce = rmnet_get_coalesce,
.set_coalesce = rmnet_set_coalesce,
.get_ethtool_stats = rmnet_get_ethtool_stats,
.get_strings = rmnet_get_strings,
.get_sset_count = rmnet_get_sset_count,
};
/* Called by kernel whenever a new rmnet<n> device is created. Sets MTU,
* flags, ARP type, needed headroom, etc...
*/
void rmnet_vnd_setup(struct net_device *rmnet_dev)
{
rmnet_dev->netdev_ops = &rmnet_vnd_ops;
rmnet_dev->mtu = RMNET_DFLT_PACKET_SIZE;
rmnet_dev->needed_headroom = RMNET_NEEDED_HEADROOM;
eth_hw_addr_random(rmnet_dev);
rmnet_dev->tx_queue_len = RMNET_TX_QUEUE_LEN;
/* Raw IP mode */
rmnet_dev->header_ops = NULL; /* No header */
rmnet_dev->type = ARPHRD_RAWIP;
rmnet_dev->hard_header_len = 0;
rmnet_dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
rmnet_dev->needs_free_netdev = true;
rmnet_dev->ethtool_ops = &rmnet_ethtool_ops;
rmnet_dev->lltx = true;
/* This perm addr will be used as interface identifier by IPv6 */
rmnet_dev->addr_assign_type = NET_ADDR_RANDOM;
eth_random_addr(rmnet_dev->perm_addr);
}
/* Exposed API */
int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
struct rmnet_port *port,
struct net_device *real_dev,
struct rmnet_endpoint *ep,
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(rmnet_dev);
u32 headroom;
int rc;
if (rmnet_get_endpoint(port, id)) {
NL_SET_ERR_MSG_MOD(extack, "MUX ID already exists");
return -EBUSY;
}
rmnet_dev->hw_features = NETIF_F_RXCSUM;
rmnet_dev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
rmnet_dev->hw_features |= NETIF_F_SG;
priv->real_dev = real_dev;
headroom = rmnet_vnd_headroom(port);
if (rmnet_vnd_change_mtu(rmnet_dev, real_dev->mtu - headroom)) {
NL_SET_ERR_MSG_MOD(extack, "Invalid MTU on real dev");
return -EINVAL;
}
rc = register_netdevice(rmnet_dev);
if (!rc) {
ep->egress_dev = rmnet_dev;
ep->mux_id = id;
port->nr_rmnet_devs++;
rmnet_dev->rtnl_link_ops = &rmnet_link_ops;
priv->mux_id = id;
netdev_dbg(rmnet_dev, "rmnet dev created\n");
}
return rc;
}
int rmnet_vnd_dellink(u8 id, struct rmnet_port *port,
struct rmnet_endpoint *ep)
{
if (id >= RMNET_MAX_LOGICAL_EP || !ep->egress_dev)
return -EINVAL;
ep->egress_dev = NULL;
port->nr_rmnet_devs--;
return 0;
}
int rmnet_vnd_do_flow_control(struct net_device *rmnet_dev, int enable)
{
netdev_dbg(rmnet_dev, "Setting VND TX queue state to %d\n", enable);
/* Although we expect similar number of enable/disable
* commands, optimize for the disable. That is more
* latency sensitive than enable
*/
if (unlikely(enable))
netif_wake_queue(rmnet_dev);
else
netif_stop_queue(rmnet_dev);
return 0;
}
int rmnet_vnd_validate_real_dev_mtu(struct net_device *real_dev)
{
struct hlist_node *tmp_ep;
struct rmnet_endpoint *ep;
struct rmnet_port *port;
unsigned long bkt_ep;
u32 headroom;
port = rmnet_get_port_rtnl(real_dev);
headroom = rmnet_vnd_headroom(port);
hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) {
if (ep->egress_dev->mtu > (real_dev->mtu - headroom))
return -1;
}
return 0;
}
int rmnet_vnd_update_dev_mtu(struct rmnet_port *port,
struct net_device *real_dev)
{
struct hlist_node *tmp_ep;
struct rmnet_endpoint *ep;
unsigned long bkt_ep;
u32 headroom;
headroom = rmnet_vnd_headroom(port);
hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) {
if (ep->egress_dev->mtu <= (real_dev->mtu - headroom))
continue;
if (rmnet_vnd_change_mtu(ep->egress_dev,
real_dev->mtu - headroom))
return -1;
}
return 0;
}
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* RMNET Data Virtual Network Device APIs
*/
#ifndef _RMNET_VND_H_
#define _RMNET_VND_H_
int rmnet_vnd_do_flow_control(struct net_device *dev, int enable);
int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
struct rmnet_port *port,
struct net_device *real_dev,
struct rmnet_endpoint *ep,
struct netlink_ext_ack *extack);
int rmnet_vnd_dellink(u8 id, struct rmnet_port *port,
struct rmnet_endpoint *ep);
void rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev);
void rmnet_vnd_tx_fixup_len(unsigned int len, struct net_device *dev);
void rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev);
void rmnet_vnd_setup(struct net_device *dev);
int rmnet_vnd_validate_real_dev_mtu(struct net_device *real_dev);
int rmnet_vnd_update_dev_mtu(struct rmnet_port *port,
struct net_device *real_dev);
#endif /* _RMNET_VND_H_ */