From patchwork Thu Jun 14 11:11:30 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ilias Apalodimas X-Patchwork-Id: 138554 Delivered-To: patch@linaro.org Received: by 2002:a2e:970d:0:0:0:0:0 with SMTP id r13-v6csp1968583lji; Thu, 14 Jun 2018 04:11:58 -0700 (PDT) X-Google-Smtp-Source: ADUXVKIkF7mBZS9lfxP/xdin3urWNFEgefUEDzx02u3mfZvarfINpg3r8nx2mKKjUoB9ToSlGKbP X-Received: by 2002:a63:6fce:: with SMTP id k197-v6mr1918510pgc.307.1528974718683; Thu, 14 Jun 2018 04:11:58 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1528974718; cv=none; d=google.com; s=arc-20160816; b=L/WVI+uxG85FnccxBGuVGbq4tEyAnF+3Ar9hfjvlg4DYfxD02uVEwTO96AI7+j6RmZ 6vWKrYbmLjJwiWhbVe96Mc8guskBvQiK3HhLuk8BlIfbWRzanYb1qf+5cvSGOKun+iiz aKzMkeI8S0HAu3QwEGSRGEzQz4QsWYgDHA2G84P9uQAfxgKv+luAyxxrz2sh0AfbexsB Qhi59H3S4ZHv33RIEnS/xQZQok1qAwvLIuxhLIN/gbSebVWgutubc41wUo/sS/pYKGKB 0AxmDkb+1lBeldBrMutE6XuyC86j6kiktciS07U7AI2bc2Pm1HbbRc/UPZc69IrALoEg J6GA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=CWxuPrMwNoeMlD2H7BonnuHPHftjhAXmqkhLnCzvUUs=; b=wVPFu1ZbCqNqTGz5Y+bggOwtE+luFja9T9BznNE8EiJc7CBLmngXKstodNCN4e+iDc 7IIT6Db/ot9Ud6tSn8/hHlq2gjct2I0rK9ii86eKwEujjKOwoom7/8FvRYSNfa8/Bo0F HREynwfTHfnrGEHs1t5OvcGuZ0rQjykERGNNpJkXr4FjV5PfLJ+TpPUlbVJB85xhxLo3 TdcdlRvBUhWSRWOQwsf3bQX1sr8OCNHkofKSsRRs3MmnYhzRgXqCHdSkI98jbA1R4g2g v+gVBBnKVqwvji8FJA+4nLrNbPlCxK42jk1EcDOrNO1jnMC2/RgSH/XeZcXH9TpsvEU2 zFAQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=HELfBkIl; spf=pass (google.com: best guess record for domain of netdev-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=netdev-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id y83-v6si5037554pfb.284.2018.06.14.04.11.58; Thu, 14 Jun 2018 04:11:58 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of netdev-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=HELfBkIl; spf=pass (google.com: best guess record for domain of netdev-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=netdev-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755106AbeFNLL4 (ORCPT + 9 others); Thu, 14 Jun 2018 07:11:56 -0400 Received: from mail-wm0-f66.google.com ([74.125.82.66]:36729 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755055AbeFNLLu (ORCPT ); Thu, 14 Jun 2018 07:11:50 -0400 Received: by mail-wm0-f66.google.com with SMTP id v131-v6so11206944wma.1 for ; Thu, 14 Jun 2018 04:11:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=CWxuPrMwNoeMlD2H7BonnuHPHftjhAXmqkhLnCzvUUs=; b=HELfBkIl7RMKjLIh9rfvRNSymNsQeqoSxPle4VqtCLDuDmBLAVLaAaIX+j+tvo3iAI 2Jm7OfruYvnzwxM2uQdHSMZXlEE+Ux38sBw59sEMRdGpJ4kJnmVNY3FMuHq0+ntbMPa7 5rfjndH2Sc3B+NsrFVp+p9YYVulBg9ovHrPsk= 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=CWxuPrMwNoeMlD2H7BonnuHPHftjhAXmqkhLnCzvUUs=; b=Ai50BXTZ3BelfvUqFGka4qxdnHIY4C961c4oFVqyw2dIqmPyNo0yU9aNpAPrZsGm0r Fbvbh552pAgV5tPLuKMO3VtTvn+aD5CKMEd8bl1zHujFnH9RhXYAHirhddJIOnrh4R8p k3J5BZ6DdHQFHQbCjgEbPjYoENX/F08/i3h8yN5IuDnspyiVK69XTuF/dQuoGE5/CeEM kOLF3BVb2gWEHh5AWAwRrmk5Xw/SBuwQymeMh/zM+z5XyBZBZjaBxmu3wBo7Pb1zR3Ey 1zXTsh/Yv9jW7Vd7He1qdAUhaFt9YKKLRSbEg1JJ5SXVdXVINn3T/mYmd0WpHgFELcMn sQYw== X-Gm-Message-State: APt69E3EDLyIhTWZLUMyEOPB+Abnj0ZdtwFAkdbITrFrR1D5oWCvRd55 CHGTfRGS23h0uqjupvmCWGvXE3fsJww= X-Received: by 2002:a1c:b50b:: with SMTP id e11-v6mr1451965wmf.84.1528974708031; Thu, 14 Jun 2018 04:11:48 -0700 (PDT) Received: from localhost.localdomain ([2a02:587:4609:4e00:6c01:5076:e5d5:7a4c]) by smtp.gmail.com with ESMTPSA id t124-v6sm3657466wmt.29.2018.06.14.04.11.45 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 14 Jun 2018 04:11:47 -0700 (PDT) From: Ilias Apalodimas To: netdev@vger.kernel.org, grygorii.strashko@ti.com, ivan.khoronzhuk@linaro.org, nsekhar@ti.com, jiri@resnulli.us, ivecera@redhat.com, andrew@lunn.ch, f.fainelli@gmail.com Cc: francois.ozog@linaro.org, yogeshs@ti.com, spatton@ti.com, Jose.Abreu@synopsys.com, Ilias Apalodimas Subject: [RFC v2, net-next, PATCH 4/4] net/cpsw_switchdev: add switchdev mode of operation on cpsw driver Date: Thu, 14 Jun 2018 14:11:30 +0300 Message-Id: <1528974690-31600-5-git-send-email-ilias.apalodimas@linaro.org> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1528974690-31600-1-git-send-email-ilias.apalodimas@linaro.org> References: <1528974690-31600-1-git-send-email-ilias.apalodimas@linaro.org> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This patch enables switchdev funtionality on the driver based on a .config option(CONFIG_TI_CPSW_SWITCHDEV). CPSW driver used a DTS option called dual_emac to enable switch or dual emac mode. The new config option will override this configuration. It creates 2 ports, eth0 and eth1(that can be renamed to sw0p1 and sw0p2 via udev rules). sw0p1 and sw0p2 are the netdev interfaces connected to PHY devices. This hardware also has a CPU port which is configured invidividually in the case of VLANs. On device init all netdevices (including the CPU port) will operate on VLAN 0. sw0p1 and sw0p2 will operate as normal netdev interfaces. Once they are added in a bridge the default bridge vlan will not be added to the CPU port. In order to get an ip address on br0 you'll need to add the CPU port on that vlan by issuing: bridge vlan add dev br0 vid pvid untagged self Multicast traffic: setting IFF_MULTICAST on and off will affect registered multicast on that port(if enabled port will be added on registered multicast traffic mask). This muct occur before adding VLANs on the interfaces. If you change the flag after the VLAN configuration you need to re-issue the VLAN config commands. MDBs/FDBs: If the CPU port is member of the appropriate VLANs then switchdev API will add FDB/MDB entries uppon detection. If the CPU port is not a member the user can manually specify the entries. ALE_P0_UNI_FLOOD will be enabled when the first interface joins the bridge and will be disabled once the last interface leaves the bridge Signed-off-by: Ilias Apalodimas --- drivers/net/ethernet/ti/Kconfig | 9 + drivers/net/ethernet/ti/Makefile | 1 + drivers/net/ethernet/ti/cpsw.c | 306 +++++++++++++++++++++- drivers/net/ethernet/ti/cpsw_priv.h | 2 + drivers/net/ethernet/ti/cpsw_switchdev.c | 418 +++++++++++++++++++++++++++++++ drivers/net/ethernet/ti/cpsw_switchdev.h | 4 + 6 files changed, 731 insertions(+), 9 deletions(-) create mode 100644 drivers/net/ethernet/ti/cpsw_switchdev.c create mode 100644 drivers/net/ethernet/ti/cpsw_switchdev.h -- 2.7.4 diff --git a/drivers/net/ethernet/ti/Kconfig b/drivers/net/ethernet/ti/Kconfig index 9263d63..a299d86 100644 --- a/drivers/net/ethernet/ti/Kconfig +++ b/drivers/net/ethernet/ti/Kconfig @@ -73,6 +73,15 @@ config TI_CPSW To compile this driver as a module, choose M here: the module will be called cpsw. +config TI_CPSW_SWITCHDEV + bool "TI CPSW switchdev support" + depends on TI_CPSW + depends on NET_SWITCHDEV + help + Enable switchdev support on TI's CPSW Ethernet Switch. + + This will allow you to configure the switch using standard tools. + config TI_CPTS bool "TI Common Platform Time Sync (CPTS) Support" depends on TI_CPSW || TI_KEYSTONE_NETCP || COMPILE_TEST diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile index 0be551d..d6eb2a2 100644 --- a/drivers/net/ethernet/ti/Makefile +++ b/drivers/net/ethernet/ti/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_TI_CPSW_PHY_SEL) += cpsw-phy-sel.o obj-$(CONFIG_TI_CPSW_ALE) += cpsw_ale.o obj-$(CONFIG_TI_CPTS_MOD) += cpts.o obj-$(CONFIG_TI_CPSW) += ti_cpsw.o +obj-$(CONFIG_TI_CPSW_SWITCHDEV) += cpsw_switchdev.o ti_cpsw-y := cpsw.o obj-$(CONFIG_TI_KEYSTONE_NETCP) += keystone_netcp.o diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c index e5765cc..b501908 100644 --- a/drivers/net/ethernet/ti/cpsw.c +++ b/drivers/net/ethernet/ti/cpsw.c @@ -18,12 +18,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include #include @@ -43,6 +41,7 @@ #include "cpsw.h" #include "cpsw_ale.h" #include "cpsw_priv.h" +#include "cpsw_switchdev.h" #include "cpts.h" #include "davinci_cpdma.h" @@ -361,6 +360,13 @@ struct cpsw_hw_stats { u32 rxdmaoverruns; }; +struct cpsw_switchdev_event_work { + struct work_struct work; + struct switchdev_notifier_fdb_info fdb_info; + struct cpsw_priv *priv; + unsigned long event; +}; + #define CPSW_STAT(m) CPSW_STATS, \ sizeof(((struct cpsw_hw_stats *)0)->m), \ offsetof(struct cpsw_hw_stats, m) @@ -488,14 +494,32 @@ static int cpsw_is_switch(u8 switch_mode) return switch_mode == CPSW_TI_SWITCH; } +static int cpsw_is_switchdev(u8 switch_mode) +{ + return switch_mode == CPSW_SWITCHDEV; +} + static int cpsw_slave_index(struct cpsw_priv *priv) { struct cpsw_common *cpsw = priv->cpsw; +#if IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV) + if (priv->emac_port == HOST_PORT_NUM) + return -1; +#endif + return cpsw->data.switch_mode ? priv->emac_port - 1 : cpsw->data.active_slave; } +static void cpsw_switchdev_port_enable(struct net_device *ndev) +{ +#if IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV) + cpsw_port_switchdev_init(ndev); + ndev->features |= NETIF_F_NETNS_LOCAL; +#endif +} + static void cpsw_set_promiscious(struct net_device *ndev, bool enable) { struct cpsw_common *cpsw = ndev_to_cpsw(ndev); @@ -521,6 +545,7 @@ static void cpsw_set_promiscious(struct net_device *ndev, bool enable) if (enable) { /* Enable Bypass */ cpsw_ale_control_set(ale, 0, ALE_BYPASS, 1); + cpsw_ale_set_allmulti(ale, IFF_ALLMULTI); dev_dbg(&ndev->dev, "promiscuity enabled\n"); } else { @@ -554,6 +579,7 @@ static void cpsw_set_promiscious(struct net_device *ndev, bool enable) /* Flood All Unicast Packets to Host port */ cpsw_ale_control_set(ale, 0, ALE_P0_UNI_FLOOD, 1); + cpsw_ale_set_allmulti(ale, IFF_ALLMULTI); dev_dbg(&ndev->dev, "promiscuity enabled\n"); } else { /* Don't Flood All Unicast Packets to Host port */ @@ -568,6 +594,19 @@ static void cpsw_set_promiscious(struct net_device *ndev, bool enable) } dev_dbg(&ndev->dev, "promiscuity disabled\n"); } + } else if (cpsw_is_switchdev(cpsw->data.switch_mode)) { + /* When interfaces are placed into a bridge they'll switch to + * promiscuous mode. In switchdev case ALE_P0_UNI_FLOOD is + * changed whether any switch port participates in the bridge + * or not + */ + struct cpsw_priv *priv = netdev_priv(ndev); + int slave_idx = cpsw_slave_index(priv); + int slave_num; + + slave_num = cpsw_get_slave_port(slave_idx); + cpsw_ale_control_set(ale, slave_num, ALE_PORT_NOLEARN, 0); + cpsw_ale_control_set(ale, slave_num, ALE_PORT_NO_SA_UPDATE, 0); } } @@ -586,7 +625,6 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev) if (ndev->flags & IFF_PROMISC) { /* Enable promiscuous mode */ cpsw_set_promiscious(ndev, true); - cpsw_ale_set_allmulti(cpsw->ale, IFF_ALLMULTI); return; } else { /* Disable promiscuous mode */ @@ -721,6 +759,10 @@ static void cpsw_rx_handler(void *token, int len, int status) return; } +#if IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV) + if (cpsw_is_switchdev(cpsw->data.switch_mode)) + skb->offload_fwd_mark = 1; +#endif new_skb = netdev_alloc_skb_ip_align(ndev, cpsw->rx_packet_max); if (new_skb) { skb_copy_queue_mapping(new_skb, skb); @@ -1427,10 +1469,13 @@ static void cpsw_init_host_port(struct cpsw_priv *priv) ALE_PORT_STATE, ALE_PORT_STATE_FORWARD); if (!cpsw_is_dual_mac(cpsw->data.switch_mode)) { - cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM, - 0, 0); + char stpa[] = {0x01, 0x80, 0xc2, 0x0, 0x0, 0x0}; + cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast, ALE_PORT_HOST, 0, 0, ALE_MCAST_FWD_2); + cpsw_ale_add_mcast(cpsw->ale, stpa, + ALE_PORT_HOST, ALE_SUPER, 0, + ALE_MCAST_BLOCK_LEARN_FWD); } } @@ -1529,11 +1574,14 @@ static int cpsw_ndo_open(struct net_device *ndev) for_each_slave(priv, cpsw_slave_open, priv); /* Add default VLAN */ - if (!cpsw_is_dual_mac(cpsw->data.switch_mode)) + if (!cpsw_is_dual_mac(cpsw->data.switch_mode)) { cpsw_add_default_vlan(priv); - else + cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM, 0, + 0); + } else { cpsw_ale_add_vlan(cpsw->ale, cpsw->data.default_vlan, ALE_ALL_PORTS, ALE_ALL_PORTS, 0, 0); + } /* initialize shared resources for every ndev */ if (!cpsw->usage_count) { @@ -1852,6 +1900,9 @@ static int cpsw_ndo_ioctl(struct net_device *dev, struct ifreq *req, int cmd) if (!netif_running(dev)) return -EINVAL; + if (slave_no < 0) + return -EOPNOTSUPP; + switch (cmd) { case SIOCSHWTSTAMP: return cpsw_hwtstamp_set(dev, req); @@ -1941,7 +1992,7 @@ static inline int cpsw_add_vlan_ale_entry(struct cpsw_priv *priv, u32 port_mask; struct cpsw_common *cpsw = priv->cpsw; - if (cpsw_is_dual_mac(cpsw->data.switch_mode)) { + if (!cpsw_is_switch(cpsw->data.switch_mode)) { port_mask = (1 << priv->emac_port) | ALE_PORT_HOST; if (priv->ndev->flags & IFF_ALLMULTI) @@ -1989,6 +2040,10 @@ static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev, if (vid == cpsw->data.default_vlan) return 0; + if (cpsw_is_switchdev(cpsw->data.switch_mode) && + (netif_is_bridge_port(ndev))) + return -EOPNOTSUPP; + ret = pm_runtime_get_sync(cpsw->dev); if (ret < 0) { pm_runtime_put_noidle(cpsw->dev); @@ -2025,6 +2080,10 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev, if (vid == cpsw->data.default_vlan) return 0; + if (cpsw_is_switchdev(cpsw->data.switch_mode) && + (netif_is_bridge_port(ndev))) + return -EOPNOTSUPP; + ret = pm_runtime_get_sync(cpsw->dev); if (ret < 0) { pm_runtime_put_noidle(cpsw->dev); @@ -2056,6 +2115,24 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev, return ret; } +static int cpsw_ndo_get_phys_port_name(struct net_device *ndev, char *name, + size_t len) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + struct cpsw_common *cpsw = priv->cpsw; + int err; + + if (!cpsw_is_switchdev(cpsw->data.switch_mode)) + return -EOPNOTSUPP; + + err = snprintf(name, len, "p%d", priv->emac_port); + + if (err >= len) + return -EINVAL; + + return 0; +} + static int cpsw_ndo_set_tx_maxrate(struct net_device *ndev, int queue, u32 rate) { struct cpsw_priv *priv = netdev_priv(ndev); @@ -2122,6 +2199,7 @@ static const struct net_device_ops cpsw_netdev_ops = { #endif .ndo_vlan_rx_add_vid = cpsw_ndo_vlan_rx_add_vid, .ndo_vlan_rx_kill_vid = cpsw_ndo_vlan_rx_kill_vid, + .ndo_get_phys_port_name = cpsw_ndo_get_phys_port_name, }; static int cpsw_get_regs_len(struct net_device *ndev) @@ -2711,6 +2789,10 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data, if (of_property_read_bool(node, "dual_emac")) data->switch_mode = CPSW_DUAL_EMAC; + /* switchdev overrides DTS */ + if (IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV)) + data->switch_mode = CPSW_SWITCHDEV; + /* * Populate all the child nodes here... */ @@ -2874,6 +2956,9 @@ static int cpsw_probe_dual_emac(struct cpsw_priv *priv) cpsw->slaves[1].ndev = ndev; ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; + if (cpsw_is_switchdev(cpsw->data.switch_mode)) + cpsw_switchdev_port_enable(ndev); + ndev->netdev_ops = &cpsw_netdev_ops; ndev->ethtool_ops = &cpsw_ethtool_ops; @@ -2903,6 +2988,196 @@ static const struct soc_device_attribute cpsw_soc_devices[] = { { /* sentinel */ } }; +static bool cpsw_port_dev_check(const struct net_device *dev) +{ + return dev->netdev_ops == &cpsw_netdev_ops; +} + +static void cpsw_fdb_offload_notify(struct net_device *ndev, + struct switchdev_notifier_fdb_info *rcv) +{ + struct switchdev_notifier_fdb_info info; + + info.addr = rcv->addr; + info.vid = rcv->vid; + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, + ndev, &info.info); +} + +static void cpsw_switchdev_event_work(struct work_struct *work) +{ + struct cpsw_switchdev_event_work *switchdev_work = + container_of(work, struct cpsw_switchdev_event_work, work); + struct cpsw_priv *priv = switchdev_work->priv; + struct switchdev_notifier_fdb_info *fdb; + struct cpsw_common *cpsw = priv->cpsw; + int port = priv->emac_port; + + rtnl_lock(); + switch (switchdev_work->event) { + case SWITCHDEV_FDB_ADD_TO_DEVICE: + fdb = &switchdev_work->fdb_info; + if (memcmp(priv->mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0) + port = HOST_PORT_NUM; + cpsw_ale_add_ucast(cpsw->ale, (u8 *)fdb->addr, port, ALE_VLAN, + fdb->vid); + cpsw_fdb_offload_notify(priv->ndev, fdb); + break; + case SWITCHDEV_FDB_DEL_TO_DEVICE: + fdb = &switchdev_work->fdb_info; + if (memcmp(priv->mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0) + port = HOST_PORT_NUM; + cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port, ALE_VLAN, + fdb->vid); + break; + default: + break; + } + rtnl_unlock(); + + kfree(switchdev_work->fdb_info.addr); + kfree(switchdev_work); + dev_put(priv->ndev); +} + +/* called under rcu_read_lock() */ +static int cpsw_switchdev_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *ndev = switchdev_notifier_info_to_dev(ptr); + struct switchdev_notifier_fdb_info *fdb_info = ptr; + struct cpsw_switchdev_event_work *switchdev_work; + struct cpsw_priv *priv = netdev_priv(ndev); + + if (!cpsw_port_dev_check(ndev)) + return NOTIFY_DONE; + + switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC); + if (WARN_ON(!switchdev_work)) + return NOTIFY_BAD; + + INIT_WORK(&switchdev_work->work, cpsw_switchdev_event_work); + switchdev_work->priv = priv; + switchdev_work->event = event; + + switch (event) { + case SWITCHDEV_FDB_ADD_TO_DEVICE: + case SWITCHDEV_FDB_DEL_TO_DEVICE: + memcpy(&switchdev_work->fdb_info, ptr, + sizeof(switchdev_work->fdb_info)); + switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); + ether_addr_copy((u8 *)switchdev_work->fdb_info.addr, + fdb_info->addr); + dev_hold(ndev); + break; + default: + kfree(switchdev_work); + return NOTIFY_DONE; + } + + queue_work(system_long_wq, &switchdev_work->work); + + return NOTIFY_DONE; +} + +static struct notifier_block cpsw_switchdev_notifier = { + .notifier_call = cpsw_switchdev_event, +}; + +static void cpsw_netdevice_port_link(struct net_device *ndev) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + struct cpsw_common *cpsw = priv->cpsw; + + if (!cpsw->br_members) { + cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, + 1); + dev_dbg(&ndev->dev, "Set P0_UNI_FLOOD\n"); + } + cpsw->br_members++; +} + +static void cpsw_netdevice_port_unlink(struct net_device *ndev) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + struct cpsw_common *cpsw = priv->cpsw; + + cpsw->br_members--; + if (!cpsw->br_members) { + cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, + 0); + dev_dbg(&ndev->dev, "unset P0_UNI_FLOOD\n"); + } +} + +/* netdev notifier */ +static int cpsw_netdevice_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *ndev = netdev_notifier_info_to_dev(ptr); + struct netdev_notifier_changeupper_info *info; + + switch (event) { + case NETDEV_CHANGEUPPER: + info = ptr; + if (!info->master) + goto out; + if (info->linking) + cpsw_netdevice_port_link(ndev); + else + cpsw_netdevice_port_unlink(ndev); + break; + default: + return NOTIFY_DONE; + } + +out: + return NOTIFY_DONE; +} + +static struct notifier_block cpsw_netdevice_nb __read_mostly = { + .notifier_call = cpsw_netdevice_event, +}; + +static int cpsw_register_notifiers(struct cpsw_priv *priv) +{ + int ret; + + ret = register_netdevice_notifier(&cpsw_netdevice_nb); + if (ret) { + cpsw_err(priv, probe, "can't register netdevice notifier\n"); + return ret; + } + + ret = register_switchdev_notifier(&cpsw_switchdev_notifier); + if (ret) { + cpsw_err(priv, probe, "can't register switchdev notifier\n"); + goto unreg_netdevice; + } + + return ret; + +unreg_netdevice: + ret = unregister_netdevice_notifier(&cpsw_netdevice_nb); + + return ret; +} + +static int cpsw_unregister_notifiers(struct cpsw_priv *priv) +{ + int ret; + + ret = unregister_switchdev_notifier(&cpsw_switchdev_notifier); + if (ret) + dev_err(priv->dev, "can't unregister switchdev notifier\n"); + + ret += unregister_netdevice_notifier(&cpsw_netdevice_nb); + if (ret) + dev_err(priv->dev, "can't unregister netdevice notifier\n"); + + return ret; +} + static int cpsw_probe(struct platform_device *pdev) { struct clk *clk; @@ -3135,6 +3410,9 @@ static int cpsw_probe(struct platform_device *pdev) goto clean_dma_ret; } + if (cpsw_is_switchdev(cpsw->data.switch_mode)) + cpsw_switchdev_port_enable(ndev); + ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_VLAN_CTAG_RX; ndev->netdev_ops = &cpsw_netdev_ops; @@ -3202,6 +3480,12 @@ static int cpsw_probe(struct platform_device *pdev) goto clean_dma_ret; } + if (cpsw_is_switchdev(cpsw->data.switch_mode)) { + ret = cpsw_register_notifiers(priv); + if (ret) + goto clean_dma_ret; + } + cpsw_notice(priv, probe, "initialized device (regs %pa, irq %d, pool size %d)\n", &ss_res->start, ndev->irq, dma_params.descs_pool_size); @@ -3227,7 +3511,8 @@ static int cpsw_probe(struct platform_device *pdev) static int cpsw_remove(struct platform_device *pdev) { struct net_device *ndev = platform_get_drvdata(pdev); - struct cpsw_common *cpsw = ndev_to_cpsw(ndev); + struct cpsw_priv *priv = netdev_priv(ndev); + struct cpsw_common *cpsw = priv->cpsw; int ret; ret = pm_runtime_get_sync(&pdev->dev); @@ -3236,6 +3521,9 @@ static int cpsw_remove(struct platform_device *pdev) return ret; } + if (cpsw_is_switchdev(cpsw->data.switch_mode)) + ret = cpsw_unregister_notifiers(priv); + if (!cpsw_is_switch(cpsw->data.switch_mode)) unregister_netdev(cpsw->slaves[1].ndev); unregister_netdev(ndev); diff --git a/drivers/net/ethernet/ti/cpsw_priv.h b/drivers/net/ethernet/ti/cpsw_priv.h index 86a2709..4380b1c 100644 --- a/drivers/net/ethernet/ti/cpsw_priv.h +++ b/drivers/net/ethernet/ti/cpsw_priv.h @@ -33,6 +33,7 @@ enum { CPSW_TI_SWITCH, CPSW_DUAL_EMAC, + CPSW_SWITCHDEV, }; struct cpsw_slave_data { @@ -98,6 +99,7 @@ struct cpsw_common { int rx_ch_num, tx_ch_num; int speed; int usage_count; + u8 br_members; }; struct cpsw_priv { diff --git a/drivers/net/ethernet/ti/cpsw_switchdev.c b/drivers/net/ethernet/ti/cpsw_switchdev.c new file mode 100644 index 0000000..528e99e --- /dev/null +++ b/drivers/net/ethernet/ti/cpsw_switchdev.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Texas Instruments switchdev Driver + * + * Copyright (C) 2018 Texas Instruments + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include "cpsw.h" +#include "cpsw_priv.h" +#include "cpsw_ale.h" + +static u32 cpsw_switchdev_get_ver(struct net_device *ndev) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + struct cpsw_common *cpsw = priv->cpsw; + + return cpsw->version; +} + +static int cpsw_port_stp_state_set(struct cpsw_priv *priv, + struct switchdev_trans *trans, u8 state) +{ + struct cpsw_common *cpsw = priv->cpsw; + u8 cpsw_state; + int ret = 0; + + if (switchdev_trans_ph_prepare(trans)) + return 0; + + switch (state) { + case BR_STATE_FORWARDING: + cpsw_state = ALE_PORT_STATE_FORWARD; + break; + case BR_STATE_LEARNING: + cpsw_state = ALE_PORT_STATE_LEARN; + break; + case BR_STATE_DISABLED: + cpsw_state = ALE_PORT_STATE_DISABLE; + break; + case BR_STATE_LISTENING: + case BR_STATE_BLOCKING: + cpsw_state = ALE_PORT_STATE_BLOCK; + break; + default: + return -EOPNOTSUPP; + } + + ret = cpsw_ale_control_set(cpsw->ale, priv->emac_port, + ALE_PORT_STATE, cpsw_state); + dev_dbg(priv->dev, "ale state: %u\n", cpsw_state); + + return ret; +} + +static int cpsw_port_attr_br_flags_set(struct cpsw_priv *priv, + struct switchdev_trans *trans, + struct net_device *orig_dev, + unsigned long brport_flags) +{ + struct cpsw_common *cpsw = priv->cpsw; + bool unreg_mcast_add = false; + + if (switchdev_trans_ph_prepare(trans)) + return 0; + + if (brport_flags & BR_MCAST_FLOOD) + unreg_mcast_add = true; + cpsw_ale_set_unreg_mcast(cpsw->ale, BIT(priv->emac_port), + unreg_mcast_add); + + return 0; +} + +static int cpsw_port_attr_set(struct net_device *ndev, + const struct switchdev_attr *attr, + struct switchdev_trans *trans) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + u8 state; + int ret; + + dev_dbg(priv->dev, "attr: id %u dev: %s port: %u\n", attr->id, + priv->ndev->name, priv->emac_port); + + switch (attr->id) { + case SWITCHDEV_ATTR_ID_PORT_STP_STATE: + ret = cpsw_port_stp_state_set(priv, trans, attr->u.stp_state); + dev_dbg(priv->dev, "stp state: %u\n", state); + break; + case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS: + ret = cpsw_port_attr_br_flags_set(priv, trans, attr->orig_dev, + attr->u.brport_flags); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static int cpsw_port_attr_get(struct net_device *dev, + struct switchdev_attr *attr) +{ + u32 cpsw_ver; + int err = 0; + + switch (attr->id) { + case SWITCHDEV_ATTR_ID_PORT_PARENT_ID: + cpsw_ver = cpsw_switchdev_get_ver(dev); + attr->u.ppid.id_len = sizeof(cpsw_ver); + memcpy(&attr->u.ppid.id, &cpsw_ver, attr->u.ppid.id_len); + break; + case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS_SUPPORT: + attr->u.brport_flags_support = BR_MCAST_FLOOD; + break; + default: + return -EOPNOTSUPP; + } + + return err; +} + +static u16 cpsw_get_pvid(struct cpsw_priv *priv) +{ + struct cpsw_common *cpsw = priv->cpsw; + u32 __iomem *port_vlan_reg; + u32 pvid; + + if (priv->emac_port) { + int reg = CPSW2_PORT_VLAN; + + if (cpsw->version == CPSW_VERSION_1) + reg = CPSW1_PORT_VLAN; + pvid = slave_read(cpsw->slaves + (priv->emac_port - 1), reg); + } else { + port_vlan_reg = &cpsw->host_port_regs->port_vlan; + pvid = readl(port_vlan_reg); + } + + pvid = pvid & 0xfff; + + return pvid; +} + +static void cpsw_set_pvid(struct cpsw_priv *priv, u16 vid, bool cfi, u32 cos) +{ + struct cpsw_common *cpsw = priv->cpsw; + void __iomem *port_vlan_reg; + u32 pvid; + + pvid = vid; + pvid |= cfi ? BIT(12) : 0; + pvid |= (cos & 0x7) << 13; + + if (priv->emac_port) { + int reg = CPSW2_PORT_VLAN; + + if (cpsw->version == CPSW_VERSION_1) + reg = CPSW1_PORT_VLAN; + /* no barrier */ + slave_write(cpsw->slaves + (priv->emac_port - 1), pvid, reg); + } else { + /* CPU port */ + port_vlan_reg = &cpsw->host_port_regs->port_vlan; + writel(pvid, port_vlan_reg); + } +} + +static int cpsw_port_vlan_add(struct cpsw_priv *priv, bool untag, bool pvid, + u16 vid, struct net_device *orig_dev) +{ + bool cpu_port = netif_is_bridge_master(orig_dev); + struct cpsw_common *cpsw = priv->cpsw; + int unreg_mcast_mask = 0; + int reg_mcast_mask = 0; + int untag_mask = 0; + int port_mask; + int ret = 0; + u32 flags; + + if (cpu_port) { + port_mask = BIT(HOST_PORT_NUM); + flags = orig_dev->flags; + unreg_mcast_mask = port_mask; + } else { + port_mask = BIT(priv->emac_port); + flags = priv->ndev->flags; + } + + if (flags & IFF_MULTICAST) + reg_mcast_mask = port_mask; + + if (untag) + untag_mask = port_mask; + + ret = cpsw_ale_vlan_add_modify(cpsw->ale, vid, port_mask, untag_mask, + reg_mcast_mask, unreg_mcast_mask); + if (ret) { + dev_err(priv->dev, "Unable to add vlan\n"); + return ret; + } + + if (!pvid) + return ret; + + cpsw_set_pvid(priv, vid, 0, 0); + + dev_dbg(priv->dev, "VID add: %u dev: %s port: %u\n", vid, + priv->ndev->name, priv->emac_port); + + return ret; +} + +static int cpsw_port_vlan_del(struct cpsw_priv *priv, u16 vid, + struct net_device *orig_dev) +{ + bool cpu_port = netif_is_bridge_master(orig_dev); + struct cpsw_common *cpsw = priv->cpsw; + int port_mask; + int ret = 0; + + if (cpu_port) + port_mask = BIT(HOST_PORT_NUM); + else + port_mask = BIT(priv->emac_port); + + ret = cpsw_ale_vlan_del_modify(cpsw->ale, vid, port_mask); + if (ret != 0) + return ret; + + /* We don't care for the return value here, error is returned only if + * the unicast entry is not present + */ + cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr, + HOST_PORT_NUM, ALE_VLAN, vid); + + if (vid == cpsw_get_pvid(priv)) + cpsw_set_pvid(priv, 0, 0, 0); + + /* We don't care for the return value here, error is returned only if + * the multicast entry is not present + */ + cpsw_ale_del_mcast(cpsw->ale, priv->ndev->broadcast, + 0, ALE_VLAN, vid); + + dev_dbg(priv->dev, "VID del: %u dev: %s port: %u\n", vid, + priv->ndev->name, priv->emac_port); + + return ret; +} + +static int cpsw_port_vlans_add(struct cpsw_priv *priv, + const struct switchdev_obj_port_vlan *vlan, + struct switchdev_trans *trans) +{ + bool untag = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + struct net_device *orig_dev = vlan->obj.orig_dev; + bool cpu_port = netif_is_bridge_master(orig_dev); + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + u16 vid; + + if (cpu_port && !(vlan->flags & BRIDGE_VLAN_INFO_BRENTRY)) + return 0; + + if (switchdev_trans_ph_prepare(trans)) + return 0; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + int err; + + err = cpsw_port_vlan_add(priv, untag, pvid, vid, orig_dev); + if (err) + return err; + } + + return 0; +} + +static int cpsw_port_vlans_del(struct cpsw_priv *priv, + const struct switchdev_obj_port_vlan *vlan) + +{ + struct net_device *orig_dev = vlan->obj.orig_dev; + u16 vid; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + int err; + + err = cpsw_port_vlan_del(priv, vid, orig_dev); + if (err) + return err; + } + + return 0; +} + +static int cpsw_port_mdb_add(struct cpsw_priv *priv, + struct switchdev_obj_port_mdb *mdb, + struct switchdev_trans *trans) + +{ + struct net_device *orig_dev = mdb->obj.orig_dev; + bool cpu_port = netif_is_bridge_master(orig_dev); + struct cpsw_common *cpsw = priv->cpsw; + int port_mask; + int err; + + if (switchdev_trans_ph_prepare(trans)) + return 0; + + if (cpu_port) + port_mask = BIT(HOST_PORT_NUM); + else + port_mask = BIT(priv->emac_port); + + err = cpsw_ale_mcast_add_modify(cpsw->ale, mdb->addr, port_mask, + ALE_VLAN, mdb->vid, 0); + + dev_dbg(priv->dev, "MDB add: %pM dev: %s vid %u port: %u\n", mdb->addr, + priv->ndev->name, mdb->vid, priv->emac_port); + + return err; +} + +static int cpsw_port_mdb_del(struct cpsw_priv *priv, + struct switchdev_obj_port_mdb *mdb) + +{ + struct net_device *orig_dev = mdb->obj.orig_dev; + bool cpu_port = netif_is_bridge_master(orig_dev); + struct cpsw_common *cpsw = priv->cpsw; + int del_mask; + int err; + + if (cpu_port) + del_mask = BIT(HOST_PORT_NUM); + else + del_mask = BIT(priv->emac_port); + err = cpsw_ale_mcast_del_modify(cpsw->ale, mdb->addr, del_mask, + ALE_VLAN, mdb->vid); + dev_dbg(priv->dev, "MDB del: %pM dev: %s vid %u port: %u\n", mdb->addr, + priv->ndev->name, mdb->vid, priv->emac_port); + + return err; +} + +static int cpsw_port_obj_add(struct net_device *ndev, + const struct switchdev_obj *obj, + struct switchdev_trans *trans) +{ + struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj); + struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj); + struct cpsw_priv *priv = netdev_priv(ndev); + int err = 0; + + switch (obj->id) { + case SWITCHDEV_OBJ_ID_PORT_VLAN: + err = cpsw_port_vlans_add(priv, vlan, trans); + break; + case SWITCHDEV_OBJ_ID_PORT_MDB: + case SWITCHDEV_OBJ_ID_HOST_MDB: + err = cpsw_port_mdb_add(priv, mdb, trans); + break; + default: + err = -EOPNOTSUPP; + break; + } + + return err; +} + +static int cpsw_port_obj_del(struct net_device *ndev, + const struct switchdev_obj *obj) +{ + struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj); + struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj); + struct cpsw_priv *priv = netdev_priv(ndev); + int err = 0; + + switch (obj->id) { + case SWITCHDEV_OBJ_ID_PORT_VLAN: + err = cpsw_port_vlans_del(priv, vlan); + break; + case SWITCHDEV_OBJ_ID_PORT_MDB: + case SWITCHDEV_OBJ_ID_HOST_MDB: + err = cpsw_port_mdb_del(priv, mdb); + break; + default: + err = -EOPNOTSUPP; + break; + } + + return err; +} + +static const struct switchdev_ops cpsw_port_switchdev_ops = { + .switchdev_port_attr_set = cpsw_port_attr_set, + .switchdev_port_attr_get = cpsw_port_attr_get, + .switchdev_port_obj_add = cpsw_port_obj_add, + .switchdev_port_obj_del = cpsw_port_obj_del, +}; + +void cpsw_port_switchdev_init(struct net_device *ndev) +{ + ndev->switchdev_ops = &cpsw_port_switchdev_ops; +} diff --git a/drivers/net/ethernet/ti/cpsw_switchdev.h b/drivers/net/ethernet/ti/cpsw_switchdev.h new file mode 100644 index 0000000..4940462 --- /dev/null +++ b/drivers/net/ethernet/ti/cpsw_switchdev.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include + +void cpsw_port_switchdev_init(struct net_device *ndev);