1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/net/ethernet/mediatek/mtk_ppe.c
Sujuan Chen b230812b9d net: ethernet: mtk_wed: introduce partial AMSDU offload support for MT7988
Introduce partial AMSDU offload support for MT7988 SoC in order to merge
in hw packets belonging to the same AMSDU before passing them to the
WLAN nic.

Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
2023-09-19 18:27:56 +02:00

1106 lines
27 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <net/dst_metadata.h>
#include <net/dsa.h>
#include "mtk_eth_soc.h"
#include "mtk_ppe.h"
#include "mtk_ppe_regs.h"
static DEFINE_SPINLOCK(ppe_lock);
static const struct rhashtable_params mtk_flow_l2_ht_params = {
.head_offset = offsetof(struct mtk_flow_entry, l2_node),
.key_offset = offsetof(struct mtk_flow_entry, data.bridge),
.key_len = offsetof(struct mtk_foe_bridge, key_end),
.automatic_shrinking = true,
};
static void ppe_w32(struct mtk_ppe *ppe, u32 reg, u32 val)
{
writel(val, ppe->base + reg);
}
static u32 ppe_r32(struct mtk_ppe *ppe, u32 reg)
{
return readl(ppe->base + reg);
}
static u32 ppe_m32(struct mtk_ppe *ppe, u32 reg, u32 mask, u32 set)
{
u32 val;
val = ppe_r32(ppe, reg);
val &= ~mask;
val |= set;
ppe_w32(ppe, reg, val);
return val;
}
static u32 ppe_set(struct mtk_ppe *ppe, u32 reg, u32 val)
{
return ppe_m32(ppe, reg, 0, val);
}
static u32 ppe_clear(struct mtk_ppe *ppe, u32 reg, u32 val)
{
return ppe_m32(ppe, reg, val, 0);
}
static u32 mtk_eth_timestamp(struct mtk_eth *eth)
{
return mtk_r32(eth, 0x0010) & mtk_get_ib1_ts_mask(eth);
}
static int mtk_ppe_wait_busy(struct mtk_ppe *ppe)
{
int ret;
u32 val;
ret = readl_poll_timeout(ppe->base + MTK_PPE_GLO_CFG, val,
!(val & MTK_PPE_GLO_CFG_BUSY),
20, MTK_PPE_WAIT_TIMEOUT_US);
if (ret)
dev_err(ppe->dev, "PPE table busy");
return ret;
}
static int mtk_ppe_mib_wait_busy(struct mtk_ppe *ppe)
{
int ret;
u32 val;
ret = readl_poll_timeout(ppe->base + MTK_PPE_MIB_SER_CR, val,
!(val & MTK_PPE_MIB_SER_CR_ST),
20, MTK_PPE_WAIT_TIMEOUT_US);
if (ret)
dev_err(ppe->dev, "MIB table busy");
return ret;
}
static int mtk_mib_entry_read(struct mtk_ppe *ppe, u16 index, u64 *bytes, u64 *packets)
{
u32 val, cnt_r0, cnt_r1, cnt_r2;
int ret;
val = FIELD_PREP(MTK_PPE_MIB_SER_CR_ADDR, index) | MTK_PPE_MIB_SER_CR_ST;
ppe_w32(ppe, MTK_PPE_MIB_SER_CR, val);
ret = mtk_ppe_mib_wait_busy(ppe);
if (ret)
return ret;
cnt_r0 = readl(ppe->base + MTK_PPE_MIB_SER_R0);
cnt_r1 = readl(ppe->base + MTK_PPE_MIB_SER_R1);
cnt_r2 = readl(ppe->base + MTK_PPE_MIB_SER_R2);
if (mtk_is_netsys_v3_or_greater(ppe->eth)) {
/* 64 bit for each counter */
u32 cnt_r3 = readl(ppe->base + MTK_PPE_MIB_SER_R3);
*bytes = ((u64)cnt_r1 << 32) | cnt_r0;
*packets = ((u64)cnt_r3 << 32) | cnt_r2;
} else {
/* 48 bit byte counter, 40 bit packet counter */
u32 byte_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R0_BYTE_CNT_LOW, cnt_r0);
u32 byte_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R1_BYTE_CNT_HIGH, cnt_r1);
u32 pkt_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R1_PKT_CNT_LOW, cnt_r1);
u32 pkt_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH, cnt_r2);
*bytes = ((u64)byte_cnt_high << 32) | byte_cnt_low;
*packets = ((u64)pkt_cnt_high << 16) | pkt_cnt_low;
}
return 0;
}
static void mtk_ppe_cache_clear(struct mtk_ppe *ppe)
{
ppe_set(ppe, MTK_PPE_CACHE_CTL, MTK_PPE_CACHE_CTL_CLEAR);
ppe_clear(ppe, MTK_PPE_CACHE_CTL, MTK_PPE_CACHE_CTL_CLEAR);
}
static void mtk_ppe_cache_enable(struct mtk_ppe *ppe, bool enable)
{
mtk_ppe_cache_clear(ppe);
ppe_m32(ppe, MTK_PPE_CACHE_CTL, MTK_PPE_CACHE_CTL_EN,
enable * MTK_PPE_CACHE_CTL_EN);
}
static u32 mtk_ppe_hash_entry(struct mtk_eth *eth, struct mtk_foe_entry *e)
{
u32 hv1, hv2, hv3;
u32 hash;
switch (mtk_get_ib1_pkt_type(eth, e->ib1)) {
case MTK_PPE_PKT_TYPE_IPV4_ROUTE:
case MTK_PPE_PKT_TYPE_IPV4_HNAPT:
hv1 = e->ipv4.orig.ports;
hv2 = e->ipv4.orig.dest_ip;
hv3 = e->ipv4.orig.src_ip;
break;
case MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T:
case MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T:
hv1 = e->ipv6.src_ip[3] ^ e->ipv6.dest_ip[3];
hv1 ^= e->ipv6.ports;
hv2 = e->ipv6.src_ip[2] ^ e->ipv6.dest_ip[2];
hv2 ^= e->ipv6.dest_ip[0];
hv3 = e->ipv6.src_ip[1] ^ e->ipv6.dest_ip[1];
hv3 ^= e->ipv6.src_ip[0];
break;
case MTK_PPE_PKT_TYPE_IPV4_DSLITE:
case MTK_PPE_PKT_TYPE_IPV6_6RD:
default:
WARN_ON_ONCE(1);
return MTK_PPE_HASH_MASK;
}
hash = (hv1 & hv2) | ((~hv1) & hv3);
hash = (hash >> 24) | ((hash & 0xffffff) << 8);
hash ^= hv1 ^ hv2 ^ hv3;
hash ^= hash >> 16;
hash <<= (ffs(eth->soc->hash_offset) - 1);
hash &= MTK_PPE_ENTRIES - 1;
return hash;
}
static inline struct mtk_foe_mac_info *
mtk_foe_entry_l2(struct mtk_eth *eth, struct mtk_foe_entry *entry)
{
int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
if (type == MTK_PPE_PKT_TYPE_BRIDGE)
return &entry->bridge.l2;
if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE)
return &entry->ipv6.l2;
return &entry->ipv4.l2;
}
static inline u32 *
mtk_foe_entry_ib2(struct mtk_eth *eth, struct mtk_foe_entry *entry)
{
int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
if (type == MTK_PPE_PKT_TYPE_BRIDGE)
return &entry->bridge.ib2;
if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE)
return &entry->ipv6.ib2;
return &entry->ipv4.ib2;
}
int mtk_foe_entry_prepare(struct mtk_eth *eth, struct mtk_foe_entry *entry,
int type, int l4proto, u8 pse_port, u8 *src_mac,
u8 *dest_mac)
{
struct mtk_foe_mac_info *l2;
u32 ports_pad, val;
memset(entry, 0, sizeof(*entry));
if (mtk_is_netsys_v2_or_greater(eth)) {
val = FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND) |
FIELD_PREP(MTK_FOE_IB1_PACKET_TYPE_V2, type) |
FIELD_PREP(MTK_FOE_IB1_UDP, l4proto == IPPROTO_UDP) |
MTK_FOE_IB1_BIND_CACHE_V2 | MTK_FOE_IB1_BIND_TTL_V2;
entry->ib1 = val;
val = FIELD_PREP(MTK_FOE_IB2_DEST_PORT_V2, pse_port) |
FIELD_PREP(MTK_FOE_IB2_PORT_AG_V2, 0xf);
} else {
int port_mg = eth->soc->offload_version > 1 ? 0 : 0x3f;
val = FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_BIND) |
FIELD_PREP(MTK_FOE_IB1_PACKET_TYPE, type) |
FIELD_PREP(MTK_FOE_IB1_UDP, l4proto == IPPROTO_UDP) |
MTK_FOE_IB1_BIND_CACHE | MTK_FOE_IB1_BIND_TTL;
entry->ib1 = val;
val = FIELD_PREP(MTK_FOE_IB2_DEST_PORT, pse_port) |
FIELD_PREP(MTK_FOE_IB2_PORT_MG, port_mg) |
FIELD_PREP(MTK_FOE_IB2_PORT_AG, 0x1f);
}
if (is_multicast_ether_addr(dest_mac))
val |= mtk_get_ib2_multicast_mask(eth);
ports_pad = 0xa5a5a500 | (l4proto & 0xff);
if (type == MTK_PPE_PKT_TYPE_IPV4_ROUTE)
entry->ipv4.orig.ports = ports_pad;
if (type == MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T)
entry->ipv6.ports = ports_pad;
if (type == MTK_PPE_PKT_TYPE_BRIDGE) {
ether_addr_copy(entry->bridge.src_mac, src_mac);
ether_addr_copy(entry->bridge.dest_mac, dest_mac);
entry->bridge.ib2 = val;
l2 = &entry->bridge.l2;
} else if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE) {
entry->ipv6.ib2 = val;
l2 = &entry->ipv6.l2;
} else {
entry->ipv4.ib2 = val;
l2 = &entry->ipv4.l2;
}
l2->dest_mac_hi = get_unaligned_be32(dest_mac);
l2->dest_mac_lo = get_unaligned_be16(dest_mac + 4);
l2->src_mac_hi = get_unaligned_be32(src_mac);
l2->src_mac_lo = get_unaligned_be16(src_mac + 4);
if (type >= MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T)
l2->etype = ETH_P_IPV6;
else
l2->etype = ETH_P_IP;
return 0;
}
int mtk_foe_entry_set_pse_port(struct mtk_eth *eth,
struct mtk_foe_entry *entry, u8 port)
{
u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
u32 val = *ib2;
if (mtk_is_netsys_v2_or_greater(eth)) {
val &= ~MTK_FOE_IB2_DEST_PORT_V2;
val |= FIELD_PREP(MTK_FOE_IB2_DEST_PORT_V2, port);
} else {
val &= ~MTK_FOE_IB2_DEST_PORT;
val |= FIELD_PREP(MTK_FOE_IB2_DEST_PORT, port);
}
*ib2 = val;
return 0;
}
int mtk_foe_entry_set_ipv4_tuple(struct mtk_eth *eth,
struct mtk_foe_entry *entry, bool egress,
__be32 src_addr, __be16 src_port,
__be32 dest_addr, __be16 dest_port)
{
int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
struct mtk_ipv4_tuple *t;
switch (type) {
case MTK_PPE_PKT_TYPE_IPV4_HNAPT:
if (egress) {
t = &entry->ipv4.new;
break;
}
fallthrough;
case MTK_PPE_PKT_TYPE_IPV4_DSLITE:
case MTK_PPE_PKT_TYPE_IPV4_ROUTE:
t = &entry->ipv4.orig;
break;
case MTK_PPE_PKT_TYPE_IPV6_6RD:
entry->ipv6_6rd.tunnel_src_ip = be32_to_cpu(src_addr);
entry->ipv6_6rd.tunnel_dest_ip = be32_to_cpu(dest_addr);
return 0;
default:
WARN_ON_ONCE(1);
return -EINVAL;
}
t->src_ip = be32_to_cpu(src_addr);
t->dest_ip = be32_to_cpu(dest_addr);
if (type == MTK_PPE_PKT_TYPE_IPV4_ROUTE)
return 0;
t->src_port = be16_to_cpu(src_port);
t->dest_port = be16_to_cpu(dest_port);
return 0;
}
int mtk_foe_entry_set_ipv6_tuple(struct mtk_eth *eth,
struct mtk_foe_entry *entry,
__be32 *src_addr, __be16 src_port,
__be32 *dest_addr, __be16 dest_port)
{
int type = mtk_get_ib1_pkt_type(eth, entry->ib1);
u32 *src, *dest;
int i;
switch (type) {
case MTK_PPE_PKT_TYPE_IPV4_DSLITE:
src = entry->dslite.tunnel_src_ip;
dest = entry->dslite.tunnel_dest_ip;
break;
case MTK_PPE_PKT_TYPE_IPV6_ROUTE_5T:
case MTK_PPE_PKT_TYPE_IPV6_6RD:
entry->ipv6.src_port = be16_to_cpu(src_port);
entry->ipv6.dest_port = be16_to_cpu(dest_port);
fallthrough;
case MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T:
src = entry->ipv6.src_ip;
dest = entry->ipv6.dest_ip;
break;
default:
WARN_ON_ONCE(1);
return -EINVAL;
}
for (i = 0; i < 4; i++)
src[i] = be32_to_cpu(src_addr[i]);
for (i = 0; i < 4; i++)
dest[i] = be32_to_cpu(dest_addr[i]);
return 0;
}
int mtk_foe_entry_set_dsa(struct mtk_eth *eth, struct mtk_foe_entry *entry,
int port)
{
struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
l2->etype = BIT(port);
if (!(entry->ib1 & mtk_get_ib1_vlan_layer_mask(eth)))
entry->ib1 |= mtk_prep_ib1_vlan_layer(eth, 1);
else
l2->etype |= BIT(8);
entry->ib1 &= ~mtk_get_ib1_vlan_tag_mask(eth);
return 0;
}
int mtk_foe_entry_set_vlan(struct mtk_eth *eth, struct mtk_foe_entry *entry,
int vid)
{
struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
switch (mtk_get_ib1_vlan_layer(eth, entry->ib1)) {
case 0:
entry->ib1 |= mtk_get_ib1_vlan_tag_mask(eth) |
mtk_prep_ib1_vlan_layer(eth, 1);
l2->vlan1 = vid;
return 0;
case 1:
if (!(entry->ib1 & mtk_get_ib1_vlan_tag_mask(eth))) {
l2->vlan1 = vid;
l2->etype |= BIT(8);
} else {
l2->vlan2 = vid;
entry->ib1 += mtk_prep_ib1_vlan_layer(eth, 1);
}
return 0;
default:
return -ENOSPC;
}
}
int mtk_foe_entry_set_pppoe(struct mtk_eth *eth, struct mtk_foe_entry *entry,
int sid)
{
struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
if (!(entry->ib1 & mtk_get_ib1_vlan_layer_mask(eth)) ||
(entry->ib1 & mtk_get_ib1_vlan_tag_mask(eth)))
l2->etype = ETH_P_PPP_SES;
entry->ib1 |= mtk_get_ib1_ppoe_mask(eth);
l2->pppoe_id = sid;
return 0;
}
int mtk_foe_entry_set_wdma(struct mtk_eth *eth, struct mtk_foe_entry *entry,
int wdma_idx, int txq, int bss, int wcid,
bool amsdu_en)
{
struct mtk_foe_mac_info *l2 = mtk_foe_entry_l2(eth, entry);
u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
switch (eth->soc->version) {
case 3:
*ib2 &= ~MTK_FOE_IB2_PORT_MG_V2;
*ib2 |= FIELD_PREP(MTK_FOE_IB2_RX_IDX, txq) |
MTK_FOE_IB2_WDMA_WINFO_V2;
l2->w3info = FIELD_PREP(MTK_FOE_WINFO_WCID_V3, wcid) |
FIELD_PREP(MTK_FOE_WINFO_BSS_V3, bss);
l2->amsdu = FIELD_PREP(MTK_FOE_WINFO_AMSDU_EN, amsdu_en);
break;
case 2:
*ib2 &= ~MTK_FOE_IB2_PORT_MG_V2;
*ib2 |= FIELD_PREP(MTK_FOE_IB2_RX_IDX, txq) |
MTK_FOE_IB2_WDMA_WINFO_V2;
l2->winfo = FIELD_PREP(MTK_FOE_WINFO_WCID, wcid) |
FIELD_PREP(MTK_FOE_WINFO_BSS, bss);
break;
default:
*ib2 &= ~MTK_FOE_IB2_PORT_MG;
*ib2 |= MTK_FOE_IB2_WDMA_WINFO;
if (wdma_idx)
*ib2 |= MTK_FOE_IB2_WDMA_DEVIDX;
l2->vlan2 = FIELD_PREP(MTK_FOE_VLAN2_WINFO_BSS, bss) |
FIELD_PREP(MTK_FOE_VLAN2_WINFO_WCID, wcid) |
FIELD_PREP(MTK_FOE_VLAN2_WINFO_RING, txq);
break;
}
return 0;
}
int mtk_foe_entry_set_queue(struct mtk_eth *eth, struct mtk_foe_entry *entry,
unsigned int queue)
{
u32 *ib2 = mtk_foe_entry_ib2(eth, entry);
if (mtk_is_netsys_v2_or_greater(eth)) {
*ib2 &= ~MTK_FOE_IB2_QID_V2;
*ib2 |= FIELD_PREP(MTK_FOE_IB2_QID_V2, queue);
*ib2 |= MTK_FOE_IB2_PSE_QOS_V2;
} else {
*ib2 &= ~MTK_FOE_IB2_QID;
*ib2 |= FIELD_PREP(MTK_FOE_IB2_QID, queue);
*ib2 |= MTK_FOE_IB2_PSE_QOS;
}
return 0;
}
static bool
mtk_flow_entry_match(struct mtk_eth *eth, struct mtk_flow_entry *entry,
struct mtk_foe_entry *data)
{
int type, len;
if ((data->ib1 ^ entry->data.ib1) & MTK_FOE_IB1_UDP)
return false;
type = mtk_get_ib1_pkt_type(eth, entry->data.ib1);
if (type > MTK_PPE_PKT_TYPE_IPV4_DSLITE)
len = offsetof(struct mtk_foe_entry, ipv6._rsv);
else
len = offsetof(struct mtk_foe_entry, ipv4.ib2);
return !memcmp(&entry->data.data, &data->data, len - 4);
}
static void
__mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
{
struct hlist_head *head;
struct hlist_node *tmp;
if (entry->type == MTK_FLOW_TYPE_L2) {
rhashtable_remove_fast(&ppe->l2_flows, &entry->l2_node,
mtk_flow_l2_ht_params);
head = &entry->l2_flows;
hlist_for_each_entry_safe(entry, tmp, head, l2_data.list)
__mtk_foe_entry_clear(ppe, entry);
return;
}
hlist_del_init(&entry->list);
if (entry->hash != 0xffff) {
struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, entry->hash);
hwe->ib1 &= ~MTK_FOE_IB1_STATE;
hwe->ib1 |= FIELD_PREP(MTK_FOE_IB1_STATE, MTK_FOE_STATE_INVALID);
dma_wmb();
mtk_ppe_cache_clear(ppe);
if (ppe->accounting) {
struct mtk_foe_accounting *acct;
acct = ppe->acct_table + entry->hash * sizeof(*acct);
acct->packets = 0;
acct->bytes = 0;
}
}
entry->hash = 0xffff;
if (entry->type != MTK_FLOW_TYPE_L2_SUBFLOW)
return;
hlist_del_init(&entry->l2_data.list);
kfree(entry);
}
static int __mtk_foe_entry_idle_time(struct mtk_ppe *ppe, u32 ib1)
{
u32 ib1_ts_mask = mtk_get_ib1_ts_mask(ppe->eth);
u16 now = mtk_eth_timestamp(ppe->eth);
u16 timestamp = ib1 & ib1_ts_mask;
if (timestamp > now)
return ib1_ts_mask + 1 - timestamp + now;
else
return now - timestamp;
}
static void
mtk_flow_entry_update_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
{
u32 ib1_ts_mask = mtk_get_ib1_ts_mask(ppe->eth);
struct mtk_flow_entry *cur;
struct mtk_foe_entry *hwe;
struct hlist_node *tmp;
int idle;
idle = __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
hlist_for_each_entry_safe(cur, tmp, &entry->l2_flows, l2_data.list) {
int cur_idle;
u32 ib1;
hwe = mtk_foe_get_entry(ppe, cur->hash);
ib1 = READ_ONCE(hwe->ib1);
if (FIELD_GET(MTK_FOE_IB1_STATE, ib1) != MTK_FOE_STATE_BIND) {
cur->hash = 0xffff;
__mtk_foe_entry_clear(ppe, cur);
continue;
}
cur_idle = __mtk_foe_entry_idle_time(ppe, ib1);
if (cur_idle >= idle)
continue;
idle = cur_idle;
entry->data.ib1 &= ~ib1_ts_mask;
entry->data.ib1 |= hwe->ib1 & ib1_ts_mask;
}
}
static void
mtk_flow_entry_update(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
{
struct mtk_foe_entry foe = {};
struct mtk_foe_entry *hwe;
spin_lock_bh(&ppe_lock);
if (entry->type == MTK_FLOW_TYPE_L2) {
mtk_flow_entry_update_l2(ppe, entry);
goto out;
}
if (entry->hash == 0xffff)
goto out;
hwe = mtk_foe_get_entry(ppe, entry->hash);
memcpy(&foe, hwe, ppe->eth->soc->foe_entry_size);
if (!mtk_flow_entry_match(ppe->eth, entry, &foe)) {
entry->hash = 0xffff;
goto out;
}
entry->data.ib1 = foe.ib1;
out:
spin_unlock_bh(&ppe_lock);
}
static void
__mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
u16 hash)
{
struct mtk_eth *eth = ppe->eth;
u16 timestamp = mtk_eth_timestamp(eth);
struct mtk_foe_entry *hwe;
u32 val;
if (mtk_is_netsys_v2_or_greater(eth)) {
entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP_V2;
entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP_V2,
timestamp);
} else {
entry->ib1 &= ~MTK_FOE_IB1_BIND_TIMESTAMP;
entry->ib1 |= FIELD_PREP(MTK_FOE_IB1_BIND_TIMESTAMP,
timestamp);
}
hwe = mtk_foe_get_entry(ppe, hash);
memcpy(&hwe->data, &entry->data, eth->soc->foe_entry_size - sizeof(hwe->ib1));
wmb();
hwe->ib1 = entry->ib1;
if (ppe->accounting) {
if (mtk_is_netsys_v2_or_greater(eth))
val = MTK_FOE_IB2_MIB_CNT_V2;
else
val = MTK_FOE_IB2_MIB_CNT;
*mtk_foe_entry_ib2(eth, hwe) |= val;
}
dma_wmb();
mtk_ppe_cache_clear(ppe);
}
void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
{
spin_lock_bh(&ppe_lock);
__mtk_foe_entry_clear(ppe, entry);
spin_unlock_bh(&ppe_lock);
}
static int
mtk_foe_entry_commit_l2(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
{
struct mtk_flow_entry *prev;
entry->type = MTK_FLOW_TYPE_L2;
prev = rhashtable_lookup_get_insert_fast(&ppe->l2_flows, &entry->l2_node,
mtk_flow_l2_ht_params);
if (likely(!prev))
return 0;
if (IS_ERR(prev))
return PTR_ERR(prev);
return rhashtable_replace_fast(&ppe->l2_flows, &prev->l2_node,
&entry->l2_node, mtk_flow_l2_ht_params);
}
int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
{
const struct mtk_soc_data *soc = ppe->eth->soc;
int type = mtk_get_ib1_pkt_type(ppe->eth, entry->data.ib1);
u32 hash;
if (type == MTK_PPE_PKT_TYPE_BRIDGE)
return mtk_foe_entry_commit_l2(ppe, entry);
hash = mtk_ppe_hash_entry(ppe->eth, &entry->data);
entry->hash = 0xffff;
spin_lock_bh(&ppe_lock);
hlist_add_head(&entry->list, &ppe->foe_flow[hash / soc->hash_offset]);
spin_unlock_bh(&ppe_lock);
return 0;
}
static void
mtk_foe_entry_commit_subflow(struct mtk_ppe *ppe, struct mtk_flow_entry *entry,
u16 hash)
{
const struct mtk_soc_data *soc = ppe->eth->soc;
struct mtk_flow_entry *flow_info;
struct mtk_foe_entry foe = {}, *hwe;
struct mtk_foe_mac_info *l2;
u32 ib1_mask = mtk_get_ib1_pkt_type_mask(ppe->eth) | MTK_FOE_IB1_UDP;
int type;
flow_info = kzalloc(sizeof(*flow_info), GFP_ATOMIC);
if (!flow_info)
return;
flow_info->l2_data.base_flow = entry;
flow_info->type = MTK_FLOW_TYPE_L2_SUBFLOW;
flow_info->hash = hash;
hlist_add_head(&flow_info->list,
&ppe->foe_flow[hash / soc->hash_offset]);
hlist_add_head(&flow_info->l2_data.list, &entry->l2_flows);
hwe = mtk_foe_get_entry(ppe, hash);
memcpy(&foe, hwe, soc->foe_entry_size);
foe.ib1 &= ib1_mask;
foe.ib1 |= entry->data.ib1 & ~ib1_mask;
l2 = mtk_foe_entry_l2(ppe->eth, &foe);
memcpy(l2, &entry->data.bridge.l2, sizeof(*l2));
type = mtk_get_ib1_pkt_type(ppe->eth, foe.ib1);
if (type == MTK_PPE_PKT_TYPE_IPV4_HNAPT)
memcpy(&foe.ipv4.new, &foe.ipv4.orig, sizeof(foe.ipv4.new));
else if (type >= MTK_PPE_PKT_TYPE_IPV6_ROUTE_3T && l2->etype == ETH_P_IP)
l2->etype = ETH_P_IPV6;
*mtk_foe_entry_ib2(ppe->eth, &foe) = entry->data.bridge.ib2;
__mtk_foe_entry_commit(ppe, &foe, hash);
}
void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
{
const struct mtk_soc_data *soc = ppe->eth->soc;
struct hlist_head *head = &ppe->foe_flow[hash / soc->hash_offset];
struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, hash);
struct mtk_flow_entry *entry;
struct mtk_foe_bridge key = {};
struct hlist_node *n;
struct ethhdr *eh;
bool found = false;
u8 *tag;
spin_lock_bh(&ppe_lock);
if (FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) == MTK_FOE_STATE_BIND)
goto out;
hlist_for_each_entry_safe(entry, n, head, list) {
if (entry->type == MTK_FLOW_TYPE_L2_SUBFLOW) {
if (unlikely(FIELD_GET(MTK_FOE_IB1_STATE, hwe->ib1) ==
MTK_FOE_STATE_BIND))
continue;
entry->hash = 0xffff;
__mtk_foe_entry_clear(ppe, entry);
continue;
}
if (found || !mtk_flow_entry_match(ppe->eth, entry, hwe)) {
if (entry->hash != 0xffff)
entry->hash = 0xffff;
continue;
}
entry->hash = hash;
__mtk_foe_entry_commit(ppe, &entry->data, hash);
found = true;
}
if (found)
goto out;
eh = eth_hdr(skb);
ether_addr_copy(key.dest_mac, eh->h_dest);
ether_addr_copy(key.src_mac, eh->h_source);
tag = skb->data - 2;
key.vlan = 0;
switch (skb->protocol) {
#if IS_ENABLED(CONFIG_NET_DSA)
case htons(ETH_P_XDSA):
if (!netdev_uses_dsa(skb->dev) ||
skb->dev->dsa_ptr->tag_ops->proto != DSA_TAG_PROTO_MTK)
goto out;
if (!skb_metadata_dst(skb))
tag += 4;
if (get_unaligned_be16(tag) != ETH_P_8021Q)
break;
fallthrough;
#endif
case htons(ETH_P_8021Q):
key.vlan = get_unaligned_be16(tag + 2) & VLAN_VID_MASK;
break;
default:
break;
}
entry = rhashtable_lookup_fast(&ppe->l2_flows, &key, mtk_flow_l2_ht_params);
if (!entry)
goto out;
mtk_foe_entry_commit_subflow(ppe, entry, hash);
out:
spin_unlock_bh(&ppe_lock);
}
int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
{
mtk_flow_entry_update(ppe, entry);
return __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
}
int mtk_ppe_prepare_reset(struct mtk_ppe *ppe)
{
if (!ppe)
return -EINVAL;
/* disable KA */
ppe_clear(ppe, MTK_PPE_TB_CFG, MTK_PPE_TB_CFG_KEEPALIVE);
ppe_clear(ppe, MTK_PPE_BIND_LMT1, MTK_PPE_NTU_KEEPALIVE);
ppe_w32(ppe, MTK_PPE_KEEPALIVE, 0);
usleep_range(10000, 11000);
/* set KA timer to maximum */
ppe_set(ppe, MTK_PPE_BIND_LMT1, MTK_PPE_NTU_KEEPALIVE);
ppe_w32(ppe, MTK_PPE_KEEPALIVE, 0xffffffff);
/* set KA tick select */
ppe_set(ppe, MTK_PPE_TB_CFG, MTK_PPE_TB_TICK_SEL);
ppe_set(ppe, MTK_PPE_TB_CFG, MTK_PPE_TB_CFG_KEEPALIVE);
usleep_range(10000, 11000);
/* disable scan mode */
ppe_clear(ppe, MTK_PPE_TB_CFG, MTK_PPE_TB_CFG_SCAN_MODE);
usleep_range(10000, 11000);
return mtk_ppe_wait_busy(ppe);
}
struct mtk_foe_accounting *mtk_foe_entry_get_mib(struct mtk_ppe *ppe, u32 index,
struct mtk_foe_accounting *diff)
{
struct mtk_foe_accounting *acct;
int size = sizeof(struct mtk_foe_accounting);
u64 bytes, packets;
if (!ppe->accounting)
return NULL;
if (mtk_mib_entry_read(ppe, index, &bytes, &packets))
return NULL;
acct = ppe->acct_table + index * size;
acct->bytes += bytes;
acct->packets += packets;
if (diff) {
diff->bytes = bytes;
diff->packets = packets;
}
return acct;
}
struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int index)
{
bool accounting = eth->soc->has_accounting;
const struct mtk_soc_data *soc = eth->soc;
struct mtk_foe_accounting *acct;
struct device *dev = eth->dev;
struct mtk_mib_entry *mib;
struct mtk_ppe *ppe;
u32 foe_flow_size;
void *foe;
ppe = devm_kzalloc(dev, sizeof(*ppe), GFP_KERNEL);
if (!ppe)
return NULL;
rhashtable_init(&ppe->l2_flows, &mtk_flow_l2_ht_params);
/* need to allocate a separate device, since it PPE DMA access is
* not coherent.
*/
ppe->base = base;
ppe->eth = eth;
ppe->dev = dev;
ppe->version = eth->soc->offload_version;
ppe->accounting = accounting;
foe = dmam_alloc_coherent(ppe->dev,
MTK_PPE_ENTRIES * soc->foe_entry_size,
&ppe->foe_phys, GFP_KERNEL);
if (!foe)
goto err_free_l2_flows;
ppe->foe_table = foe;
foe_flow_size = (MTK_PPE_ENTRIES / soc->hash_offset) *
sizeof(*ppe->foe_flow);
ppe->foe_flow = devm_kzalloc(dev, foe_flow_size, GFP_KERNEL);
if (!ppe->foe_flow)
goto err_free_l2_flows;
if (accounting) {
mib = dmam_alloc_coherent(ppe->dev, MTK_PPE_ENTRIES * sizeof(*mib),
&ppe->mib_phys, GFP_KERNEL);
if (!mib)
return NULL;
ppe->mib_table = mib;
acct = devm_kzalloc(dev, MTK_PPE_ENTRIES * sizeof(*acct),
GFP_KERNEL);
if (!acct)
return NULL;
ppe->acct_table = acct;
}
mtk_ppe_debugfs_init(ppe, index);
return ppe;
err_free_l2_flows:
rhashtable_destroy(&ppe->l2_flows);
return NULL;
}
void mtk_ppe_deinit(struct mtk_eth *eth)
{
int i;
for (i = 0; i < ARRAY_SIZE(eth->ppe); i++) {
if (!eth->ppe[i])
return;
rhashtable_destroy(&eth->ppe[i]->l2_flows);
}
}
static void mtk_ppe_init_foe_table(struct mtk_ppe *ppe)
{
static const u8 skip[] = { 12, 25, 38, 51, 76, 89, 102 };
int i, k;
memset(ppe->foe_table, 0,
MTK_PPE_ENTRIES * ppe->eth->soc->foe_entry_size);
if (!IS_ENABLED(CONFIG_SOC_MT7621))
return;
/* skip all entries that cross the 1024 byte boundary */
for (i = 0; i < MTK_PPE_ENTRIES; i += 128) {
for (k = 0; k < ARRAY_SIZE(skip); k++) {
struct mtk_foe_entry *hwe;
hwe = mtk_foe_get_entry(ppe, i + skip[k]);
hwe->ib1 |= MTK_FOE_IB1_STATIC;
}
}
}
void mtk_ppe_start(struct mtk_ppe *ppe)
{
u32 val;
if (!ppe)
return;
mtk_ppe_init_foe_table(ppe);
ppe_w32(ppe, MTK_PPE_TB_BASE, ppe->foe_phys);
val = MTK_PPE_TB_CFG_AGE_NON_L4 |
MTK_PPE_TB_CFG_AGE_UNBIND |
MTK_PPE_TB_CFG_AGE_TCP |
MTK_PPE_TB_CFG_AGE_UDP |
MTK_PPE_TB_CFG_AGE_TCP_FIN |
FIELD_PREP(MTK_PPE_TB_CFG_SEARCH_MISS,
MTK_PPE_SEARCH_MISS_ACTION_FORWARD_BUILD) |
FIELD_PREP(MTK_PPE_TB_CFG_KEEPALIVE,
MTK_PPE_KEEPALIVE_DISABLE) |
FIELD_PREP(MTK_PPE_TB_CFG_HASH_MODE, 1) |
FIELD_PREP(MTK_PPE_TB_CFG_SCAN_MODE,
MTK_PPE_SCAN_MODE_KEEPALIVE_AGE) |
FIELD_PREP(MTK_PPE_TB_CFG_ENTRY_NUM,
MTK_PPE_ENTRIES_SHIFT);
if (mtk_is_netsys_v2_or_greater(ppe->eth))
val |= MTK_PPE_TB_CFG_INFO_SEL;
if (!mtk_is_netsys_v3_or_greater(ppe->eth))
val |= MTK_PPE_TB_CFG_ENTRY_80B;
ppe_w32(ppe, MTK_PPE_TB_CFG, val);
ppe_w32(ppe, MTK_PPE_IP_PROTO_CHK,
MTK_PPE_IP_PROTO_CHK_IPV4 | MTK_PPE_IP_PROTO_CHK_IPV6);
mtk_ppe_cache_enable(ppe, true);
val = MTK_PPE_FLOW_CFG_IP6_3T_ROUTE |
MTK_PPE_FLOW_CFG_IP6_5T_ROUTE |
MTK_PPE_FLOW_CFG_IP6_6RD |
MTK_PPE_FLOW_CFG_IP4_NAT |
MTK_PPE_FLOW_CFG_IP4_NAPT |
MTK_PPE_FLOW_CFG_IP4_DSLITE |
MTK_PPE_FLOW_CFG_IP4_NAT_FRAG;
if (mtk_is_netsys_v2_or_greater(ppe->eth))
val |= MTK_PPE_MD_TOAP_BYP_CRSN0 |
MTK_PPE_MD_TOAP_BYP_CRSN1 |
MTK_PPE_MD_TOAP_BYP_CRSN2 |
MTK_PPE_FLOW_CFG_IP4_HASH_GRE_KEY;
else
val |= MTK_PPE_FLOW_CFG_IP4_TCP_FRAG |
MTK_PPE_FLOW_CFG_IP4_UDP_FRAG;
ppe_w32(ppe, MTK_PPE_FLOW_CFG, val);
val = FIELD_PREP(MTK_PPE_UNBIND_AGE_MIN_PACKETS, 1000) |
FIELD_PREP(MTK_PPE_UNBIND_AGE_DELTA, 3);
ppe_w32(ppe, MTK_PPE_UNBIND_AGE, val);
val = FIELD_PREP(MTK_PPE_BIND_AGE0_DELTA_UDP, 12) |
FIELD_PREP(MTK_PPE_BIND_AGE0_DELTA_NON_L4, 1);
ppe_w32(ppe, MTK_PPE_BIND_AGE0, val);
val = FIELD_PREP(MTK_PPE_BIND_AGE1_DELTA_TCP_FIN, 1) |
FIELD_PREP(MTK_PPE_BIND_AGE1_DELTA_TCP, 7);
ppe_w32(ppe, MTK_PPE_BIND_AGE1, val);
val = MTK_PPE_BIND_LIMIT0_QUARTER | MTK_PPE_BIND_LIMIT0_HALF;
ppe_w32(ppe, MTK_PPE_BIND_LIMIT0, val);
val = MTK_PPE_BIND_LIMIT1_FULL |
FIELD_PREP(MTK_PPE_BIND_LIMIT1_NON_L4, 1);
ppe_w32(ppe, MTK_PPE_BIND_LIMIT1, val);
val = FIELD_PREP(MTK_PPE_BIND_RATE_BIND, 30) |
FIELD_PREP(MTK_PPE_BIND_RATE_PREBIND, 1);
ppe_w32(ppe, MTK_PPE_BIND_RATE, val);
/* enable PPE */
val = MTK_PPE_GLO_CFG_EN |
MTK_PPE_GLO_CFG_IP4_L4_CS_DROP |
MTK_PPE_GLO_CFG_IP4_CS_DROP |
MTK_PPE_GLO_CFG_FLOW_DROP_UPDATE;
ppe_w32(ppe, MTK_PPE_GLO_CFG, val);
ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT, 0);
if (mtk_is_netsys_v2_or_greater(ppe->eth)) {
ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT1, 0xcb777);
ppe_w32(ppe, MTK_PPE_SBW_CTRL, 0x7f);
}
if (ppe->accounting && ppe->mib_phys) {
ppe_w32(ppe, MTK_PPE_MIB_TB_BASE, ppe->mib_phys);
ppe_m32(ppe, MTK_PPE_MIB_CFG, MTK_PPE_MIB_CFG_EN,
MTK_PPE_MIB_CFG_EN);
ppe_m32(ppe, MTK_PPE_MIB_CFG, MTK_PPE_MIB_CFG_RD_CLR,
MTK_PPE_MIB_CFG_RD_CLR);
ppe_m32(ppe, MTK_PPE_MIB_CACHE_CTL, MTK_PPE_MIB_CACHE_CTL_EN,
MTK_PPE_MIB_CFG_RD_CLR);
}
}
int mtk_ppe_stop(struct mtk_ppe *ppe)
{
u32 val;
int i;
if (!ppe)
return 0;
for (i = 0; i < MTK_PPE_ENTRIES; i++) {
struct mtk_foe_entry *hwe = mtk_foe_get_entry(ppe, i);
hwe->ib1 = FIELD_PREP(MTK_FOE_IB1_STATE,
MTK_FOE_STATE_INVALID);
}
mtk_ppe_cache_enable(ppe, false);
/* disable offload engine */
ppe_clear(ppe, MTK_PPE_GLO_CFG, MTK_PPE_GLO_CFG_EN);
ppe_w32(ppe, MTK_PPE_FLOW_CFG, 0);
/* disable aging */
val = MTK_PPE_TB_CFG_AGE_NON_L4 |
MTK_PPE_TB_CFG_AGE_UNBIND |
MTK_PPE_TB_CFG_AGE_TCP |
MTK_PPE_TB_CFG_AGE_UDP |
MTK_PPE_TB_CFG_AGE_TCP_FIN;
ppe_clear(ppe, MTK_PPE_TB_CFG, val);
return mtk_ppe_wait_busy(ppe);
}