From patchwork Sun Mar 24 03:23:41 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Oltean X-Patchwork-Id: 1062814 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="quq3HJoA"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44RjSz4SgWz9sST for ; Sun, 24 Mar 2019 14:25:23 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728208AbfCXDZV (ORCPT ); Sat, 23 Mar 2019 23:25:21 -0400 Received: from mail-wm1-f68.google.com ([209.85.128.68]:38971 "EHLO mail-wm1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728164AbfCXDZS (ORCPT ); Sat, 23 Mar 2019 23:25:18 -0400 Received: by mail-wm1-f68.google.com with SMTP id t124so5637058wma.4 for ; Sat, 23 Mar 2019 20:25:16 -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=0+W1bEBVRt3APG39Z7qYnqltxrH/h5UNdi/jptWnVAo=; b=quq3HJoAQnVI26me8HQez5R5BYQ2CciVjtHPgsS7YK3/yJZGLTVcGYsj3yDn6byqFM QYA1xgcq2eMiIbdJUNk2vfmVnwdaPc6IMRWIHlyVEtD/thX5rjvqoWAPOcscm3s2n9tb dSZf/nD4y2KH8lM53UMagXz0RN4b043Y8K+ywohZg0im94SFEZgMcIccBx5Wu69jiN5O nkk2cErQC+S45qa1bvI4fVNlr0QHMwcqrFEcs1RlCEMkHf6Wta8I05sxTdROc4HcvwRb 0gduJhPLhncnjIAjHyhFWiJdHWyAhVVQdggECgGI1y0amwzJcyEEVmPo91zI5ZsHE2Aw ClBw== 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=0+W1bEBVRt3APG39Z7qYnqltxrH/h5UNdi/jptWnVAo=; b=i5nlXekexxRNvZPQVI4i3AR0U2wgow35AdRe78T9nH6ahwvHNk+VOtr4crEyk8q3qk cE50OSnMKjrgAqETlO3nYUJtEyujlw7dPhjCr2k3pAMaN21EMqC/aPuPiT/fyVPHajyD pUY9eMJ9oJvUagD8elF9WYOtVmNwK5wN9yVV8lZiuVBe296ta0ispY9vtUeWCNl+/oL6 SMkuq3oXQppHaBpYoj6n7OPAAa6+3tKSRA6d7iljTO7uoNHEz1DBNtgnfXAj9WroLA7N IQEl2XtsoAxLB4PCiy0TKLCIiVo8/+b0u0liBhGN8/Jxexl8eAOxvNZyaZQYd/2UWwHJ Wf6Q== X-Gm-Message-State: APjAAAWDs1lliOQC906oC2OeYtv7ruqCVY8n+ur6OhO6GcZfkulo9c9r 1Fyx0/BRmQ3Wnnv6gii+xiY= X-Google-Smtp-Source: APXvYqyAu0rszDBynFORm/isUCnEHje1V89M04UONyXBMoBgJMzV3jv57OXM4Uxh6MQ9GMNYSyLMyQ== X-Received: by 2002:a1c:21c1:: with SMTP id h184mr1441696wmh.128.1553397915361; Sat, 23 Mar 2019 20:25:15 -0700 (PDT) Received: from localhost.localdomain ([188.26.228.227]) by smtp.gmail.com with ESMTPSA id c20sm12243049wre.28.2019.03.23.20.25.14 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 23 Mar 2019 20:25:14 -0700 (PDT) From: Vladimir Oltean To: davem@davemloft.net, netdev@vger.kernel.org Cc: f.fainelli@gmail.com, andrew@lunn.ch, vivien.didelot@gmail.com, linus.walleij@linaro.org, Vladimir Oltean Subject: [RFC PATCH net-next 08/13] net: dsa: sja1105: Add support for VLAN operations Date: Sun, 24 Mar 2019 05:23:41 +0200 Message-Id: <20190324032346.32394-9-olteanv@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190324032346.32394-1-olteanv@gmail.com> References: <20190324032346.32394-1-olteanv@gmail.com> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org VLAN filtering cannot be properly disabled in SJA1105. So in order to emulate the "no VLAN awareness" behavior (not dropping traffic that is tagged with a VID that isn't configured on the port), we need to hack another switch feature: programmable TPID (which is 0x8100 for 802.1Q). We are reprogramming the TPID to a bogus value (ETH_P_EDSA) which leaves the switch thinking that all traffic is untagged, and therefore accepts it. Under a vlan_filtering bridge, the proper TPID of ETH_P_8021Q is installed again, and the switch starts identifying 802.1Q-tagged traffic. Signed-off-by: Vladimir Oltean Reviewed-by: Florian Fainelli --- drivers/net/dsa/sja1105/sja1105_main.c | 275 ++++++++++++++++++++++++- 1 file changed, 273 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c index afcca9926497..a1d7f3b03099 100644 --- a/drivers/net/dsa/sja1105/sja1105_main.c +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -263,6 +263,13 @@ static int sja1105_init_static_vlan(struct sja1105_private *priv) table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP]; /* The static VLAN table will only contain the initial pvid of 0. + * All other VLANs are to be configured through dynamic entries, + * and kept in the static configuration table as backing memory. + * The pvid of 0 is sufficient to pass traffic while the ports are + * standalone and when vlan_filtering is disabled. When filtering + * gets enabled, the switchdev core sets up the VLAN ID 1 and sets + * it as the new pvid. Actually 'pvid 1' still comes up in 'bridge + * vlan' even when vlan_filtering is off, but it has no effect. */ if (table->entry_count) { kfree(table->entries); @@ -403,8 +410,11 @@ static int sja1105_init_general_params(struct sja1105_private *priv) .vlmask = 0, /* Only update correctionField for 1-step PTP (L2 transport) */ .ignore2stf = 0, - .tpid = ETH_P_8021Q, - .tpid2 = ETH_P_8021Q, + /* Forcefully disable VLAN filtering by telling + * the switch that VLAN has a different EtherType. + */ + .tpid = ETH_P_EDSA, + .tpid2 = ETH_P_EDSA, /* P/Q/R/S only */ .queue_ts = 0, .egrmirrvid = 0, @@ -934,12 +944,269 @@ static void sja1105_bridge_leave(struct dsa_switch *ds, int port, sja1105_bridge_member(ds, port, br, false); } +/* For situations where we need to change a setting at runtime that is only + * available through the static configuration, resetting the switch in order + * to upload the new static config is unavoidable. Back up the settings we + * modify at runtime (currently only MAC) and restore them after uploading, + * such that this operation is relatively seamless. + */ +static int sja1105_static_config_reload(struct sja1105_private *priv) +{ + struct sja1105_mac_config_entry *mac; + int speed_mbps[SJA1105_NUM_PORTS]; + int rc, i; + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + + /* Back up settings changed by sja1105_adjust_port_config and + * and restore their defaults. + */ + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + speed_mbps[i] = sja1105_speed[mac[i].speed]; + mac[i].speed = SJA1105_SPEED_AUTO; + } + + /* Reset switch and send updated static configuration */ + rc = sja1105_static_config_upload(priv); + if (rc < 0) + goto out; + + /* Configure the CGU (PLLs) for MII and RMII PHYs. + * For these interfaces there is no dynamic configuration + * needed, since PLLs have same settings at all speeds. + */ + rc = sja1105_clocking_setup(priv); + if (rc < 0) + goto out; + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + bool enabled = (speed_mbps[i] != SJA1105_SPEED_AUTO); + + rc = sja1105_adjust_port_config(priv, i, speed_mbps[i], + enabled); + if (rc < 0) + goto out; + } +out: + return rc; +} + +#define sja1105_vlan_filtering_enabled(priv) \ + (((struct sja1105_general_params_entry *) \ + ((struct sja1105_private *)priv)->static_config. \ + tables[BLK_IDX_GENERAL_PARAMS].entries)->tpid == ETH_P_8021Q) + +/* The TPID setting belongs to the General Parameters table, + * which can only be partially reconfigured at runtime (and not the TPID). + * So a switch reset is required. + */ +static int sja1105_change_tpid(struct sja1105_private *priv, + u16 tpid, u16 tpid2) +{ + struct sja1105_general_params_entry *general_params; + struct sja1105_table *table; + + table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS]; + general_params = table->entries; + general_params->tpid = tpid; + general_params->tpid2 = tpid2; + return sja1105_static_config_reload(priv); +} + +static int sja1105_pvid_apply(struct sja1105_private *priv, int port, u16 pvid) +{ + struct sja1105_mac_config_entry *mac; + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + + mac[port].vlanid = pvid; + + return sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, port, + &mac[port], true); +} + +static int sja1105_is_vlan_configured(struct sja1105_private *priv, u16 vid) +{ + struct sja1105_vlan_lookup_entry *vlan; + int count, i; + + vlan = priv->static_config.tables[BLK_IDX_VLAN_LOOKUP].entries; + count = priv->static_config.tables[BLK_IDX_VLAN_LOOKUP].entry_count; + + for (i = 0; i < count; i++) + if (vlan[i].vlanid == vid) + return i; + + /* Return an invalid entry index if not found */ + return -1; +} + +static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid, + bool enabled, bool untagged) +{ + struct sja1105_vlan_lookup_entry *vlan; + struct sja1105_table *table; + bool keep = true; + int match, rc; + + table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP]; + + match = sja1105_is_vlan_configured(priv, vid); + if (match < 0) { + /* Can't delete a missing entry. */ + if (!enabled) + return 0; + rc = sja1105_table_resize(table, table->entry_count + 1); + if (rc) + return rc; + match = table->entry_count - 1; + } + /* Assign pointer after the resize (it's new memory) */ + vlan = table->entries; + vlan[match].vlanid = vid; + if (enabled) { + vlan[match].vlan_bc |= BIT(port); + vlan[match].vmemb_port |= BIT(port); + } else { + vlan[match].vlan_bc &= ~BIT(port); + vlan[match].vmemb_port &= ~BIT(port); + } + /* Also unset tag_port if removing this VLAN was requested, + * just so we don't have a confusing bitmap (no practical purpose). + */ + if (untagged || !enabled) + vlan[match].tag_port &= ~BIT(port); + else + vlan[match].tag_port |= BIT(port); + /* If there's no port left as member of this VLAN, + * it's time for it to go. + */ + if (!vlan[match].vmemb_port) + keep = false; + + dev_dbg(priv->ds->dev, + "%s: port %d, vid %llu, broadcast domain 0x%llx, " + "port members 0x%llx, tagged ports 0x%llx, keep %d\n", + __func__, port, vlan[match].vlanid, vlan[match].vlan_bc, + vlan[match].vmemb_port, vlan[match].tag_port, keep); + + rc = sja1105_dynamic_config_write(priv, BLK_IDX_VLAN_LOOKUP, vid, + &vlan[match], keep); + if (rc < 0) + return rc; + + if (!keep) + return sja1105_table_delete_entry(table, match); + + return 0; +} + static enum dsa_tag_protocol sja1105_get_tag_protocol(struct dsa_switch *ds, int port) { return DSA_TAG_PROTO_NONE; } +/* This callback needs to be present */ +static int sja1105_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + return 0; +} + +static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled) +{ + struct sja1105_private *priv = ds->priv; + int rc = 0, i; + + /* On SJA1105, VLAN filtering per se is always enabled in hardware. + * The only thing we can do to disable it is lie about what the 802.1Q + * EtherType is. So it will still try to apply VLAN filtering, but all + * ingress traffic (except frames received with EtherType of + * ETH_P_EDSA, which are invalid) will be internally tagged with a + * distorted VLAN header where the TPID is ETH_P_EDSA, and the VLAN ID + * is the port pvid. So since this operation is global to the switch, + * we need to handle the case where multiple bridges span the same + * switch device and one of them has a different setting than what is + * being requested. + */ + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + struct net_device *bridge_dev; + + bridge_dev = dsa_to_port(ds, i)->bridge_dev; + if (bridge_dev && + bridge_dev != dsa_to_port(ds, port)->bridge_dev && + br_vlan_enabled(bridge_dev) != enabled) { + netdev_err(bridge_dev, + "VLAN filtering is global to the switch!\n"); + return -EINVAL; + } + } + + if (enabled && !sja1105_vlan_filtering_enabled(priv)) + /* Enable VLAN filtering. + * TODO read these from bridge_dev->protocol. + */ + rc = sja1105_change_tpid(priv, ETH_P_8021Q, ETH_P_8021AD); + else if (!enabled && sja1105_vlan_filtering_enabled(priv)) + /* Disable VLAN filtering. TODO: Install a TPID + * that also encodes the switch ID (aka ds->index) + * so that stacking switch tags will be supported. + */ + rc = sja1105_change_tpid(priv, ETH_P_EDSA, ETH_P_EDSA); + else + return 0; + if (rc) + dev_err(ds->dev, "Failed to change VLAN Ethertype\n"); + + return rc; +} + +static void sja1105_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct sja1105_private *priv = ds->priv; + u16 vid; + int rc; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + rc = sja1105_vlan_apply(priv, port, vid, true, vlan->flags & + BRIDGE_VLAN_INFO_UNTAGGED); + if (rc < 0) { + dev_err(ds->dev, "Failed to add VLAN %d to port %d: %d\n", + vid, port, rc); + return; + } + if (vlan->flags & BRIDGE_VLAN_INFO_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", + vid, port, rc); + return; + } + } + } +} + +static int sja1105_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct sja1105_private *priv = ds->priv; + u16 vid; + int rc; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + rc = sja1105_vlan_apply(priv, port, vid, false, vlan->flags & + BRIDGE_VLAN_INFO_UNTAGGED); + if (rc < 0) { + dev_err(ds->dev, "Failed to remove VLAN %d from port %d: %d\n", + vid, port, rc); + return rc; + } + } + return 0; +} + /* The programming model for the SJA1105 switch is "all-at-once" via static * configuration tables. Some of these can be dynamically modified at runtime, * but not the xMII mode parameters table. @@ -988,6 +1255,10 @@ static const struct dsa_switch_ops sja1105_switch_ops = { .port_fdb_del = sja1105_fdb_del, .port_bridge_join = sja1105_bridge_join, .port_bridge_leave = sja1105_bridge_leave, + .port_vlan_prepare = sja1105_vlan_prepare, + .port_vlan_filtering = sja1105_vlan_filtering, + .port_vlan_add = sja1105_vlan_add, + .port_vlan_del = sja1105_vlan_del, .port_mdb_prepare = sja1105_mdb_prepare, .port_mdb_add = sja1105_mdb_add, .port_mdb_del = sja1105_mdb_del,