mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-05 20:30:41 +00:00

When running these commands on DUT (and similar at the other end)
ip link set dev eth0 up
ip link add link eth0 name eth0.10 type vlan id 10
ip addr add 10.0.0.1/24 dev eth0.10
ip link set dev eth0.10 up
ping 10.0.0.2
The ping will fail.
The reason why is failing is because, the network interfaces for lan966x
have a flag saying that the HW can insert the vlan tags into the
frames(NETIF_F_HW_VLAN_CTAG_TX). Meaning that the frames that are
transmitted don't have the vlan tag inside the skb data, but they have
it inside the skb. We already get that vlan tag and put it in the IFH
but the problem is that we don't configure the HW to rewrite the frame
when the interface is in host mode.
The fix consists in actually configuring the HW to insert the vlan tag
if it is different than 0.
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Fixes: 6d2c186afa
("net: lan966x: Add vlan support.")
Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
Link: https://patch.msgid.link/20250528093619.3738998-1-horatiu.vultur@microchip.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
345 lines
9.8 KiB
C
345 lines
9.8 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
#include "lan966x_main.h"
|
|
|
|
#define VLANACCESS_CMD_IDLE 0
|
|
#define VLANACCESS_CMD_READ 1
|
|
#define VLANACCESS_CMD_WRITE 2
|
|
#define VLANACCESS_CMD_INIT 3
|
|
|
|
static int lan966x_vlan_get_status(struct lan966x *lan966x)
|
|
{
|
|
return lan_rd(lan966x, ANA_VLANACCESS);
|
|
}
|
|
|
|
static int lan966x_vlan_wait_for_completion(struct lan966x *lan966x)
|
|
{
|
|
u32 val;
|
|
|
|
return readx_poll_timeout(lan966x_vlan_get_status,
|
|
lan966x, val,
|
|
(val & ANA_VLANACCESS_VLAN_TBL_CMD) ==
|
|
VLANACCESS_CMD_IDLE,
|
|
TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
|
|
}
|
|
|
|
static void lan966x_vlan_set_mask(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
u16 mask = lan966x->vlan_mask[vid];
|
|
bool cpu_dis;
|
|
|
|
cpu_dis = !(mask & BIT(CPU_PORT));
|
|
|
|
/* Set flags and the VID to configure */
|
|
lan_rmw(ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(cpu_dis) |
|
|
ANA_VLANTIDX_V_INDEX_SET(vid),
|
|
ANA_VLANTIDX_VLAN_PGID_CPU_DIS |
|
|
ANA_VLANTIDX_V_INDEX,
|
|
lan966x, ANA_VLANTIDX);
|
|
|
|
/* Set the vlan port members mask */
|
|
lan_rmw(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_SET(mask),
|
|
ANA_VLAN_PORT_MASK_VLAN_PORT_MASK,
|
|
lan966x, ANA_VLAN_PORT_MASK);
|
|
|
|
/* Issue a write command */
|
|
lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_WRITE),
|
|
ANA_VLANACCESS_VLAN_TBL_CMD,
|
|
lan966x, ANA_VLANACCESS);
|
|
|
|
if (lan966x_vlan_wait_for_completion(lan966x))
|
|
dev_err(lan966x->dev, "Vlan set mask failed\n");
|
|
}
|
|
|
|
static void lan966x_vlan_port_add_vlan_mask(struct lan966x_port *port, u16 vid)
|
|
{
|
|
struct lan966x *lan966x = port->lan966x;
|
|
u8 p = port->chip_port;
|
|
|
|
lan966x->vlan_mask[vid] |= BIT(p);
|
|
lan966x_vlan_set_mask(lan966x, vid);
|
|
}
|
|
|
|
static void lan966x_vlan_port_del_vlan_mask(struct lan966x_port *port, u16 vid)
|
|
{
|
|
struct lan966x *lan966x = port->lan966x;
|
|
u8 p = port->chip_port;
|
|
|
|
lan966x->vlan_mask[vid] &= ~BIT(p);
|
|
lan966x_vlan_set_mask(lan966x, vid);
|
|
}
|
|
|
|
static bool lan966x_vlan_port_any_vlan_mask(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
return !!(lan966x->vlan_mask[vid] & ~BIT(CPU_PORT));
|
|
}
|
|
|
|
static void lan966x_vlan_cpu_add_vlan_mask(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
lan966x->vlan_mask[vid] |= BIT(CPU_PORT);
|
|
lan966x_vlan_set_mask(lan966x, vid);
|
|
}
|
|
|
|
static void lan966x_vlan_cpu_del_vlan_mask(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
lan966x->vlan_mask[vid] &= ~BIT(CPU_PORT);
|
|
lan966x_vlan_set_mask(lan966x, vid);
|
|
}
|
|
|
|
static void lan966x_vlan_cpu_add_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
__set_bit(vid, lan966x->cpu_vlan_mask);
|
|
}
|
|
|
|
static void lan966x_vlan_cpu_del_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
__clear_bit(vid, lan966x->cpu_vlan_mask);
|
|
}
|
|
|
|
bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
return test_bit(vid, lan966x->cpu_vlan_mask);
|
|
}
|
|
|
|
static u16 lan966x_vlan_port_get_pvid(struct lan966x_port *port)
|
|
{
|
|
struct lan966x *lan966x = port->lan966x;
|
|
|
|
if (!(lan966x->bridge_mask & BIT(port->chip_port)))
|
|
return HOST_PVID;
|
|
|
|
return port->vlan_aware ? port->pvid : UNAWARE_PVID;
|
|
}
|
|
|
|
int lan966x_vlan_port_set_vid(struct lan966x_port *port, u16 vid,
|
|
bool pvid, bool untagged)
|
|
{
|
|
struct lan966x *lan966x = port->lan966x;
|
|
|
|
/* Egress vlan classification */
|
|
if (untagged && port->vid != vid) {
|
|
if (port->vid) {
|
|
dev_err(lan966x->dev,
|
|
"Port already has a native VLAN: %d\n",
|
|
port->vid);
|
|
return -EBUSY;
|
|
}
|
|
port->vid = vid;
|
|
}
|
|
|
|
/* Default ingress vlan classification */
|
|
if (pvid)
|
|
port->pvid = vid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lan966x_vlan_port_remove_vid(struct lan966x_port *port, u16 vid)
|
|
{
|
|
if (port->pvid == vid)
|
|
port->pvid = 0;
|
|
|
|
if (port->vid == vid)
|
|
port->vid = 0;
|
|
}
|
|
|
|
void lan966x_vlan_port_set_vlan_aware(struct lan966x_port *port,
|
|
bool vlan_aware)
|
|
{
|
|
port->vlan_aware = vlan_aware;
|
|
}
|
|
|
|
/* When the interface is in host mode, the interface should not be vlan aware
|
|
* but it should insert all the tags that it gets from the network stack.
|
|
* The tags are not in the data of the frame but actually in the skb and the ifh
|
|
* is configured already to get this tag. So what we need to do is to update the
|
|
* rewriter to insert the vlan tag for all frames which have a vlan tag
|
|
* different than 0.
|
|
*/
|
|
void lan966x_vlan_port_rew_host(struct lan966x_port *port)
|
|
{
|
|
struct lan966x *lan966x = port->lan966x;
|
|
u32 val;
|
|
|
|
/* Tag all frames except when VID=0*/
|
|
val = REW_TAG_CFG_TAG_CFG_SET(2);
|
|
|
|
/* Update only some bits in the register */
|
|
lan_rmw(val,
|
|
REW_TAG_CFG_TAG_CFG,
|
|
lan966x, REW_TAG_CFG(port->chip_port));
|
|
}
|
|
|
|
void lan966x_vlan_port_apply(struct lan966x_port *port)
|
|
{
|
|
struct lan966x *lan966x = port->lan966x;
|
|
u16 pvid;
|
|
u32 val;
|
|
|
|
pvid = lan966x_vlan_port_get_pvid(port);
|
|
|
|
/* Ingress classification (ANA_PORT_VLAN_CFG) */
|
|
/* Default vlan to classify for untagged frames (may be zero) */
|
|
val = ANA_VLAN_CFG_VLAN_VID_SET(pvid);
|
|
if (port->vlan_aware)
|
|
val |= ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
|
|
ANA_VLAN_CFG_VLAN_POP_CNT_SET(1);
|
|
|
|
lan_rmw(val,
|
|
ANA_VLAN_CFG_VLAN_VID | ANA_VLAN_CFG_VLAN_AWARE_ENA |
|
|
ANA_VLAN_CFG_VLAN_POP_CNT,
|
|
lan966x, ANA_VLAN_CFG(port->chip_port));
|
|
|
|
lan_rmw(DEV_MAC_TAGS_CFG_VLAN_AWR_ENA_SET(port->vlan_aware) |
|
|
DEV_MAC_TAGS_CFG_VLAN_DBL_AWR_ENA_SET(port->vlan_aware),
|
|
DEV_MAC_TAGS_CFG_VLAN_AWR_ENA |
|
|
DEV_MAC_TAGS_CFG_VLAN_DBL_AWR_ENA,
|
|
lan966x, DEV_MAC_TAGS_CFG(port->chip_port));
|
|
|
|
/* Drop frames with multicast source address */
|
|
val = ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(1);
|
|
if (port->vlan_aware && !pvid)
|
|
/* If port is vlan-aware and tagged, drop untagged and priority
|
|
* tagged frames.
|
|
*/
|
|
val |= ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(1) |
|
|
ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(1) |
|
|
ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(1);
|
|
|
|
lan_wr(val, lan966x, ANA_DROP_CFG(port->chip_port));
|
|
|
|
/* Egress configuration (REW_TAG_CFG): VLAN tag type to 8021Q */
|
|
val = REW_TAG_CFG_TAG_TPID_CFG_SET(0);
|
|
if (port->vlan_aware) {
|
|
if (port->vid)
|
|
/* Tag all frames except when VID == DEFAULT_VLAN */
|
|
val |= REW_TAG_CFG_TAG_CFG_SET(1);
|
|
else
|
|
val |= REW_TAG_CFG_TAG_CFG_SET(3);
|
|
}
|
|
|
|
/* Update only some bits in the register */
|
|
lan_rmw(val,
|
|
REW_TAG_CFG_TAG_TPID_CFG | REW_TAG_CFG_TAG_CFG,
|
|
lan966x, REW_TAG_CFG(port->chip_port));
|
|
|
|
/* Set default VLAN and tag type to 8021Q */
|
|
lan_rmw(REW_PORT_VLAN_CFG_PORT_TPID_SET(ETH_P_8021Q) |
|
|
REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid),
|
|
REW_PORT_VLAN_CFG_PORT_TPID |
|
|
REW_PORT_VLAN_CFG_PORT_VID,
|
|
lan966x, REW_PORT_VLAN_CFG(port->chip_port));
|
|
}
|
|
|
|
void lan966x_vlan_port_add_vlan(struct lan966x_port *port,
|
|
u16 vid,
|
|
bool pvid,
|
|
bool untagged)
|
|
{
|
|
struct lan966x *lan966x = port->lan966x;
|
|
|
|
/* If the CPU(br) is already part of the vlan then add the fdb
|
|
* entries in MAC table to copy the frames to the CPU(br).
|
|
* If the CPU(br) is not part of the vlan then it would
|
|
* just drop the frames.
|
|
*/
|
|
if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, vid)) {
|
|
lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
|
|
lan966x_fdb_write_entries(lan966x, vid);
|
|
lan966x_mdb_write_entries(lan966x, vid);
|
|
}
|
|
|
|
lan966x_vlan_port_set_vid(port, vid, pvid, untagged);
|
|
lan966x_vlan_port_add_vlan_mask(port, vid);
|
|
lan966x_vlan_port_apply(port);
|
|
}
|
|
|
|
void lan966x_vlan_port_del_vlan(struct lan966x_port *port, u16 vid)
|
|
{
|
|
struct lan966x *lan966x = port->lan966x;
|
|
|
|
lan966x_vlan_port_remove_vid(port, vid);
|
|
lan966x_vlan_port_del_vlan_mask(port, vid);
|
|
lan966x_vlan_port_apply(port);
|
|
|
|
/* In case there are no other ports in vlan then remove the CPU from
|
|
* that vlan but still keep it in the mask because it may be needed
|
|
* again then another port gets added in that vlan
|
|
*/
|
|
if (!lan966x_vlan_port_any_vlan_mask(lan966x, vid)) {
|
|
lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
|
|
lan966x_fdb_erase_entries(lan966x, vid);
|
|
lan966x_mdb_erase_entries(lan966x, vid);
|
|
}
|
|
}
|
|
|
|
void lan966x_vlan_cpu_add_vlan(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
/* Add an entry in the MAC table for the CPU
|
|
* Add the CPU part of the vlan only if there is another port in that
|
|
* vlan otherwise all the broadcast frames in that vlan will go to CPU
|
|
* even if none of the ports are in the vlan and then the CPU will just
|
|
* need to discard these frames. It is required to store this
|
|
* information so when a front port is added then it would add also the
|
|
* CPU port.
|
|
*/
|
|
if (lan966x_vlan_port_any_vlan_mask(lan966x, vid)) {
|
|
lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
|
|
lan966x_mdb_write_entries(lan966x, vid);
|
|
}
|
|
|
|
lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, vid);
|
|
lan966x_fdb_write_entries(lan966x, vid);
|
|
}
|
|
|
|
void lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x, u16 vid)
|
|
{
|
|
/* Remove the CPU part of the vlan */
|
|
lan966x_vlan_cpu_del_cpu_vlan_mask(lan966x, vid);
|
|
lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
|
|
lan966x_fdb_erase_entries(lan966x, vid);
|
|
lan966x_mdb_erase_entries(lan966x, vid);
|
|
}
|
|
|
|
void lan966x_vlan_init(struct lan966x *lan966x)
|
|
{
|
|
u16 port, vid;
|
|
|
|
/* Clear VLAN table, by default all ports are members of all VLANS */
|
|
lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_INIT),
|
|
ANA_VLANACCESS_VLAN_TBL_CMD,
|
|
lan966x, ANA_VLANACCESS);
|
|
lan966x_vlan_wait_for_completion(lan966x);
|
|
|
|
for (vid = 1; vid < VLAN_N_VID; vid++) {
|
|
lan966x->vlan_mask[vid] = 0;
|
|
lan966x_vlan_set_mask(lan966x, vid);
|
|
}
|
|
|
|
/* Set all the ports + cpu to be part of HOST_PVID and UNAWARE_PVID */
|
|
lan966x->vlan_mask[HOST_PVID] =
|
|
GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT);
|
|
lan966x_vlan_set_mask(lan966x, HOST_PVID);
|
|
|
|
lan966x->vlan_mask[UNAWARE_PVID] =
|
|
GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT);
|
|
lan966x_vlan_set_mask(lan966x, UNAWARE_PVID);
|
|
|
|
lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, UNAWARE_PVID);
|
|
|
|
/* Configure the CPU port to be vlan aware */
|
|
lan_wr(ANA_VLAN_CFG_VLAN_VID_SET(0) |
|
|
ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
|
|
ANA_VLAN_CFG_VLAN_POP_CNT_SET(1),
|
|
lan966x, ANA_VLAN_CFG(CPU_PORT));
|
|
|
|
/* Set vlan ingress filter mask to all ports */
|
|
lan_wr(GENMASK(lan966x->num_phys_ports, 0),
|
|
lan966x, ANA_VLANMASK);
|
|
|
|
for (port = 0; port < lan966x->num_phys_ports; port++) {
|
|
lan_wr(0, lan966x, REW_PORT_VLAN_CFG(port));
|
|
lan_wr(0, lan966x, REW_TAG_CFG(port));
|
|
}
|
|
}
|