From patchwork Mon Aug 2 13:10:32 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 490614 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=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, 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 B1BDCC4320A for ; Mon, 2 Aug 2021 13:11:03 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9D4B760EC0 for ; Mon, 2 Aug 2021 13:11:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234002AbhHBNLL (ORCPT ); Mon, 2 Aug 2021 09:11:11 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57282 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233814AbhHBNK6 (ORCPT ); Mon, 2 Aug 2021 09:10:58 -0400 Received: from metis.ext.pengutronix.de (metis.ext.pengutronix.de [IPv6:2001:67c:670:201:290:27ff:fe1d:cc33]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A26C0C06179A for ; Mon, 2 Aug 2021 06:10:47 -0700 (PDT) Received: from dude.hi.pengutronix.de ([2001:67c:670:100:1d::7]) by metis.ext.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mAXiK-0003MY-Om; Mon, 02 Aug 2021 15:10:40 +0200 Received: from ore by dude.hi.pengutronix.de with local (Exim 4.92) (envelope-from ) id 1mAXiJ-00006S-G0; Mon, 02 Aug 2021 15:10:39 +0200 From: Oleksij Rempel To: Andrew Lunn , Vivien Didelot , Florian Fainelli , Vladimir Oltean , "David S. Miller" , Jakub Kicinski , Russell King Cc: Oleksij Rempel , Pengutronix Kernel Team , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mips@vger.kernel.org Subject: [PATCH net-next v3 1/6] net: dsa: qca: ar9331: reorder MDIO write sequence Date: Mon, 2 Aug 2021 15:10:32 +0200 Message-Id: <20210802131037.32326-2-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210802131037.32326-1-o.rempel@pengutronix.de> References: <20210802131037.32326-1-o.rempel@pengutronix.de> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2001:67c:670:100:1d::7 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: netdev@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org In case of this switch we work with 32bit registers on top of 16bit bus. Some registers (for example access to forwarding database) have trigger bit on the first 16bit half of request and the result + configuration of request in the second half. Without this patch, we would trigger database operation and overwrite result in one run. To make it work properly, we should do the second part of transfer before the first one is done. So far, this rule seems to work for all registers on this switch. Signed-off-by: Oleksij Rempel Reviewed-by: Andrew Lunn Reviewed-by: Vladimir Oltean --- drivers/net/dsa/qca/ar9331.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/net/dsa/qca/ar9331.c b/drivers/net/dsa/qca/ar9331.c index ca2ad77b71f1..6686192e1883 100644 --- a/drivers/net/dsa/qca/ar9331.c +++ b/drivers/net/dsa/qca/ar9331.c @@ -837,16 +837,24 @@ static int ar9331_mdio_write(void *ctx, u32 reg, u32 val) return 0; } - ret = __ar9331_mdio_write(sbus, AR9331_SW_MDIO_PHY_MODE_REG, reg, val); + /* In case of this switch we work with 32bit registers on top of 16bit + * bus. Some registers (for example access to forwarding database) have + * trigger bit on the first 16bit half of request, the result and + * configuration of request in the second half. + * To make it work properly, we should do the second part of transfer + * before the first one is done. + */ + ret = __ar9331_mdio_write(sbus, AR9331_SW_MDIO_PHY_MODE_REG, reg + 2, + val >> 16); if (ret < 0) goto error; - ret = __ar9331_mdio_write(sbus, AR9331_SW_MDIO_PHY_MODE_REG, reg + 2, - val >> 16); + ret = __ar9331_mdio_write(sbus, AR9331_SW_MDIO_PHY_MODE_REG, reg, val); if (ret < 0) goto error; return 0; + error: dev_err_ratelimited(&sbus->dev, "Bus error. Failed to write register.\n"); return ret; From patchwork Mon Aug 2 13:10:34 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 490613 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=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, 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 F3650C43214 for ; Mon, 2 Aug 2021 13:11:09 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id DE21F60FC1 for ; Mon, 2 Aug 2021 13:11:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234040AbhHBNLR (ORCPT ); Mon, 2 Aug 2021 09:11:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57296 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233886AbhHBNK6 (ORCPT ); Mon, 2 Aug 2021 09:10:58 -0400 Received: from metis.ext.pengutronix.de (metis.ext.pengutronix.de [IPv6:2001:67c:670:201:290:27ff:fe1d:cc33]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5F3FAC06179C for ; Mon, 2 Aug 2021 06:10:47 -0700 (PDT) Received: from dude.hi.pengutronix.de ([2001:67c:670:100:1d::7]) by metis.ext.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mAXiK-0003Mb-Oa; Mon, 02 Aug 2021 15:10:40 +0200 Received: from ore by dude.hi.pengutronix.de with local (Exim 4.92) (envelope-from ) id 1mAXiJ-00008E-Hv; Mon, 02 Aug 2021 15:10:39 +0200 From: Oleksij Rempel To: Andrew Lunn , Vivien Didelot , Florian Fainelli , Vladimir Oltean , "David S. Miller" , Jakub Kicinski , Russell King Cc: Oleksij Rempel , Pengutronix Kernel Team , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mips@vger.kernel.org Subject: [PATCH net-next v3 3/6] net: dsa: qca: ar9331: add forwarding database support Date: Mon, 2 Aug 2021 15:10:34 +0200 Message-Id: <20210802131037.32326-4-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210802131037.32326-1-o.rempel@pengutronix.de> References: <20210802131037.32326-1-o.rempel@pengutronix.de> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2001:67c:670:100:1d::7 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: netdev@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This switch provides simple address resolution table, without VLAN or multicast specific information. With this patch we are able now to read, modify unicast and multicast addresses. Signed-off-by: Oleksij Rempel --- drivers/net/dsa/qca/ar9331.c | 349 +++++++++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) diff --git a/drivers/net/dsa/qca/ar9331.c b/drivers/net/dsa/qca/ar9331.c index 2f5673ea3140..d94c7ea163c4 100644 --- a/drivers/net/dsa/qca/ar9331.c +++ b/drivers/net/dsa/qca/ar9331.c @@ -66,6 +66,47 @@ #define AR9331_SW_REG_GLOBAL_CTRL 0x30 #define AR9331_SW_GLOBAL_CTRL_MFS_M GENMASK(13, 0) +/* Size of the address resolution table (ARL) */ +#define AR9331_SW_NUM_ARL_RECORDS 1024 + +#define AR9331_SW_REG_ADDR_TABLE_FUNCTION0 0x50 +#define AR9331_SW_AT_ADDR_BYTES4 GENMASK(31, 24) +#define AR9331_SW_AT_ADDR_BYTES5 GENMASK(23, 16) +#define AR9331_SW_AT_FULL_VIO BIT(12) +#define AR9331_SW_AT_PORT_NUM GENMASK(11, 8) +#define AR9331_SW_AT_FLUSH_STATIC_EN BIT(4) +#define AR9331_SW_AT_BUSY BIT(3) +#define AR9331_SW_AT_FUNC GENMASK(2, 0) +#define AR9331_SW_AT_FUNC_NOP 0 +#define AR9331_SW_AT_FUNC_FLUSH_ALL 1 +#define AR9331_SW_AT_FUNC_LOAD_ENTRY 2 +#define AR9331_SW_AT_FUNC_PURGE_ENTRY 3 +#define AR9331_SW_AT_FUNC_FLUSH_ALL_UNLOCKED 4 +#define AR9331_SW_AT_FUNC_FLUSH_PORT 5 +#define AR9331_SW_AT_FUNC_GET_NEXT 6 +#define AR9331_SW_AT_FUNC_FIND_MAC 7 + +#define AR9331_SW_REG_ADDR_TABLE_FUNCTION1 0x54 +#define AR9331_SW_AT_ADDR_BYTES0 GENMASK(31, 24) +#define AR9331_SW_AT_ADDR_BYTES1 GENMASK(23, 16) +#define AR9331_SW_AT_ADDR_BYTES2 GENMASK(15, 8) +#define AR9331_SW_AT_ADDR_BYTES3 GENMASK(7, 0) + +#define AR9331_SW_REG_ADDR_TABLE_FUNCTION2 0x58 +#define AR9331_SW_AT_COPY_TO_CPU BIT(26) +#define AR9331_SW_AT_REDIRECT_TOCPU BIT(25) +#define AR9331_SW_AT_LEAKY_EN BIT(24) +#define AR9331_SW_AT_STATUS GENMASK(19, 16) +#define AR9331_SW_AT_STATUS_EMPTY 0 +/* STATUS values from 7 to 1 are different aging levels */ +#define AR9331_SW_AT_STATUS_STATIC 0xf + +#define AR9331_SW_AT_SA_DROP_EN BIT(14) +#define AR9331_SW_AT_MIRROR_EN BIT(13) +#define AR9331_SW_AT_PRIORITY_EN BIT(12) +#define AR9331_SW_AT_PRIORITY GENMASK(11, 10) +#define AR9331_SW_AT_DES_PORT GENMASK(5, 0) + #define AR9331_SW_REG_ADDR_TABLE_CTRL 0x5c #define AR9331_SW_AT_ARP_EN BIT(20) #define AR9331_SW_AT_LEARN_CHANGE_EN BIT(18) @@ -267,6 +308,12 @@ struct ar9331_sw_port { struct spinlock stats_lock; }; +struct ar9331_sw_fdb { + u8 port_mask; + u8 aging; + u8 mac[ETH_ALEN]; +}; + struct ar9331_sw_priv { struct device *dev; struct dsa_switch ds; @@ -731,6 +778,302 @@ static void ar9331_get_stats64(struct dsa_switch *ds, int port, spin_unlock(&p->stats_lock); } +static int ar9331_sw_fdb_wait(struct ar9331_sw_priv *priv, u32 *f0) +{ + struct regmap *regmap = priv->regmap; + + return regmap_read_poll_timeout(regmap, + AR9331_SW_REG_ADDR_TABLE_FUNCTION0, + *f0, !(*f0 & AR9331_SW_AT_BUSY), + 10, 2000); +} + +static int ar9331_sw_port_fdb_write(struct ar9331_sw_priv *priv, + u32 f0, u32 f1, u32 f2) +{ + struct regmap *regmap = priv->regmap; + int ret; + + ret = regmap_write(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION2, f2); + if (ret) + return ret; + + ret = regmap_write(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION1, f1); + if (ret) + return ret; + + return regmap_write(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION0, f0); +} + +static int ar9331_sw_fdb_next(struct ar9331_sw_priv *priv, + struct ar9331_sw_fdb *fdb, int port) +{ + struct regmap *regmap = priv->regmap; + unsigned int status, ports; + u32 f0, f1, f2; + int ret; + + /* Keep AT_ADDR_BYTES4/5 to search next entry after current */ + ret = regmap_update_bits(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION0, + AR9331_SW_AT_FUNC | AR9331_SW_AT_BUSY, + AR9331_SW_AT_BUSY | + FIELD_PREP(AR9331_SW_AT_FUNC, + AR9331_SW_AT_FUNC_GET_NEXT)); + if (ret) + return ret; + + ret = ar9331_sw_fdb_wait(priv, &f0); + if (ret) + return ret; + + ret = regmap_read(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION2, &f2); + if (ret) + return ret; + + /* If the hardware returns an MAC != 0 and the AT_STATUS is zero, there + * is no next valid entry in the address table. + */ + status = FIELD_GET(AR9331_SW_AT_STATUS, f2); + fdb->aging = status; + if (!status) + return 0; + + ret = regmap_read(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION1, &f1); + if (ret) + return ret; + + fdb->mac[0] = FIELD_GET(AR9331_SW_AT_ADDR_BYTES0, f1); + fdb->mac[1] = FIELD_GET(AR9331_SW_AT_ADDR_BYTES1, f1); + fdb->mac[2] = FIELD_GET(AR9331_SW_AT_ADDR_BYTES2, f1); + fdb->mac[3] = FIELD_GET(AR9331_SW_AT_ADDR_BYTES3, f1); + fdb->mac[4] = FIELD_GET(AR9331_SW_AT_ADDR_BYTES4, f0); + fdb->mac[5] = FIELD_GET(AR9331_SW_AT_ADDR_BYTES5, f0); + + ports = FIELD_GET(AR9331_SW_AT_DES_PORT, f2); + if (!(ports & BIT(port))) + return -EAGAIN; + + return 0; +} + +static void ar9331_sw_port_fdb_prepare(const unsigned char *mac, u32 *f0, + u32 *f1, unsigned int func) +{ + *f1 = FIELD_PREP(AR9331_SW_AT_ADDR_BYTES0, mac[0]) | + FIELD_PREP(AR9331_SW_AT_ADDR_BYTES1, mac[1]) | + FIELD_PREP(AR9331_SW_AT_ADDR_BYTES2, mac[2]) | + FIELD_PREP(AR9331_SW_AT_ADDR_BYTES3, mac[3]); + *f0 = FIELD_PREP(AR9331_SW_AT_ADDR_BYTES4, mac[4]) | + FIELD_PREP(AR9331_SW_AT_ADDR_BYTES5, mac[5]) | + FIELD_PREP(AR9331_SW_AT_FUNC, func) | AR9331_SW_AT_BUSY; +} + +static int ar9331_sw_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv; + int cnt = AR9331_SW_NUM_ARL_RECORDS; + struct ar9331_sw_fdb fdb = { 0 }; + bool is_static; + int ret; + u32 f0; + + /* Make sure no pending operation is in progress. Since no timeout and + * interval values are documented, we use here "seems to be sane, works + * for me" values. + */ + ret = ar9331_sw_fdb_wait(priv, &f0); + if (ret) + return ret; + + /* If the address and the AT_STATUS are both zero, the hardware will + * search the first valid entry from entry0. + * If the address is set to zero and the AT_STATUS is not zero, the + * hardware will discover the next valid entry which has an address + * of 0x0. + */ + ret = ar9331_sw_port_fdb_write(priv, 0, 0, 0); + if (ret) + return ret; + + while (cnt--) { + ret = ar9331_sw_fdb_next(priv, &fdb, port); + if (ret == -EAGAIN) + continue; + else if (ret) + return ret; + + if (!fdb.aging) + break; + + is_static = (fdb.aging == AR9331_SW_AT_STATUS_STATIC); + ret = cb(fdb.mac, 0, is_static, data); + if (ret) + break; + } + + return ret; +} + +static int ar9331_sw_port_fdb_rmw(struct ar9331_sw_priv *priv, + const unsigned char *mac, + u8 port_mask_set, + u8 port_mask_clr) +{ + u8 port_mask, port_mask_new, status, func; + struct regmap *regmap = priv->regmap; + u32 f0, f1, f2 = 0; + int ret; + + ret = ar9331_sw_fdb_wait(priv, &f0); + if (ret) + return ret; + + ar9331_sw_port_fdb_prepare(mac, &f0, &f1, AR9331_SW_AT_FUNC_FIND_MAC); + + ret = ar9331_sw_port_fdb_write(priv, f0, f1, f2); + if (ret) + return ret; + + ret = ar9331_sw_fdb_wait(priv, &f0); + if (ret) + return ret; + + ret = regmap_read(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION2, &f2); + if (ret) + return ret; + + port_mask = FIELD_GET(AR9331_SW_AT_DES_PORT, f2); + status = FIELD_GET(AR9331_SW_AT_STATUS, f2); + if (status > 0 && status < AR9331_SW_AT_STATUS_STATIC) { + dev_dbg(priv->dev, "%s: found existing dynamic entry on %x\n", + __func__, port_mask); + + if (port_mask_set && port_mask_set != port_mask) + dev_dbg(priv->dev, "%s: found existing dynamic entry on %x, replacing it with static on %x\n", + __func__, port_mask, port_mask_set); + port_mask = 0; + } else if (!status && !port_mask_set) { + return 0; + } + + port_mask_new = port_mask & ~port_mask_clr; + port_mask_new |= port_mask_set; + + if (port_mask_new == port_mask && + status == AR9331_SW_AT_STATUS_STATIC) { + dev_dbg(priv->dev, "%s: no need to overwrite existing valid entry on %x\n", + __func__, port_mask_new); + return 0; + } + + if (port_mask_new) { + func = AR9331_SW_AT_FUNC_LOAD_ENTRY; + } else { + func = AR9331_SW_AT_FUNC_PURGE_ENTRY; + port_mask_new = port_mask; + } + + f2 = FIELD_PREP(AR9331_SW_AT_DES_PORT, port_mask_new) | + FIELD_PREP(AR9331_SW_AT_STATUS, AR9331_SW_AT_STATUS_STATIC); + + ar9331_sw_port_fdb_prepare(mac, &f0, &f1, func); + + ret = ar9331_sw_port_fdb_write(priv, f0, f1, f2); + if (ret) + return ret; + + ret = ar9331_sw_fdb_wait(priv, &f0); + if (ret) + return ret; + + if (f0 & AR9331_SW_AT_FULL_VIO) { + /* cleanup error status */ + regmap_write(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION0, 0); + dev_err(priv->dev, "%s: can't add new entry, ATU is full\n", __func__); + return -ENOMEM; + } + + return 0; +} + +static int ar9331_sw_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *mac, u16 vid) +{ + struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv; + u16 port_mask = BIT(port); + + if (vid) + return -EINVAL; + + return ar9331_sw_port_fdb_rmw(priv, mac, port_mask, 0); +} + +static int ar9331_sw_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *mac, u16 vid) +{ + struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv; + u16 port_mask = BIT(port); + + if (vid) + return -EINVAL; + + return ar9331_sw_port_fdb_rmw(priv, mac, 0, port_mask); +} + +static int ar9331_sw_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb) +{ + struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv; + u16 port_mask = BIT(port); + + if (mdb->vid) + return -EOPNOTSUPP; + + return ar9331_sw_port_fdb_rmw(priv, mdb->addr, port_mask, 0); +} + +static int ar9331_sw_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb) +{ + struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv; + u16 port_mask = BIT(port); + + if (mdb->vid) + return -EOPNOTSUPP; + + return ar9331_sw_port_fdb_rmw(priv, mdb->addr, 0, port_mask); +} + +static void ar9331_sw_port_fast_age(struct dsa_switch *ds, int port) +{ + struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv; + struct regmap *regmap = priv->regmap; + int ret; + u32 f0; + + ret = ar9331_sw_fdb_wait(priv, &f0); + if (ret) + goto error; + + /* Flush all non static unicast address on a given port */ + f0 = FIELD_PREP(AR9331_SW_AT_PORT_NUM, port) | + FIELD_PREP(AR9331_SW_AT_FUNC, AR9331_SW_AT_FUNC_FLUSH_PORT) | + AR9331_SW_AT_BUSY; + + ret = regmap_write(regmap, AR9331_SW_REG_ADDR_TABLE_FUNCTION0, f0); + if (ret) + goto error; + + ret = ar9331_sw_fdb_wait(priv, &f0); + if (ret) + goto error; + + return; +error: + dev_err(priv->dev, "%s: error: %i\n", __func__, ret); +} + static const struct dsa_switch_ops ar9331_sw_ops = { .get_tag_protocol = ar9331_sw_get_tag_protocol, .setup = ar9331_sw_setup, @@ -740,6 +1083,12 @@ static const struct dsa_switch_ops ar9331_sw_ops = { .phylink_mac_link_down = ar9331_sw_phylink_mac_link_down, .phylink_mac_link_up = ar9331_sw_phylink_mac_link_up, .get_stats64 = ar9331_get_stats64, + .port_fast_age = ar9331_sw_port_fast_age, + .port_fdb_del = ar9331_sw_port_fdb_del, + .port_fdb_add = ar9331_sw_port_fdb_add, + .port_fdb_dump = ar9331_sw_port_fdb_dump, + .port_mdb_add = ar9331_sw_port_mdb_add, + .port_mdb_del = ar9331_sw_port_mdb_del, }; static irqreturn_t ar9331_sw_irq(int irq, void *data) From patchwork Mon Aug 2 13:10:37 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 490615 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=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable 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 E5041C4338F for ; Mon, 2 Aug 2021 13:10:58 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CD88460F6D for ; Mon, 2 Aug 2021 13:10:58 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233916AbhHBNLG (ORCPT ); Mon, 2 Aug 2021 09:11:06 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57276 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233785AbhHBNK5 (ORCPT ); Mon, 2 Aug 2021 09:10:57 -0400 Received: from metis.ext.pengutronix.de (metis.ext.pengutronix.de [IPv6:2001:67c:670:201:290:27ff:fe1d:cc33]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 86696C061796 for ; Mon, 2 Aug 2021 06:10:47 -0700 (PDT) Received: from dude.hi.pengutronix.de ([2001:67c:670:100:1d::7]) by metis.ext.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mAXiK-0003Me-OY; Mon, 02 Aug 2021 15:10:40 +0200 Received: from ore by dude.hi.pengutronix.de with local (Exim 4.92) (envelope-from ) id 1mAXiJ-00008l-L3; Mon, 02 Aug 2021 15:10:39 +0200 From: Oleksij Rempel To: Andrew Lunn , Vivien Didelot , Florian Fainelli , Vladimir Oltean , "David S. Miller" , Jakub Kicinski , Russell King Cc: Oleksij Rempel , Pengutronix Kernel Team , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mips@vger.kernel.org Subject: [PATCH net-next v3 6/6] net: dsa: qca: ar9331: add vlan support Date: Mon, 2 Aug 2021 15:10:37 +0200 Message-Id: <20210802131037.32326-7-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210802131037.32326-1-o.rempel@pengutronix.de> References: <20210802131037.32326-1-o.rempel@pengutronix.de> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2001:67c:670:100:1d::7 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: netdev@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This switch provides simple VLAN resolution database for 16 entries. With this database we can cover typical functionalities as port based VLANs, untagged and tagged egress. Port based ingress filtering. The VLAN database is working on top of forwarding database. So, potentially, we can have multiple VLANs on top of multiple bridges. Hawing one VLAN on top of multiple bridges will fail on different levels, most probably DSA framework should warn if some one won't to make something likes this. Signed-off-by: Oleksij Rempel --- drivers/net/dsa/qca/ar9331.c | 235 ++++++++++++++++++++++++++++++++++- 1 file changed, 233 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/qca/ar9331.c b/drivers/net/dsa/qca/ar9331.c index a0324fed2136..0865ffbc2c74 100644 --- a/drivers/net/dsa/qca/ar9331.c +++ b/drivers/net/dsa/qca/ar9331.c @@ -67,6 +67,27 @@ #define AR9331_SW_REG_GLOBAL_CTRL 0x30 #define AR9331_SW_GLOBAL_CTRL_MFS_M GENMASK(13, 0) +#define AR9331_SW_NUM_VLAN_RECORDS 16 + +#define AR9331_SW_REG_VLAN_TABLE_FUNCTION0 0x40 +#define AR9331_SW_VT0_PRI_EN BIT(31) +#define AR9331_SW_VT0_PRI GENMASK(30, 28) +#define AR9331_SW_VT0_VID GENMASK(27, 16) +#define AR9331_SW_VT0_PORT_NUM GENMASK(11, 8) +#define AR9331_SW_VT0_FULL_VIO BIT(4) +#define AR9331_SW_VT0_BUSY BIT(3) +#define AR9331_SW_VT0_FUNC GENMASK(2, 0) +#define AR9331_SW_VT0_FUNC_NOP 0 +#define AR9331_SW_VT0_FUNC_FLUSH_ALL 1 +#define AR9331_SW_VT0_FUNC_LOAD_ENTRY 2 +#define AR9331_SW_VT0_FUNC_PURGE_ENTRY 3 +#define AR9331_SW_VT0_FUNC_DEL_PORT 4 +#define AR9331_SW_VT0_FUNC_GET_NEXT 5 + +#define AR9331_SW_REG_VLAN_TABLE_FUNCTION1 0x44 +#define AR9331_SW_VT1_VALID BIT(11) +#define AR9331_SW_VT1_VID_MEM GENMASK(9, 0) + /* Size of the address resolution table (ARL) */ #define AR9331_SW_NUM_ARL_RECORDS 1024 @@ -309,6 +330,11 @@ struct ar9331_sw_port { struct spinlock stats_lock; }; +struct ar9331_sw_vlan_db { + u16 vid; + u8 port_mask; +}; + struct ar9331_sw_fdb { u8 port_mask; u8 aging; @@ -327,6 +353,7 @@ struct ar9331_sw_priv { struct regmap *regmap; struct reset_control *sw_reset; struct ar9331_sw_port port[AR9331_SW_PORTS]; + struct ar9331_sw_vlan_db vdb[AR9331_SW_NUM_VLAN_RECORDS]; }; static struct ar9331_sw_priv *ar9331_sw_port_to_priv(struct ar9331_sw_port *port) @@ -557,8 +584,6 @@ static int ar9331_sw_setup(struct dsa_switch *ds) goto error; } - ds->configure_vlan_while_not_filtering = false; - return 0; error: dev_err_ratelimited(priv->dev, "%s: %i\n", __func__, ret); @@ -1144,6 +1169,209 @@ static void ar9331_sw_port_bridge_leave(struct dsa_switch *ds, int port, ar9331_sw_port_bridge_mod(ds, port, br, false); } +static int ar9331_port_vlan_filtering(struct dsa_switch *ds, int port, + bool vlan_filtering, + struct netlink_ext_ack *extack) +{ + struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv; + struct regmap *regmap = priv->regmap; + u32 mode; + int ret; + + if (vlan_filtering) + mode = AR9331_SW_8021Q_MODE_SECURE; + else + mode = AR9331_SW_8021Q_MODE_NONE; + + ret = regmap_update_bits(regmap, AR9331_SW_REG_PORT_VLAN(port), + AR9331_SW_PORT_VLAN_8021Q_MODE, + FIELD_PREP(AR9331_SW_PORT_VLAN_8021Q_MODE, + mode)); + if (ret) + dev_err(priv->dev, "%s: error: %pe\n", __func__, ERR_PTR(ret)); + + return ret; +} + +static int ar9331_sw_vt_wait(struct ar9331_sw_priv *priv, u32 *f0) +{ + struct regmap *regmap = priv->regmap; + + return regmap_read_poll_timeout(regmap, + AR9331_SW_REG_VLAN_TABLE_FUNCTION0, + *f0, !(*f0 & AR9331_SW_VT0_BUSY), + 100, 2000); +} + +static int ar9331_sw_port_vt_rmw(struct ar9331_sw_priv *priv, u16 vid, + u8 port_mask_set, u8 port_mask_clr) +{ + struct regmap *regmap = priv->regmap; + u32 f0, f1, port_mask = 0, port_mask_new, func; + struct ar9331_sw_vlan_db *vdb = NULL; + int ret, i; + + if (!vid) + return 0; + + ret = ar9331_sw_vt_wait(priv, &f0); + if (ret) + return ret; + + ret = regmap_write(regmap, AR9331_SW_REG_VLAN_TABLE_FUNCTION0, 0); + if (ret) + goto error; + + ret = regmap_write(regmap, AR9331_SW_REG_VLAN_TABLE_FUNCTION1, 0); + if (ret) + goto error; + + for (i = 0; i < ARRAY_SIZE(priv->vdb); i++) { + if (priv->vdb[i].vid == vid) { + vdb = &priv->vdb[i]; + break; + } + } + + ret = regmap_read(regmap, AR9331_SW_REG_VLAN_TABLE_FUNCTION1, &f1); + if (ret) + return ret; + + if (vdb) + port_mask = vdb->port_mask; + + port_mask_new = port_mask & ~port_mask_clr; + port_mask_new |= port_mask_set; + + if (port_mask_new && port_mask_new == port_mask) + return 0; + + if (port_mask_new) { + func = AR9331_SW_VT0_FUNC_LOAD_ENTRY; + } else { + func = AR9331_SW_VT0_FUNC_PURGE_ENTRY; + port_mask_new = port_mask; + } + + if (vdb) { + vdb->port_mask = port_mask_new; + + if (!port_mask_new) + vdb->vid = 0; + } else { + for (i = 0; i < ARRAY_SIZE(priv->vdb); i++) { + if (!priv->vdb[i].vid) { + vdb = &priv->vdb[i]; + break; + } + } + + if (!vdb) + return -ENOMEM; + + vdb->vid = vid; + vdb->port_mask = port_mask_new; + } + + f0 = FIELD_PREP(AR9331_SW_VT0_VID, vid) | + FIELD_PREP(AR9331_SW_VT0_FUNC, func) | + AR9331_SW_VT0_BUSY; + f1 = FIELD_PREP(AR9331_SW_VT1_VID_MEM, port_mask_new) | + AR9331_SW_VT1_VALID; + + ret = regmap_write(regmap, AR9331_SW_REG_VLAN_TABLE_FUNCTION1, f1); + if (ret) + return ret; + + ret = regmap_write(regmap, AR9331_SW_REG_VLAN_TABLE_FUNCTION0, f0); + if (ret) + return ret; + + ret = ar9331_sw_vt_wait(priv, &f0); + if (ret) + return ret; + + if (f0 & AR9331_SW_VT0_FULL_VIO) { + /* cleanup error status */ + regmap_write(regmap, AR9331_SW_REG_VLAN_TABLE_FUNCTION0, 0); + return -ENOMEM; + } + + return 0; + +error: + dev_err(priv->dev, "%s: error: %pe\n", __func__, ERR_PTR(ret)); + + return ret; +} + +static int ar9331_port_vlan_set_pvid(struct ar9331_sw_priv *priv, int port, + u16 pvid) +{ + struct regmap *regmap = priv->regmap; + int ret; + u32 mask, val; + + mask = AR9331_SW_PORT_VLAN_8021Q_MODE | + AR9331_SW_PORT_VLAN_FORCE_DEFALUT_VID_EN | + AR9331_SW_PORT_VLAN_FORCE_PORT_VLAN_EN; + val = AR9331_SW_PORT_VLAN_FORCE_DEFALUT_VID_EN | + AR9331_SW_PORT_VLAN_FORCE_PORT_VLAN_EN | + FIELD_PREP(AR9331_SW_PORT_VLAN_8021Q_MODE, + AR9331_SW_8021Q_MODE_FALLBACK); + + ret = regmap_update_bits(regmap, AR9331_SW_REG_PORT_VLAN(port), + mask, val); + if (ret) + return ret; + + return regmap_update_bits(regmap, AR9331_SW_REG_PORT_VLAN(port), + AR9331_SW_PORT_VLAN_PORT_VID, + FIELD_PREP(AR9331_SW_PORT_VLAN_PORT_VID, + pvid)); +} + +static int ar9331_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + struct ar9331_sw_priv *priv = ds->priv; + struct regmap *regmap = priv->regmap; + int ret, mode; + + ret = ar9331_sw_port_vt_rmw(priv, vlan->vid, BIT(port), 0); + if (ret) + goto error; + + if (vlan->flags & BRIDGE_VLAN_INFO_PVID) + ret = ar9331_port_vlan_set_pvid(priv, port, vlan->vid); + + if (ret) + goto error; + + if (vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED) + mode = AR9331_SW_PORT_CTRL_EG_VLAN_MODE_STRIP; + else + mode = AR9331_SW_PORT_CTRL_EG_VLAN_MODE_ADD; + + ret = regmap_update_bits(regmap, AR9331_SW_REG_PORT_CTRL(port), + AR9331_SW_PORT_CTRL_EG_VLAN_MODE, mode); + if (ret) + goto error; + + return 0; +error: + dev_err(priv->dev, "%s: error: %pe\n", __func__, ERR_PTR(ret)); + + return ret; +} + +static int ar9331_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + return ar9331_sw_port_vt_rmw(ds->priv, vlan->vid, 0, BIT(port)); +} + static const struct dsa_switch_ops ar9331_sw_ops = { .get_tag_protocol = ar9331_sw_get_tag_protocol, .setup = ar9331_sw_setup, @@ -1162,6 +1390,9 @@ static const struct dsa_switch_ops ar9331_sw_ops = { .set_ageing_time = ar9331_sw_set_ageing_time, .port_bridge_join = ar9331_sw_port_bridge_join, .port_bridge_leave = ar9331_sw_port_bridge_leave, + .port_vlan_filtering = ar9331_port_vlan_filtering, + .port_vlan_add = ar9331_port_vlan_add, + .port_vlan_del = ar9331_port_vlan_del, }; static irqreturn_t ar9331_sw_irq(int irq, void *data)