This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[FAQ] TDA4VH-Q1: How to Pass VLAN 0 Packets Transparently Through CPSW Using the Native Linux Driver

Part Number: TDA4VH-Q1

By default, CPSW does not pass VID 0 (priority-tagged) frames as-is. The Linux driver strips or replaces VID 0 at both ingress and egress, making it impossible for the use-cases that require VID 0 with priority bits set on the wire.

  • The following are causes for CPSW to block VID 0 passthrough.

    Ingress - PVID substitution: The CPSW control register has per-port PASS_PRI_TAGGED bits (bits 3-11, one per port). When these are not set, a priority-tagged frame with VID 0 arriving on any port has its VID 0 replaced by that port's configured PVID. Separately, the ALE has a VID 0 mode (ALE_NO_PORT_VLAN) that controls whether VID 0 ingress packets are processed as VID 0 or remapped to the port's VLAN. Both default to PVID substitution.

    Egress - tag stripping: At driver init, a default ALE VLAN 0 entry is created with Force Untagged Packet Egress = 0x7f (all ports). This strips the VID 0 tag on every egress port, so priority-tagged frames never appear on the wire.

    Flooding - no ALE Entry/learning for VID 0: ALE MAC learning is per-VLAN. The Linux kernel reserves VID 0 and rejects any attempt to configure it on bridge or network interfaces (bridge vlan add vid 0 returns an error). As a result, no ALE entries are ever created for VID 0, the ALE has no unicast entries under VID 0, and all VID 0 unicast traffic is flooded across every port in the VID 0 member mask.

    Fix:
    The following chages are required in CPSW Linux driver (am65-cpsw-nuss.c) to pass through VLAN 0 packets as-is.

    ---
    Change 1 - Set PASS_PRI_TAGGED bits in the CPSW control register

    Add per-port PASS_PRI_TAGGED defines and set them alongside the existing VLAN_AWARE and P0_ENABLE bits. This instructs the CPSW hardware to forward priority-tagged frames without substituting VID 0 with the port PVID.

    --- a/drivers/net/ethernet/ti/am65-cpsw-nuss.c
    +++ b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
    @@ -87,6 +87,15 @@
     
     #define AM65_CPSW_CTL_VLAN_AWARE		BIT(1)
     #define AM65_CPSW_CTL_P0_ENABLE			BIT(2)
    +#define AM65_CPSW_CTL_P0_PASS_PRI_TAGGED	BIT(3)
    +#define AM65_CPSW_CTL_P1_PASS_PRI_TAGGED	BIT(4)
    +#define AM65_CPSW_CTL_P2_PASS_PRI_TAGGED	BIT(5)
    +#define AM65_CPSW_CTL_P3_PASS_PRI_TAGGED	BIT(6)
    +#define AM65_CPSW_CTL_P4_PASS_PRI_TAGGED	BIT(7)
    +#define AM65_CPSW_CTL_P5_PASS_PRI_TAGGED	BIT(8)
    +#define AM65_CPSW_CTL_P6_PASS_PRI_TAGGED	BIT(9)
    +#define AM65_CPSW_CTL_P7_PASS_PRI_TAGGED	BIT(10)
    +#define AM65_CPSW_CTL_P8_PASS_PRI_TAGGED	BIT(11)
     #define AM65_CPSW_CTL_P0_TX_CRC_REMOVE		BIT(13)
     #define AM65_CPSW_CTL_P0_RX_PAD			BIT(14)
     
    @@ -739,7 +748,16 @@ static int am65_cpsw_nuss_common_open(struct am65_cpsw_common *common)
     
     	/* Control register */
     	writel(AM65_CPSW_CTL_P0_ENABLE | AM65_CPSW_CTL_P0_TX_CRC_REMOVE |
    -	       AM65_CPSW_CTL_P0_RX_PAD,
    +	       AM65_CPSW_CTL_VLAN_AWARE | AM65_CPSW_CTL_P0_RX_PAD |
    +	       AM65_CPSW_CTL_P0_PASS_PRI_TAGGED |
    +	       AM65_CPSW_CTL_P1_PASS_PRI_TAGGED |
    +	       AM65_CPSW_CTL_P2_PASS_PRI_TAGGED |
    +	       AM65_CPSW_CTL_P3_PASS_PRI_TAGGED |
    +	       AM65_CPSW_CTL_P4_PASS_PRI_TAGGED |
    +	       AM65_CPSW_CTL_P5_PASS_PRI_TAGGED |
    +	       AM65_CPSW_CTL_P6_PASS_PRI_TAGGED |
    +	       AM65_CPSW_CTL_P7_PASS_PRI_TAGGED |
    +	       AM65_CPSW_CTL_P8_PASS_PRI_TAGGED,
     	       common->cpsw_base + AM65_CPSW_REG_CTL);

    ---
    Change 2 - Enable VLAN 0 mode on the ALE host port


    Sets ALE_NO_PORT_VLAN in CPSW ALE control register so VID 0 ingress packets are treated as VID 0 by the ALE, not remapped to the port's VLAN.

    --- a/drivers/net/ethernet/ti/am65-cpsw-nuss.c
    +++ b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
    @@ -2987,6 +2987,10 @@ static void am65_cpsw_init_host_port_switch(struct am65_cpsw_common *common)
     
     	cpsw_ale_control_set(common->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, 1);
     	dev_dbg(common->dev, "Set P0_UNI_FLOOD\n");
    +
    +	cpsw_ale_control_set(common->ale, HOST_PORT_NUM, ALE_NO_PORT_VLAN, 1);
    +	dev_dbg(common->dev, "Set VLAN 0 Mode\n");
    +
     	cpsw_ale_control_set(common->ale, HOST_PORT_NUM, ALE_PORT_NOLEARN, 0);

    ---
    Change 3 - Clear the Force Untagged Egress mask on the ALE VID 0 entry

    The default ALE VID 0 entry is created with untag = ALE_ALL_PORTS (0x7f), which strips the VID 0 tag on every egress port. Pass untag = 0 instead so VID 0 packets egress with their VLAN tag intact.

    @@ -782,7 +800,7 @@ static int am65_cpsw_nuss_common_open(struct am65_cpsw_common *common)
     		    ~common->disabled_ports_mask;
     
     	cpsw_ale_add_vlan(common->ale, 0, port_mask,
    -			  port_mask, port_mask,
    +			  0, port_mask,
     			  port_mask & ~ALE_PORT_HOST);
     

    ---
    Change 4 - Add static HOST port ALE entries for VID 0 per external port

    Adds unicast and multicast ALE entries for the host MAC under VID 0 for each external port. Without these, the ALE has no unicast match for VID 0 traffic and floods it across all member ports.

    --- a/drivers/net/ethernet/ti/am65-cpsw-nuss.c
    +++ b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
    @@ -3059,12 +3059,20 @@ static void am65_cpsw_init_port_switch_ale(struct am65_cpsw_port *port)
     			   HOST_PORT_NUM, ALE_SECURE | ALE_BLOCKED | ALE_VLAN,
     			   slave->port_vlan);
     
    +	cpsw_ale_add_ucast(cpsw->ale, port->ndev->dev_addr,
    +			   HOST_PORT_NUM, ALE_SECURE | ALE_BLOCKED | ALE_VLAN,
    +			   0);
    +
     	port_mask = BIT(port->port_id) | ALE_PORT_HOST;
     
     	cpsw_ale_add_mcast(cpsw->ale, port->ndev->broadcast,
     			   port_mask, ALE_VLAN, slave->port_vlan,
     			   ALE_MCAST_FWD_2);
     
    +	cpsw_ale_add_mcast(cpsw->ale, port->ndev->broadcast,
    +			   port_mask, ALE_VLAN, 0,
    +			   ALE_MCAST_FWD_2);
    +
     	writel(slave->port_vlan, port->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
     
     	cpsw_ale_control_set(cpsw->ale, port->port_id,