From patchwork Mon May 4 12:43:22 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Oltean X-Patchwork-Id: 219959 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.6 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id D83F1C47257 for ; Mon, 4 May 2020 12:44:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A8A0120661 for ; Mon, 4 May 2020 12:44:22 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="kjOWA6Db" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728894AbgEDMoW (ORCPT ); Mon, 4 May 2020 08:44:22 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35602 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-FAIL-OK-FAIL) by vger.kernel.org with ESMTP id S1728876AbgEDMoU (ORCPT ); Mon, 4 May 2020 08:44:20 -0400 Received: from mail-wm1-x344.google.com (mail-wm1-x344.google.com [IPv6:2a00:1450:4864:20::344]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 52098C061A41 for ; Mon, 4 May 2020 05:44:20 -0700 (PDT) Received: by mail-wm1-x344.google.com with SMTP id u127so8865795wmg.1 for ; Mon, 04 May 2020 05:44:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=D1kbuXB6vTsPB0I0gwKSR9cCQxqxWxU9Yl8zRddbnmE=; b=kjOWA6Dbvso4hi2U+wUWzvtRlLn4MhQ8LbMJQJPoV3g8yeby9Uj8qP6jpbbClQYiqb 7R2bnSRzsvX89K9zBgnA+u5wOT0WOjW8nr5oLBr9jwogqe7mnwCj7luZi+/j8IGO5ziD EqeXTzZ1iPqfuMkbiZP9n8nhsRjhhtRnUKbN7na6c5Mxmbc/HQri+0WzDqTyIjSn7qi0 K1+AlyM8t/Ps2fh1Pg9hxUA4tvw4Z+dvpfJVr56XBz5hIv6Ej16O4h/p8aWLyd45jzSQ a5dIinFVf4IZFEAG5u/mx5v5B2S57KdFjfY9GuYav61QjCR6VkOuZE1LmGnkjdL3jTPj QbrA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=D1kbuXB6vTsPB0I0gwKSR9cCQxqxWxU9Yl8zRddbnmE=; b=N8tiO5Cu0CLx6Y1IZhRlGbjBzNMYyJ0J26aAM0FvwbEG0jMMtvGGbMLSZN5u1lu68T u6oUuJe4seKf4/yaCCw0S3jTfJJjtnQdVTN1zjAOgJkAy26Fy9vveztiyT6OEPoVd7I9 0cS3dmed1yq46DFAynzE3gYE7J6pKq2atzKedCsO4gi1gTt2W8wKHrGnOB+zTrfI8Mbk dtFjzBYwfjVFHedxXjwhZCHh/8thmMoPQZ0c1FbxZs5iweywKJTBEIpTGOag348XPbvo YX+kOWa+McAkG4GYoUZ8zl/pZQiOWXyhHhpAQall3p04ZNcueeEcYydG88Ps8Ij0pUln Hkng== X-Gm-Message-State: AGi0PuaEG8VHGLqJGTL57gsv/poZ7zbgiDjYolKPInAXU25Rme2Fpnz+ hm/LI6wdYkfnXGkARSUandg= X-Google-Smtp-Source: APiQypKJxcNLApDkAoxEE72cBWcKD8vGwSe6sazehyrTVg9djUkdG5E8SX15f/RjSWcd8G6rIBTeuQ== X-Received: by 2002:a05:600c:2196:: with SMTP id e22mr14077781wme.105.1588596258909; Mon, 04 May 2020 05:44:18 -0700 (PDT) Received: from localhost.localdomain ([86.121.118.29]) by smtp.gmail.com with ESMTPSA id 32sm17343670wrg.19.2020.05.04.05.44.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 04 May 2020 05:44:18 -0700 (PDT) From: Vladimir Oltean To: andrew@lunn.ch, f.fainelli@gmail.com, vivien.didelot@gmail.com Cc: davem@davemloft.net, jiri@resnulli.us, idosch@idosch.org, kuba@kernel.org, netdev@vger.kernel.org, nikolay@cumulusnetworks.com, roopa@cumulusnetworks.com, georg.waibel@sensor-technik.de, o.rempel@pengutronix.de, christian.herber@nxp.com Subject: [RFC 3/6] net: dsa: tag_8021q: allow DSA tags and VLAN filtering simultaneously Date: Mon, 4 May 2020 15:43:22 +0300 Message-Id: <20200504124325.26758-4-olteanv@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200504124325.26758-1-olteanv@gmail.com> References: <20200504124325.26758-1-olteanv@gmail.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Vladimir Oltean There are very good reasons to want this, but there are also very good reasons for not enabling it by default. So a devlink param named best_effort_vlan_filtering, currently driver-specific and exported only by sja1105, is used to configure this. In practice, this is perhaps the way that most users are going to use the switch in. Best-effort untagged traffic can be bridged with any net device in the system or terminated locally, and VLAN-tagged streams are forwarded autonomously in a time-sensitive manner according to their PCP (they need not transit the CPU). For those cases where the CPU needs to terminate some VLAN-tagged traffic, the next patch will also address that, via dsa_8021q sub-VLANs. Signed-off-by: Vladimir Oltean --- Documentation/networking/dsa/sja1105.rst | 21 +++--- drivers/net/dsa/sja1105/sja1105.h | 1 + drivers/net/dsa/sja1105/sja1105_main.c | 81 ++++++++++++++++++++++-- include/linux/dsa/8021q.h | 7 ++ include/linux/dsa/sja1105.h | 2 + net/dsa/tag_8021q.c | 62 ++++++++++++++++++ net/dsa/tag_sja1105.c | 16 ++--- 7 files changed, 167 insertions(+), 23 deletions(-) diff --git a/Documentation/networking/dsa/sja1105.rst b/Documentation/networking/dsa/sja1105.rst index 35d0643f1377..4a8639cba1f3 100644 --- a/Documentation/networking/dsa/sja1105.rst +++ b/Documentation/networking/dsa/sja1105.rst @@ -85,15 +85,18 @@ functionality. The following traffic modes are supported over the switch netdevices: -+--------------------+------------+------------------+------------------+ -| | Standalone | Bridged with | Bridged with | -| | ports | vlan_filtering 0 | vlan_filtering 1 | -+====================+============+==================+==================+ -| Regular traffic | Yes | Yes | No (use master) | -+--------------------+------------+------------------+------------------+ -| Management traffic | Yes | Yes | Yes | -| (BPDU, PTP) | | | | -+--------------------+------------+------------------+------------------+ ++-------------+------------+----------------+----------------+----------------------------+ +| | Standalone | Bridged with | Bridged with | Bridged with | +| | ports | vlan_filtering | vlan_filtering | best_effort_vlan_filtering | +| | | 0 | 1 | 1 | ++=============+============+================+================+============================+ +| Regular | Yes | Yes | No | Partial (untagged), | +| traffic | | | (use master) | use master for tagged | ++-------------+------------+----------------+----------------+----------------------------+ +| Management | Yes | Yes | Yes | Yes | +| traffic | | | | | +| (BPDU, PTP) | | | | | ++-------------+------------+----------------+----------------+----------------------------+ Switching features ================== diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h index 2a21cab0888c..8fedcaa99f3b 100644 --- a/drivers/net/dsa/sja1105/sja1105.h +++ b/drivers/net/dsa/sja1105/sja1105.h @@ -132,6 +132,7 @@ struct sja1105_private { struct sja1105_static_config static_config; bool rgmii_rx_delay[SJA1105_NUM_PORTS]; bool rgmii_tx_delay[SJA1105_NUM_PORTS]; + bool best_effort_vlan_filtering; const struct sja1105_info *info; struct gpio_desc *reset_gpio; struct spi_device *spidev; diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c index 8a444e6949fd..edbe5dd4af37 100644 --- a/drivers/net/dsa/sja1105/sja1105_main.c +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -1901,10 +1901,27 @@ sja1105_get_tag_protocol(struct dsa_switch *ds, int port, return DSA_TAG_PROTO_SJA1105; } -/* This callback needs to be present */ static int sja1105_vlan_prepare(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan) { + struct sja1105_private *priv = ds->priv; + u16 vid; + int rc; + + if (!dsa_port_is_vlan_filtering(dsa_to_port(ds, port)) || + !priv->best_effort_vlan_filtering) + return 0; + + /* If the user wants best-effort VLAN filtering (aka vlan_filtering + * bridge plus tagging), be sure to at least deny alterations to the + * configuration done by dsa_8021q. + */ + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + rc = dsa_8021q_vid_validate(ds, port, vid, vlan->flags); + if (rc < 0) + return rc; + } + return 0; } @@ -1918,6 +1935,7 @@ static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled) struct sja1105_general_params_entry *general_params; struct sja1105_private *priv = ds->priv; struct sja1105_table *table; + bool want_tagging; u16 tpid, tpid2; int rc; @@ -1943,8 +1961,10 @@ static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled) general_params->incl_srcpt1 = enabled; general_params->incl_srcpt0 = enabled; + want_tagging = priv->best_effort_vlan_filtering || !enabled; + /* VLAN filtering => independent VLAN learning. - * No VLAN filtering => shared VLAN learning. + * No VLAN filtering (or best effort) => shared VLAN learning. * * In shared VLAN learning mode, untagged traffic still gets * pvid-tagged, and the FDB table gets populated with entries @@ -1963,7 +1983,7 @@ static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled) */ table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS]; l2_lookup_params = table->entries; - l2_lookup_params->shared_learn = !enabled; + l2_lookup_params->shared_learn = want_tagging; rc = sja1105_static_config_reload(priv, SJA1105_VLAN_FILTERING); if (rc) @@ -1971,11 +1991,24 @@ static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled) /* Switch port identification based on 802.1Q is only passable * if we are not under a vlan_filtering bridge. So make sure - * the two configurations are mutually exclusive. + * the two configurations are mutually exclusive (of course, the + * user may know better, i.e. best_effort_vlan_filtering). */ - return sja1105_setup_8021q_tagging(ds, !enabled); + return sja1105_setup_8021q_tagging(ds, want_tagging); } +bool sja1105_can_use_vlan_as_tags(struct dsa_port *dp) +{ + struct dsa_switch *ds = dp->ds; + struct sja1105_private *priv = ds->priv; + + if (dsa_port_is_vlan_filtering(dp) && !priv->best_effort_vlan_filtering) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(sja1105_can_use_vlan_as_tags); + static void sja1105_vlan_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan) { @@ -2048,9 +2081,35 @@ static int sja1105_hostprio_set(struct sja1105_private *priv, u8 hostprio) return sja1105_static_config_reload(priv, SJA1105_HOSTPRIO); } +static int sja1105_best_effort_vlan_filtering_get(struct sja1105_private *priv, + bool *be_vlan) +{ + *be_vlan = priv->best_effort_vlan_filtering; + + return 0; +} + +static int sja1105_best_effort_vlan_filtering_set(struct sja1105_private *priv, + bool be_vlan) +{ + struct dsa_switch *ds = priv->ds; + bool vlan_filtering; + int rc; + + vlan_filtering = dsa_port_is_vlan_filtering(dsa_to_port(ds, 0)); + priv->best_effort_vlan_filtering = be_vlan; + + rtnl_lock(); + rc = sja1105_vlan_filtering(ds, 0, vlan_filtering); + rtnl_unlock(); + + return rc; +} + enum sja1105_devlink_param_id { SJA1105_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX, SJA1105_DEVLINK_PARAM_ID_HOSTPRIO, + SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING, }; static int sja1105_devlink_param_get(struct dsa_switch *ds, u32 id, @@ -2063,6 +2122,10 @@ static int sja1105_devlink_param_get(struct dsa_switch *ds, u32 id, case SJA1105_DEVLINK_PARAM_ID_HOSTPRIO: err = sja1105_hostprio_get(priv, &ctx->val.vu8); break; + case SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING: + err = sja1105_best_effort_vlan_filtering_get(priv, + &ctx->val.vbool); + break; default: err = -EOPNOTSUPP; break; @@ -2081,6 +2144,10 @@ static int sja1105_devlink_param_set(struct dsa_switch *ds, u32 id, case SJA1105_DEVLINK_PARAM_ID_HOSTPRIO: err = sja1105_hostprio_set(priv, ctx->val.vu8); break; + case SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING: + err = sja1105_best_effort_vlan_filtering_set(priv, + ctx->val.vbool); + break; default: err = -EOPNOTSUPP; break; @@ -2093,6 +2160,10 @@ static const struct devlink_param sja1105_devlink_params[] = { DSA_DEVLINK_PARAM_DRIVER(SJA1105_DEVLINK_PARAM_ID_HOSTPRIO, "hostprio", DEVLINK_PARAM_TYPE_U8, BIT(DEVLINK_PARAM_CMODE_RUNTIME)), + DSA_DEVLINK_PARAM_DRIVER(SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING, + "best_effort_vlan_filtering", + DEVLINK_PARAM_TYPE_BOOL, + BIT(DEVLINK_PARAM_CMODE_RUNTIME)), }; static int sja1105_setup_devlink_params(struct dsa_switch *ds) diff --git a/include/linux/dsa/8021q.h b/include/linux/dsa/8021q.h index b8daaec0896e..dfbd5b62f67a 100644 --- a/include/linux/dsa/8021q.h +++ b/include/linux/dsa/8021q.h @@ -25,6 +25,8 @@ struct dsa_8021q_crosschip_link { int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index, bool enabled); +int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags); + int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port, struct dsa_switch *other_ds, int other_port, bool enabled); @@ -58,6 +60,11 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index, return 0; } +int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags) +{ + return 0; +} + int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port, struct dsa_switch *other_ds, int other_port, bool enabled) diff --git a/include/linux/dsa/sja1105.h b/include/linux/dsa/sja1105.h index fa5735c353cd..a609fdbe1355 100644 --- a/include/linux/dsa/sja1105.h +++ b/include/linux/dsa/sja1105.h @@ -61,4 +61,6 @@ struct sja1105_port { bool hwts_tx_en; }; +bool sja1105_can_use_vlan_as_tags(struct dsa_port *dp); + #endif /* _NET_DSA_SJA1105_H */ diff --git a/net/dsa/tag_8021q.c b/net/dsa/tag_8021q.c index ff9c5bf64bda..158584153e15 100644 --- a/net/dsa/tag_8021q.c +++ b/net/dsa/tag_8021q.c @@ -289,6 +289,68 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int port, bool enabled) } EXPORT_SYMBOL_GPL(dsa_port_setup_8021q_tagging); +int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags) +{ + int upstream = dsa_upstream_port(ds, port); + int rx_vid_of = ds->num_ports; + int tx_vid_of = ds->num_ports; + int other_port; + + /* @vid wants to be a pvid of @port, but is not equal to its rx_vid */ + if ((flags & BRIDGE_VLAN_INFO_PVID) && + vid != dsa_8021q_rx_vid(ds, port)) + return -EPERM; + + for (other_port = 0; other_port < ds->num_ports; other_port++) { + if (vid == dsa_8021q_rx_vid(ds, other_port)) { + rx_vid_of = other_port; + break; + } + if (vid == dsa_8021q_tx_vid(ds, other_port)) { + tx_vid_of = other_port; + break; + } + } + + /* @vid is a TX VLAN of the @tx_vid_of port */ + if (tx_vid_of != ds->num_ports) { + if (tx_vid_of == port) { + if (flags != BRIDGE_VLAN_INFO_UNTAGGED) + return -EPERM; + /* Fall through on proper flags */ + } else if (port == upstream) { + if (flags != 0) + return -EPERM; + /* Fall through on proper flags */ + } else { + /* Trying to configure on other port */ + return -EPERM; + } + } + + /* @vid is an RX VLAN of the @rx_vid_of port */ + if (rx_vid_of != ds->num_ports) { + if (rx_vid_of == port) { + if (flags != (BRIDGE_VLAN_INFO_UNTAGGED | + BRIDGE_VLAN_INFO_PVID)) + return -EPERM; + /* Fall through on proper flags */ + } else if (port == upstream) { + if (flags != 0) + return -EPERM; + /* Fall through on proper flags */ + } else if (flags != BRIDGE_VLAN_INFO_UNTAGGED) { + /* Trying to configure on other port, but with + * invalid flags. + */ + return -EPERM; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(dsa_8021q_vid_validate); + int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port, struct dsa_switch *other_ds, int other_port, bool enabled) diff --git a/net/dsa/tag_sja1105.c b/net/dsa/tag_sja1105.c index d553bf36bd41..72d76743c272 100644 --- a/net/dsa/tag_sja1105.c +++ b/net/dsa/tag_sja1105.c @@ -74,7 +74,7 @@ static inline bool sja1105_is_meta_frame(const struct sk_buff *skb) */ static bool sja1105_filter(const struct sk_buff *skb, struct net_device *dev) { - if (!dsa_port_is_vlan_filtering(dev->dsa_ptr)) + if (sja1105_can_use_vlan_as_tags(skb->dev->dsa_ptr)) return true; if (sja1105_is_link_local(skb)) return true; @@ -103,6 +103,7 @@ static struct sk_buff *sja1105_xmit(struct sk_buff *skb, u16 tx_vid = dsa_8021q_tx_vid(dp->ds, dp->index); u16 queue_mapping = skb_get_queue_mapping(skb); u8 pcp = netdev_txq_to_tc(netdev, queue_mapping); + u16 tpid; /* Transmitting management traffic does not rely upon switch tagging, * but instead SPI-installed management routes. Part 2 of this @@ -111,15 +112,12 @@ static struct sk_buff *sja1105_xmit(struct sk_buff *skb, if (unlikely(sja1105_is_link_local(skb))) return sja1105_defer_xmit(dp->priv, skb); - /* If we are under a vlan_filtering bridge, IP termination on - * switch ports based on 802.1Q tags is simply too brittle to - * be passable. So just defer to the dsa_slave_notag_xmit - * implementation. - */ if (dsa_port_is_vlan_filtering(dp)) - return skb; + tpid = ETH_P_8021Q; + else + tpid = ETH_P_SJA1105; - return dsa_8021q_xmit(skb, netdev, ETH_P_SJA1105, + return dsa_8021q_xmit(skb, netdev, tpid, ((pcp << VLAN_PRIO_SHIFT) | tx_vid)); } @@ -258,7 +256,7 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb, hdr = eth_hdr(skb); tpid = ntohs(hdr->h_proto); - is_tagged = (tpid == ETH_P_SJA1105); + is_tagged = (tpid == ETH_P_SJA1105 || tpid == ETH_P_8021Q); is_link_local = sja1105_is_link_local(skb); is_meta = sja1105_is_meta_frame(skb); From patchwork Mon May 4 12:43:23 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Oltean X-Patchwork-Id: 219957 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.6 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C17DAC3A5A9 for ; Mon, 4 May 2020 12:44:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9E5062075B for ; Mon, 4 May 2020 12:44:37 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ozvX5ZbX" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728921AbgEDMof (ORCPT ); Mon, 4 May 2020 08:44:35 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35604 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-FAIL-OK-FAIL) by vger.kernel.org with ESMTP id S1728079AbgEDMoV (ORCPT ); Mon, 4 May 2020 08:44:21 -0400 Received: from mail-wr1-x441.google.com (mail-wr1-x441.google.com [IPv6:2a00:1450:4864:20::441]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6EA79C061A0E for ; Mon, 4 May 2020 05:44:21 -0700 (PDT) Received: by mail-wr1-x441.google.com with SMTP id i10so20721570wrv.10 for ; Mon, 04 May 2020 05:44:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=aMUeZPhAHahhEsEZ8yqPryuY729nsIpF6ZIBOZrog/E=; b=ozvX5ZbXg4DZPJm7JylT5zofJyeJoDfrI+Z4w2TqAW0DergchiK84yfy3rF+gKKbGI y+RDe2b2hiNLTc4cSW6NfdTuo9577enO0Z+o12VnvFs0uZNBzJIxJ+AFKd+8j+D5A/Mi 1UtbB5yYOxFMUafrD1xlRLxtViEUwu5SDLhmP0DKuOXUW06EknxMceuWMFMmeRvwMDex wMgYnra6zMHOqZYBgzhehMXpc32BZn+6O1i9QyRVZ7jha20cFTJIuCEKTEjUknE4b85A F3MP6W8lc5Y6IYU828NQy5IfWZrzHpnH4R9PDtn8AVQYGByvryQWvjRUpdJdjzD8hnpC TDEg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=aMUeZPhAHahhEsEZ8yqPryuY729nsIpF6ZIBOZrog/E=; b=nnvUMYtQnhfUu0D6lbfxEKo6eCgD5mHbnbWA+k4JTrTmRNvxfXWfzJd8IH/wVwG7tM GItbrtwcqR79Pvm2aRP4TP9sWXOuWCyScKRItN/iIYaV6r8A1KjWt+5lVyRbtosDvEf+ LfUFGU+V2dsdW8c21ewaUNmOlSg5wfN+J1wR4GebKoJ+WaUHmlV1r2q1b49aOhSY4EkN aA3KkARq5kk6SZW7vibnJx4tbq4Jp6i4p8dLTGJ9kal/+XnQto5B+o5fp4MOl2I//jCM euP9giWiWCAwu+jFRA1S6Q+IRGIqdKSj0RYITn5FLO5pvjU66lR9s6Xi8opexWGmfdm8 GZog== X-Gm-Message-State: AGi0PuZfFdBle4HPdk6qMkYlbE33Kl21tRfxISnSyoJuc84Tm0PYBL62 Psu+hsl4PpAdlHwDrwsp8zI= X-Google-Smtp-Source: APiQypIuC/TbMrUmkyUT8ix9qZsNg7fPuf12hG3aVdWvW5Pi1PKtg7gZ+wOzpWWZc95eIBStRimTig== X-Received: by 2002:adf:fe01:: with SMTP id n1mr7176445wrr.268.1588596260195; Mon, 04 May 2020 05:44:20 -0700 (PDT) Received: from localhost.localdomain ([86.121.118.29]) by smtp.gmail.com with ESMTPSA id 32sm17343670wrg.19.2020.05.04.05.44.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 04 May 2020 05:44:19 -0700 (PDT) From: Vladimir Oltean To: andrew@lunn.ch, f.fainelli@gmail.com, vivien.didelot@gmail.com Cc: davem@davemloft.net, jiri@resnulli.us, idosch@idosch.org, kuba@kernel.org, netdev@vger.kernel.org, nikolay@cumulusnetworks.com, roopa@cumulusnetworks.com, georg.waibel@sensor-technik.de, o.rempel@pengutronix.de, christian.herber@nxp.com Subject: [RFC 4/6] net: dsa: tag_8021q: skip disabled ports Date: Mon, 4 May 2020 15:43:23 +0300 Message-Id: <20200504124325.26758-5-olteanv@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200504124325.26758-1-olteanv@gmail.com> References: <20200504124325.26758-1-olteanv@gmail.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Vladimir Oltean Not only is it not needed to program dsa_8021q VLANs into ports that are not used, but it also makes validation impossible, given the fact that port == dsa_upstream_port(ds, port) for a port that is disabled. So when a user port wants to install its rx_vid into us (we the disabled port), think that we are in fact the CPU port, so we want the flags to be egress-tagged (which they aren't). So instead of trying to make dsa_upstream_port return something more sensible for disabled ports, skip this nonsense altogether. Because we didn't have VID validation until now, there's no reason to treat this patch as a bugfix. Signed-off-by: Vladimir Oltean --- net/dsa/tag_8021q.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/dsa/tag_8021q.c b/net/dsa/tag_8021q.c index 158584153e15..3958f426d60e 100644 --- a/net/dsa/tag_8021q.c +++ b/net/dsa/tag_8021q.c @@ -239,7 +239,7 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int port, bool enabled) for (i = 0; i < ds->num_ports; i++) { u16 flags; - if (i == upstream) + if (!dsa_is_user_port(ds, i)) continue; else if (i == port) /* The RX VID is pvid on this port */ @@ -302,6 +302,8 @@ int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags) return -EPERM; for (other_port = 0; other_port < ds->num_ports; other_port++) { + if (!dsa_is_user_port(ds, other_port)) + continue; if (vid == dsa_8021q_rx_vid(ds, other_port)) { rx_vid_of = other_port; break; From patchwork Mon May 4 12:43:24 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Oltean X-Patchwork-Id: 219958 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.6 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id A126FC47254 for ; Mon, 4 May 2020 12:44:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 701DF2071C for ; Mon, 4 May 2020 12:44:28 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="rw3MrNKN" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728904AbgEDMo1 (ORCPT ); Mon, 4 May 2020 08:44:27 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35610 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-FAIL-OK-FAIL) by vger.kernel.org with ESMTP id S1728125AbgEDMoX (ORCPT ); Mon, 4 May 2020 08:44:23 -0400 Received: from mail-wr1-x444.google.com (mail-wr1-x444.google.com [IPv6:2a00:1450:4864:20::444]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0F93DC061A0E for ; Mon, 4 May 2020 05:44:23 -0700 (PDT) Received: by mail-wr1-x444.google.com with SMTP id i10so20721687wrv.10 for ; Mon, 04 May 2020 05:44:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=k9wnPAkGNLB0wQOlYn6wdF/PnMiFe9GBaa49zGf0rRY=; b=rw3MrNKNK4UYS2LTZNsVF4tMZGTIT+kXwGNegImFKt0cwPYTY2AObpUJJtL9CR7E4i RR5q0Gcga0zdr4vAUriWkh+6pacmg1xf39SjyAEMbxmWWiwsVWoNlUSDDbjskkqTTiNe Dn8F2PW+dNaVyTGlCZyx28mMkjWynNgYh2SIYqedOcdYBlbw+OR4hibeNPKReDOOScEV DtEV3sNq/4avZ1y9v3Hwo3yny9eG2Syh+cSGZy7h/YHKnyX1Y6C6wKiEX3khTrzdIB+A jT+TpkqgpGJQchQSGuJ1VxsIgT15o4guvvweAWUQhWW99mRR7E32RWtmzr4Rtp+vD2z3 Tj0w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=k9wnPAkGNLB0wQOlYn6wdF/PnMiFe9GBaa49zGf0rRY=; b=r4nsDbxqXhM3cyX0t6u7nCFaPAJpwcURxfmE2fh4lB6gv6B0IWppkcLd4DSCOiAI+I jzBhSOWzRwf1i02e1RC0wM/ET2QNECwuFAkhk8PU4EJndOtEgkBnk/WMCfsobkJZ778/ Gz8WwGyEYmCOGk4byQGUNb8fYNSYVUz0WlJ4VYLY77VGbvnF2mqNcH+000aPJjM0ZOcA 6AwBJ7Yv+EeQo6xDn5TLDDwUMIeLbCkdxx2yWeUTHcqY7kE7sEuoFGQs95wfFBM9BoSv bNyopDGWDwt0Q4+7lP4f1+O6lyUSNAng7NNZAPHSltIqtOg7JHglELpl32ImQbHSmdra yp5w== X-Gm-Message-State: AGi0PuZsIXwtRkwEITw0+pqV9EjBczxgIPgh8Sko/Mktdf23LRgfywSO l5xNHjeYXDkdHJy/JFDcm5k= X-Google-Smtp-Source: APiQypIUYrmRG8C1j4X7GDXO+7ouzB5jlUG/1xc9y8U/nyQ8jGl08x8fjWepBidnLdBRaLzw0usGPg== X-Received: by 2002:adf:e751:: with SMTP id c17mr1943491wrn.351.1588596261459; Mon, 04 May 2020 05:44:21 -0700 (PDT) Received: from localhost.localdomain ([86.121.118.29]) by smtp.gmail.com with ESMTPSA id 32sm17343670wrg.19.2020.05.04.05.44.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 04 May 2020 05:44:21 -0700 (PDT) From: Vladimir Oltean To: andrew@lunn.ch, f.fainelli@gmail.com, vivien.didelot@gmail.com Cc: davem@davemloft.net, jiri@resnulli.us, idosch@idosch.org, kuba@kernel.org, netdev@vger.kernel.org, nikolay@cumulusnetworks.com, roopa@cumulusnetworks.com, georg.waibel@sensor-technik.de, o.rempel@pengutronix.de, christian.herber@nxp.com Subject: [RFC 5/6] net: dsa: sja1105: support up to 7 VLANs per port using retagging Date: Mon, 4 May 2020 15:43:24 +0300 Message-Id: <20200504124325.26758-6-olteanv@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200504124325.26758-1-olteanv@gmail.com> References: <20200504124325.26758-1-olteanv@gmail.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Vladimir Oltean For switches that support VLAN retagging, such as sja1105, we extend dsa_8021q by encoding a "sub-VLAN" into the remaining 3 free bits in the dsa_8021q tag. A sub-VLAN is nothing more than a number in the range 0-7, which serves as an index into a per-port driver lookup table. The sub-VLAN value of zero means that traffic is untagged (this is also backwards-compatible with dsa_8021q without retagging). The switch is configured to retag VLAN-tagged traffic that gets transmitted towards the CPU port (and towards the CPU only). Example: bridge vlan add dev sw1p0 vid 100 The switch retags frames received on port 0, going to the CPU, and having VID 100, to the VID of 1104 0x0450. In dsa_8021q language, 0x0450 means: | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +-----------+-----+-----------------+-----------+-----------------------+ | DIR | SVL | SWITCH_ID | SUBVLAN | PORT | +-----------+-----+-----------------+-----------+-----------------------+ aka: - DIR = 0b01: this is an RX VLAN - SUBVLAN = 0b001: this is subvlan #1 - SWITCH_ID = 0b001: this is switch 1 (see the name "sw1p0") - PORT = 0b0000: this is port 0 (see the name "sw1p0") The driver also remembers the "1 -> 100" mapping. In the hotpath, if the sub-VLAN from the tag encodes a non-untagged frame, this mapping is used to create a VLAN hwaccel tag, with the value of 100. There are some performance-related concerns, since all VLAN-retagged traffic cannot exceed 1Gbps due to the way it is implemented in hardware. This should not be an issue, because: - We only support retagging towards the CPU port, which is limited at 1Gbps anyway. - VLAN-tagged traffic between ports on the same chip works without retagging. - Untagged traffic, autonomously forwarded as well as terminated locally, works without retagging. On xmit from Linux, transmitting VLAN-tagged traffic is possible by adding a second VLAN tag with the tx_vid (which encode the destination port). But this tag needs to be transmitted using a different TPID than the plain ETH_P_8021Q, because otherwise, the switch thinks we're trying to do VLAN hopping, it freaks out and drops our frame. By using a TPID of ETH_P_8021AD, it looks at the S-tag only, which is what we want. Signed-off-by: Vladimir Oltean --- drivers/net/dsa/sja1105/sja1105_main.c | 244 ++++++++++++++++++++++++- include/linux/dsa/8021q.h | 31 ++++ include/linux/dsa/sja1105.h | 2 + net/dsa/tag_8021q.c | 80 ++++++-- net/dsa/tag_sja1105.c | 21 ++- 5 files changed, 357 insertions(+), 21 deletions(-) diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c index edbe5dd4af37..106182103b19 100644 --- a/drivers/net/dsa/sja1105/sja1105_main.c +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -387,7 +387,8 @@ static int sja1105_init_l2_forwarding_params(struct sja1105_private *priv) /* Disallow dynamic reconfiguration of vlan_pmap */ .max_dynp = 0, /* Use a single memory partition for all ingress queues */ - .part_spc = { SJA1105_MAX_FRAME_MEMORY, 0, 0, 0, 0, 0, 0, 0 }, + .part_spc = { SJA1105_MAX_FRAME_MEMORY_RETAGGING, + 0, 0, 0, 0, 0, 0, 0 }, }; struct sja1105_table *table; @@ -1733,6 +1734,31 @@ static int sja1105_is_vlan_configured(struct sja1105_private *priv, u16 vid) return -1; } +/* The Retagging Table generates packet *clones* with the new VLAN. This is a + * very odd hardware quirk which we need to suppress by dropping the original + * packet. We do that by removing the pre-retagging VID from the port + * membership of the egress port. For this strategy to be effective, we need a + * blacklist to ensure that nobody can add that VID back on the destination + * port, otherwise we'll see duplicates (with the old and the new VID). + */ +static bool sja1105_vlan_is_blacklisted(struct sja1105_private *priv, int port, + u16 vid) +{ + struct sja1105_retagging_entry *retagging; + struct sja1105_table *table; + int i; + + table = &priv->static_config.tables[BLK_IDX_RETAGGING]; + retagging = table->entries; + + for (i = 0; i < table->entry_count; i++) + if ((retagging[i].egr_port & BIT(port)) && + (retagging[i].vlan_ing == vid)) + return true; + + return false; +} + static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid, bool enabled, bool untagged) { @@ -1741,6 +1767,9 @@ static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid, bool keep = true; int match, rc; + if (enabled && sja1105_vlan_is_blacklisted(priv, port, vid)) + return 0; + table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP]; match = sja1105_is_vlan_configured(priv, vid); @@ -1793,6 +1822,194 @@ static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid, return 0; } +static int sja1105_find_retagging_entry(struct sja1105_private *priv, + int from_port, u16 from_vid, + int to_port, u16 to_vid) +{ + struct sja1105_retagging_entry *retagging; + struct sja1105_table *table; + int i; + + table = &priv->static_config.tables[BLK_IDX_RETAGGING]; + retagging = table->entries; + + for (i = 0; i < table->entry_count; i++) + if (retagging[i].ing_port & BIT(from_port) && + retagging[i].egr_port & BIT(to_port) && + retagging[i].vlan_ing == from_vid && + retagging[i].vlan_egr == to_vid) + return i; + + return -1; +} + +static int sja1105_setup_retagging_vid(struct sja1105_private *priv, + int from_port, u16 from_vid, int to_port, + u16 to_vid, bool keep, bool untagged) +{ + int rc; + + rc = sja1105_vlan_apply(priv, from_port, to_vid, keep, true); + if (rc) + return rc; + + rc = sja1105_vlan_apply(priv, to_port, to_vid, keep, untagged); + if (rc) + return rc; + + return sja1105_vlan_apply(priv, to_port, from_vid, false, false); +} + +static int sja1105_retagging_apply(struct sja1105_private *priv, int from_port, + u16 from_vid, int to_port, u16 to_vid, + bool keep, bool untagged) +{ + struct sja1105_retagging_entry *retagging; + struct sja1105_table *table; + int rc, match; + + rc = sja1105_setup_retagging_vid(priv, from_port, from_vid, to_port, + to_vid, keep, untagged); + if (rc) + return rc; + + table = &priv->static_config.tables[BLK_IDX_RETAGGING]; + + match = sja1105_find_retagging_entry(priv, from_port, from_vid, + to_port, to_vid); + if (match < 0) { + /* Can't delete a missing entry. */ + if (!keep) { + dev_err(priv->ds->dev, "can't delete a missing entry\n"); + return 0; + } + + /* No match => new entry */ + rc = sja1105_table_resize(table, table->entry_count + 1); + if (rc) { + dev_err(priv->ds->dev, "failed to resize retagging table: %d\n", rc); + return rc; + } + + match = table->entry_count - 1; + } + + /* Assign pointer after the resize (it may be new memory) */ + retagging = table->entries; + + if (keep) { + retagging[match].egr_port = BIT(to_port); + retagging[match].ing_port = BIT(from_port); + retagging[match].vlan_ing = from_vid; + retagging[match].vlan_egr = to_vid; + retagging[match].do_not_learn = false; + retagging[match].use_dest_ports = true; + retagging[match].destports = BIT(to_port); + + dev_err(priv->ds->dev, + "%s: entry %d egr_port 0x%llx ing_port 0x%llx vlan_ing %lld vlan_egr %lld do_not_learn %lld use_dest_ports %lld destports %lld\n", + __func__, match, retagging[match].egr_port, retagging[match].ing_port, retagging[match].vlan_ing, retagging[match].vlan_egr, + retagging[match].do_not_learn, retagging[match].use_dest_ports, retagging[match].destports); + return sja1105_dynamic_config_write(priv, BLK_IDX_RETAGGING, + match, &retagging[match], + true); + } + + /* To remove, the strategy is to overwrite the element with + * the last one, and then reduce the array size by 1 + */ + retagging[match] = retagging[table->entry_count - 1]; + + rc = sja1105_dynamic_config_write(priv, BLK_IDX_RETAGGING, + table->entry_count - 1, + &retagging[table->entry_count - 1], + false); + if (rc) + return rc; + + rc = sja1105_dynamic_config_write(priv, BLK_IDX_RETAGGING, match, + &retagging[match], true); + if (rc) + return rc; + + return sja1105_table_resize(table, table->entry_count - 1); +} + +static int sja1105_find_free_subvlan(struct sja1105_private *priv, int port) +{ + struct sja1105_port *sp = &priv->ports[port]; + int subvlan; + + for (subvlan = 1; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++) + if (sp->subvlan_map[subvlan] == VLAN_N_VID) + return subvlan; + + return -1; +} + +static int sja1105_find_subvlan(struct sja1105_private *priv, int port, u16 vid) +{ + struct sja1105_port *sp = &priv->ports[port]; + int subvlan; + + for (subvlan = 1; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++) + if (sp->subvlan_map[subvlan] == vid) + return subvlan; + + return -1; +} + +static int sja1105_subvlan_apply(struct sja1105_private *priv, int port, + u16 vid, bool pvid, bool keep) +{ + struct sja1105_port *sp = &priv->ports[port]; + int cpu = dsa_upstream_port(priv->ds, port); + int rc, subvlan; + u16 rx_vid; + + /* There are several situations when we don't want to add a subvlan */ + if (!priv->best_effort_vlan_filtering) + return 0; + if (vid_is_dsa_8021q(vid)) + return 0; + if (!dsa_is_user_port(priv->ds, port)) + return 0; + + if (keep) { + subvlan = sja1105_find_free_subvlan(priv, port); + if (subvlan < 0) { + dev_err(priv->ds->dev, "No more free subvlans\n"); + return -ENOSPC; + } + } else { + subvlan = sja1105_find_subvlan(priv, port, vid); + if (subvlan < 0) + /* A subvlan may not be found because either we ran out + * (and that's ok, after all, we only support up to 7 + * per port), or because the VID was added prior to + * best_effort_vlan_filtering getting toggled. So it's + * perfectly fine, don't do anything. + */ + return 0; + } + + if (pvid) + rx_vid = dsa_8021q_rx_vid(priv->ds, port); + else + rx_vid = dsa_8021q_rx_vid_subvlan(priv->ds, port, subvlan); + + rc = sja1105_retagging_apply(priv, port, vid, cpu, rx_vid, keep, false); + if (rc) + return rc; + + if (keep) + sp->subvlan_map[subvlan] = vid; + else + sp->subvlan_map[subvlan] = VLAN_N_VID; + + return 0; +} + static int sja1105_crosschip_bridge_join(struct dsa_switch *ds, int tree_index, int sw_index, int other_port, struct net_device *br) @@ -1918,8 +2135,13 @@ static int sja1105_vlan_prepare(struct dsa_switch *ds, int port, */ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { rc = dsa_8021q_vid_validate(ds, port, vid, vlan->flags); - if (rc < 0) - return rc; + /* Suppress the "wrong pvid" error. We can (and will) retag the + * pvid requested by the bridge to the dsa_8021q pvid. Untagged + * traffic is still tagged with the dsa_8021q pvid directly and + * does not require retagging. + */ + if (rc < 0 && rc != DSA_8021Q_WRONG_PVID) + return -EPERM; } return 0; @@ -2017,6 +2239,8 @@ static void sja1105_vlan_add(struct dsa_switch *ds, int port, int rc; for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + rc = sja1105_vlan_apply(priv, port, vid, true, vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); if (rc < 0) { @@ -2024,7 +2248,7 @@ static void sja1105_vlan_add(struct dsa_switch *ds, int port, vid, port, rc); return; } - if (vlan->flags & BRIDGE_VLAN_INFO_PVID) { + if (pvid) { rc = sja1105_pvid_apply(ds->priv, port, vid); if (rc < 0) { dev_err(ds->dev, "Failed to set pvid %d on port %d: %d\n", @@ -2032,6 +2256,9 @@ static void sja1105_vlan_add(struct dsa_switch *ds, int port, return; } } + rc = sja1105_subvlan_apply(priv, port, vid, pvid, true); + if (rc) + return; } } @@ -2043,6 +2270,8 @@ static int sja1105_vlan_del(struct dsa_switch *ds, int port, int rc; for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + rc = sja1105_vlan_apply(priv, port, vid, false, vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); if (rc < 0) { @@ -2050,6 +2279,9 @@ static int sja1105_vlan_del(struct dsa_switch *ds, int port, vid, port, rc); return rc; } + rc = sja1105_subvlan_apply(priv, port, vid, pvid, false); + if (rc) + return rc; } return 0; } @@ -2728,6 +2960,7 @@ static int sja1105_probe(struct spi_device *spi) struct sja1105_port *sp = &priv->ports[port]; struct dsa_port *dp = dsa_to_port(ds, port); struct net_device *slave; + int subvlan; if (!dsa_is_user_port(ds, port)) continue; @@ -2747,6 +2980,9 @@ static int sja1105_probe(struct spi_device *spi) goto out; } skb_queue_head_init(&sp->xmit_queue); + + for (subvlan = 0; subvlan < DSA_8021Q_N_SUBVLAN; subvlan++) + sp->subvlan_map[subvlan] = VLAN_N_VID; } return 0; diff --git a/include/linux/dsa/8021q.h b/include/linux/dsa/8021q.h index dfbd5b62f67a..40d85d3cdf15 100644 --- a/include/linux/dsa/8021q.h +++ b/include/linux/dsa/8021q.h @@ -20,6 +20,16 @@ struct dsa_8021q_crosschip_link { refcount_t refcount; }; +enum dsa_8021q_vid_error { + DSA_8021Q_VID_OK = 0, + DSA_8021Q_WRONG_PVID = -1, + DSA_8021Q_TX_VLAN_WRONG_FLAGS = -2, + DSA_8021Q_TX_VLAN_WRONG_PORT = -3, + DSA_8021Q_RX_VLAN_WRONG_FLAGS = -4, +}; + +#define DSA_8021Q_N_SUBVLAN 8 + #if IS_ENABLED(CONFIG_NET_DSA_TAG_8021Q) int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index, @@ -48,10 +58,16 @@ u16 dsa_8021q_tx_vid(struct dsa_switch *ds, int port); u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port); +u16 dsa_8021q_rx_vid_subvlan(struct dsa_switch *ds, int port, u16 subvlan); + int dsa_8021q_rx_switch_id(u16 vid); int dsa_8021q_rx_source_port(u16 vid); +u16 dsa_8021q_rx_subvlan(u16 vid); + +bool vid_is_dsa_8021q(u16 vid); + #else int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index, @@ -104,6 +120,11 @@ u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port) return 0; } +u16 dsa_8021q_rx_vid_subvlan(struct dsa_switch *ds, int port, u16 subvlan) +{ + return 0; +} + int dsa_8021q_rx_switch_id(u16 vid) { return 0; @@ -114,6 +135,16 @@ int dsa_8021q_rx_source_port(u16 vid) return 0; } +u16 dsa_8021q_rx_subvlan(u16 vid) +{ + return 0; +} + +bool vid_is_dsa_8021q(u16 vid) +{ + return false; +} + #endif /* IS_ENABLED(CONFIG_NET_DSA_TAG_8021Q) */ #endif /* _NET_DSA_8021Q_H */ diff --git a/include/linux/dsa/sja1105.h b/include/linux/dsa/sja1105.h index a609fdbe1355..ef04625087ef 100644 --- a/include/linux/dsa/sja1105.h +++ b/include/linux/dsa/sja1105.h @@ -9,6 +9,7 @@ #include #include +#include #include #define ETH_P_SJA1105 ETH_P_DSA_8021Q @@ -53,6 +54,7 @@ struct sja1105_skb_cb { ((struct sja1105_skb_cb *)DSA_SKB_CB_PRIV(skb)) struct sja1105_port { + u16 subvlan_map[DSA_8021Q_N_SUBVLAN]; struct kthread_worker *xmit_worker; struct kthread_work xmit_work; struct sk_buff_head xmit_queue; diff --git a/net/dsa/tag_8021q.c b/net/dsa/tag_8021q.c index 3958f426d60e..48d4cb42763f 100644 --- a/net/dsa/tag_8021q.c +++ b/net/dsa/tag_8021q.c @@ -17,7 +17,7 @@ * * | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * +-----------+-----+-----------------+-----------+-----------------------+ - * | DIR | RSV | SWITCH_ID | RSV | PORT | + * | DIR | SVL | SWITCH_ID | SUBVLAN | PORT | * +-----------+-----+-----------------+-----------+-----------------------+ * * DIR - VID[11:10]: @@ -27,17 +27,24 @@ * These values make the special VIDs of 0, 1 and 4095 to be left * unused by this coding scheme. * - * RSV - VID[9]: - * To be used for further expansion of SWITCH_ID or for other purposes. - * Must be transmitted as zero and ignored on receive. + * SVL/SUBVLAN - { VID[9], VID[5:4] }: + * Sub-VLAN encoding. Valid only when DIR indicates an RX VLAN. + * * 0 (0b000): Field does not encode a sub-VLAN, either because + * received traffic is untagged, PVID-tagged or because a second + * VLAN tag is present after this tag and not inside of it. + * * 1 (0b001): Received traffic is tagged with a VID value private + * to the host. This field encodes the index in the host's lookup + * table through which the value of the ingress VLAN ID can be + * recovered. + * * 2 (0b010): Field encodes a sub-VLAN. + * ... + * * 7 (0b111): Field encodes a sub-VLAN. + * When DIR indicates a TX VLAN, SUBVLAN must be transmitted as zero + * (by the host) and ignored on receive (by the switch). * * SWITCH_ID - VID[8:6]: * Index of switch within DSA tree. Must be between 0 and 7. * - * RSV - VID[5:4]: - * To be used for further expansion of PORT or for other purposes. - * Must be transmitted as zero and ignored on receive. - * * PORT - VID[3:0]: * Index of switch port. Must be between 0 and 15. */ @@ -54,6 +61,18 @@ #define DSA_8021Q_SWITCH_ID(x) (((x) << DSA_8021Q_SWITCH_ID_SHIFT) & \ DSA_8021Q_SWITCH_ID_MASK) +#define DSA_8021Q_SUBVLAN_HI_SHIFT 9 +#define DSA_8021Q_SUBVLAN_HI_MASK GENMASK(9, 9) +#define DSA_8021Q_SUBVLAN_LO_SHIFT 4 +#define DSA_8021Q_SUBVLAN_LO_MASK GENMASK(4, 3) +#define DSA_8021Q_SUBVLAN_HI(x) (((x) & GENMASK(2, 2)) >> 2) +#define DSA_8021Q_SUBVLAN_LO(x) ((x) & GENMASK(1, 0)) +#define DSA_8021Q_SUBVLAN(x) \ + (((DSA_8021Q_SUBVLAN_LO(x) << DSA_8021Q_SUBVLAN_LO_SHIFT) & \ + DSA_8021Q_SUBVLAN_LO_MASK) | \ + ((DSA_8021Q_SUBVLAN_HI(x) << DSA_8021Q_SUBVLAN_HI_SHIFT) & \ + DSA_8021Q_SUBVLAN_HI_MASK)) + #define DSA_8021Q_PORT_SHIFT 0 #define DSA_8021Q_PORT_MASK GENMASK(3, 0) #define DSA_8021Q_PORT(x) (((x) << DSA_8021Q_PORT_SHIFT) & \ @@ -79,6 +98,13 @@ u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port) } EXPORT_SYMBOL_GPL(dsa_8021q_rx_vid); +u16 dsa_8021q_rx_vid_subvlan(struct dsa_switch *ds, int port, u16 subvlan) +{ + return DSA_8021Q_DIR_RX | DSA_8021Q_SWITCH_ID(ds->index) | + DSA_8021Q_PORT(port) | DSA_8021Q_SUBVLAN(subvlan); +} +EXPORT_SYMBOL_GPL(dsa_8021q_rx_vid_subvlan); + /* Returns the decoded switch ID from the RX VID. */ int dsa_8021q_rx_switch_id(u16 vid) { @@ -93,6 +119,27 @@ int dsa_8021q_rx_source_port(u16 vid) } EXPORT_SYMBOL_GPL(dsa_8021q_rx_source_port); +/* Returns the decoded subvlan from the RX VID. */ +u16 dsa_8021q_rx_subvlan(u16 vid) +{ + u16 svl_hi, svl_lo; + + svl_hi = (vid & DSA_8021Q_SUBVLAN_HI_MASK) >> + DSA_8021Q_SUBVLAN_HI_SHIFT; + svl_lo = (vid & DSA_8021Q_SUBVLAN_LO_MASK) >> + DSA_8021Q_SUBVLAN_LO_SHIFT; + + return (svl_hi << 2) | svl_lo; +} +EXPORT_SYMBOL_GPL(dsa_8021q_rx_subvlan); + +bool vid_is_dsa_8021q(u16 vid) +{ + return ((vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_RX || + (vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_TX); +} +EXPORT_SYMBOL_GPL(vid_is_dsa_8021q); + static int dsa_8021q_restore_pvid(struct dsa_switch *ds, int port) { struct bridge_vlan_info vinfo; @@ -289,7 +336,8 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int port, bool enabled) } EXPORT_SYMBOL_GPL(dsa_port_setup_8021q_tagging); -int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags) +enum dsa_8021q_vid_error dsa_8021q_vid_validate(struct dsa_switch *ds, int port, + u16 vid, u16 flags) { int upstream = dsa_upstream_port(ds, port); int rx_vid_of = ds->num_ports; @@ -299,7 +347,7 @@ int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags) /* @vid wants to be a pvid of @port, but is not equal to its rx_vid */ if ((flags & BRIDGE_VLAN_INFO_PVID) && vid != dsa_8021q_rx_vid(ds, port)) - return -EPERM; + return DSA_8021Q_WRONG_PVID; for (other_port = 0; other_port < ds->num_ports; other_port++) { if (!dsa_is_user_port(ds, other_port)) @@ -318,15 +366,15 @@ int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags) if (tx_vid_of != ds->num_ports) { if (tx_vid_of == port) { if (flags != BRIDGE_VLAN_INFO_UNTAGGED) - return -EPERM; + return DSA_8021Q_TX_VLAN_WRONG_FLAGS; /* Fall through on proper flags */ } else if (port == upstream) { if (flags != 0) - return -EPERM; + return DSA_8021Q_TX_VLAN_WRONG_FLAGS; /* Fall through on proper flags */ } else { /* Trying to configure on other port */ - return -EPERM; + return DSA_8021Q_TX_VLAN_WRONG_PORT; } } @@ -335,17 +383,17 @@ int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags) if (rx_vid_of == port) { if (flags != (BRIDGE_VLAN_INFO_UNTAGGED | BRIDGE_VLAN_INFO_PVID)) - return -EPERM; + return DSA_8021Q_RX_VLAN_WRONG_FLAGS; /* Fall through on proper flags */ } else if (port == upstream) { if (flags != 0) - return -EPERM; + return DSA_8021Q_RX_VLAN_WRONG_FLAGS; /* Fall through on proper flags */ } else if (flags != BRIDGE_VLAN_INFO_UNTAGGED) { /* Trying to configure on other port, but with * invalid flags. */ - return -EPERM; + return DSA_8021Q_RX_VLAN_WRONG_FLAGS; } } diff --git a/net/dsa/tag_sja1105.c b/net/dsa/tag_sja1105.c index 72d76743c272..b47c38cd5fcc 100644 --- a/net/dsa/tag_sja1105.c +++ b/net/dsa/tag_sja1105.c @@ -113,7 +113,7 @@ static struct sk_buff *sja1105_xmit(struct sk_buff *skb, return sja1105_defer_xmit(dp->priv, skb); if (dsa_port_is_vlan_filtering(dp)) - tpid = ETH_P_8021Q; + tpid = ETH_P_8021AD; else tpid = ETH_P_SJA1105; @@ -242,6 +242,20 @@ static struct sk_buff return skb; } +static void sja1105_decode_subvlan(struct sk_buff *skb, u16 subvlan) +{ + struct dsa_port *dp = dsa_slave_to_port(skb->dev); + struct sja1105_port *sp = dp->priv; + u16 vid = sp->subvlan_map[subvlan]; + u16 vlan_tci; + + if (vid == VLAN_N_VID) + return; + + vlan_tci = (skb->priority << VLAN_PRIO_SHIFT) | vid; + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan_tci); +} + static struct sk_buff *sja1105_rcv(struct sk_buff *skb, struct net_device *netdev, struct packet_type *pt) @@ -251,6 +265,7 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb, struct ethhdr *hdr; u16 tpid, vid, tci; bool is_link_local; + u16 subvlan = 0; bool is_tagged; bool is_meta; @@ -274,6 +289,7 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb, source_port = dsa_8021q_rx_source_port(vid); switch_id = dsa_8021q_rx_switch_id(vid); skb->priority = (tci & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT; + subvlan = dsa_8021q_rx_subvlan(vid); } else if (is_link_local) { /* Management traffic path. Switch embeds the switch ID and * port ID into bytes of the destination MAC, courtesy of @@ -298,6 +314,9 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb, return NULL; } + if (subvlan) + sja1105_decode_subvlan(skb, subvlan); + return sja1105_rcv_meta_state_machine(skb, &meta, is_link_local, is_meta); }