From nobody Mon May 25 08:11:40 2026 Received: from mail-qv1-f53.google.com (mail-qv1-f53.google.com [209.85.219.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7C320259C80 for ; Sat, 16 May 2026 03:47:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903230; cv=none; b=XXriGRBo8STAjyqalABRJUeTCbUodkrilYgAb/1Ob3Jv+piQVA4Uos0zfZfiaJyQH+iQ4A5mso9UZMe5ExXygNr3kpTzfm4On8VnP+kl6hlCOAetoY3GmUtlCeI7Sxv+rg6cKUL1+NbzkVdxn+18IxSiWXI6QAFQci7/CCh5Ih8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903230; c=relaxed/simple; bh=cTbRztR4daSNn6FdbRQypiXJ+qpF6W/5moxuBhFKP+Y=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=kDcjNRxTewpTN+Ugc3ZBA5ImKpPmygcNzs9YJF4BwmLgm1AMRQ/i4ZTgkMZgIgDUuoNCv86vd9ROcYL4nCqxugrVH1jkkVz4llCOoTzVEgJ3atW1xQ5ZHKhiTWF+koiowvILxYr0CIf58VNFMLJOwRvwwcM/uHSa9Z2c9xeEmio= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MM3VyOrd; arc=none smtp.client-ip=209.85.219.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MM3VyOrd" Received: by mail-qv1-f53.google.com with SMTP id 6a1803df08f44-8bb4e8a5240so8828186d6.1 for ; Fri, 15 May 2026 20:47:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778903228; x=1779508028; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=VpCb6dtdmrBKh+Y9OCSsSmNkTM9oxHYAdURV0LdnR9s=; b=MM3VyOrdrKUbCqe41iACKtHU5UhtXivfRMcEQlYPwTjmftw++bBNf+Ul0TdGdeMCMK aVryZQVVPq3ZXQF2NH+R+lkr1R9a0+ojcSWPtob0t/t4vAsZ1vwa3oCbt1LmGgD7oI52 7/9klDRHAT3obBlDIrIC/Hj3n8z22SgBjYUzOyPS27oo4+srnx91pqrIe4BU8dlnfLeQ mIE4wibCgdN/fnWKQURXzPxPy+Rur5AgIC6kA6zT5kU9IMa/cYEjnxeNOEFJM4y/+8d5 KoGqabA0PrZbyzRr8XMprGndo0v9hCa7GjclomVFsqheXBhrCHnh4eDcIP4QgkEIVBTG QY6Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778903228; x=1779508028; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=VpCb6dtdmrBKh+Y9OCSsSmNkTM9oxHYAdURV0LdnR9s=; b=Tg1akUwxzK1Ze/hrakW8uLz9ca9pyAMDMZ5vJMKYyV4POARzcHwmVZaWuI7SdW4ytb wminzKG2OWztvP2DVMsd3M+dqltvoJBAG0mv9mGYG0xQFn32ms1cNgMchwdKSxByeVtE JyaB7VB1ndrhpbb1gYLaQbJUUvKwkD+RfL1/WteWT9wGfUoU0O/Lj4PX87QT6DY2TTKL gKi02al8j4PQEsBdhvF5UHUJl2LuSLqUQuTYjhYPOdB+ZXr/wQLtAEmLC3/xQjJ5zpE/ sM8vRwpxY8Bsqd0H8O19CBilYgemxQMuA+KDSDKCPuUL7jX36WTwsUNjcL9Moj5tU5se A5Yg== X-Forwarded-Encrypted: i=1; AFNElJ93Q1IXBn9mfGJoNy17gQErk/Yl2JYwjw74HTxregUDJHuaD699INIYiu3NZwE3iUcY7lku/pQx8WE3Z04=@vger.kernel.org X-Gm-Message-State: AOJu0YyLsJ9q0Wnwl+7McVvO0CtS8oK17n7JAD52Hf8fsqK2DZsIK2Fi PFLeR0/9eok/MzYPV1zWhsbDBmYEB7XxrAcSjfqa8NtT0YuFduY+W5uO X-Gm-Gg: Acq92OHQCswhfU3eryqzpY+kbFfDI7f89J0aVxDvWVP5rIEsD8z1canB/R6kLYosuNd 5ejpdkQCwV7yufZDsasDdAacgIo0SMKBBBJD5vhd2y8534pZVcDgTHAcrjZL0I1mdZtPsAdCD7e YyLJ/BTppEF7hBJRgkJ0/TqjNrQlAM8uUkdlI5tRkiNkg2xxMr7ZCgXZ/Wx4hbMTEpK6pd2E2Zp rAvg/hJPTBITe4650/5s3Kf5HtnV9LknkIOL3N4eJFi/2EUCD6VqhMzaCtJJKbFlAbHu/MXbML2 92V3/Ydyq7ouyxB0t6Cf5akPEFACA+sQ0reTsNVxUH+HDEuTWq0AwNs+5mrkrDC2UAm1nsYk7kq R5RK27wQGVdp9k4w8y68e8ON/v0s5VBuYHJ8H0QAKvpDa58OGKj0paXs7OgTaAj0fG6xq6fBsdN EYlULlpbYaHHP0zT1qFEok39lAXEfHADsXSZe0LFPQcvRF2NBymQ== X-Received: by 2002:a05:6214:21c6:b0:8be:3c37:5e6a with SMTP id 6a1803df08f44-8ca0f663a53mr116249596d6.21.1778903228457; Fri, 15 May 2026 20:47:08 -0700 (PDT) Received: from tresc054937.tre-sc.gov.br ([187.65.210.13]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c908d1d2e7sm70874546d6.16.2026.05.15.20.47.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 20:47:07 -0700 (PDT) From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 00:46:17 -0300 Subject: [net-next PATCH v4 1/8] net: dsa: realtek: rtl8365mb: use ERR_PTR Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260516-realtek_forward-v4-1-8b6d6a1eefdc@gmail.com> References: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> In-Reply-To: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> To: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Linus Walleij , =?utf-8?q?Alvin_=C5=A0ipraga?= , Yury Norov , Rasmus Villemoes , Russell King Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Luiz Angelo Daros de Luca X-Mailer: b4 0.15.2 Convert numeric error codes into human-readable strings by using %pe together with ERR_PTR() in dev_err() messages. Reviewed-by: Linus Walleij Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/rtl8365mb.c | 44 ++++++++++++++++++++-------------= ---- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb.c b/drivers/net/dsa/realtek/= rtl8365mb.c index c35cef01ec26..9cb0f0b9beb6 100644 --- a/drivers/net/dsa/realtek/rtl8365mb.c +++ b/drivers/net/dsa/realtek/rtl8365mb.c @@ -789,8 +789,8 @@ static int rtl8365mb_phy_read(struct realtek_priv *priv= , int phy, int regnum) ret =3D rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); if (ret) { dev_err(priv->dev, - "failed to read PHY%d reg %02x @ %04x, ret %d\n", phy, - regnum, ocp_addr, ret); + "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy, + regnum, ocp_addr, ERR_PTR(ret)); return ret; } =20 @@ -817,8 +817,8 @@ static int rtl8365mb_phy_write(struct realtek_priv *pri= v, int phy, int regnum, ret =3D rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val); if (ret) { dev_err(priv->dev, - "failed to write PHY%d reg %02x @ %04x, ret %d\n", phy, - regnum, ocp_addr, ret); + "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy, + regnum, ocp_addr, ERR_PTR(ret)); return ret; } =20 @@ -1068,8 +1068,8 @@ static void rtl8365mb_phylink_mac_config(struct phyli= nk_config *config, ret =3D rtl8365mb_ext_config_rgmii(priv, port, state->interface); if (ret) dev_err(priv->dev, - "failed to configure RGMII mode on port %d: %d\n", - port, ret); + "failed to configure RGMII mode on port %d: %pe\n", + port, ERR_PTR(ret)); return; } =20 @@ -1098,8 +1098,8 @@ static void rtl8365mb_phylink_mac_link_down(struct ph= ylink_config *config, false, false); if (ret) dev_err(priv->dev, - "failed to reset forced mode on port %d: %d\n", - port, ret); + "failed to reset forced mode on port %d: %pe\n", + port, ERR_PTR(ret)); =20 return; } @@ -1129,8 +1129,8 @@ static void rtl8365mb_phylink_mac_link_up(struct phyl= ink_config *config, rx_pause); if (ret) dev_err(priv->dev, - "failed to force mode on port %d: %d\n", port, - ret); + "failed to force mode on port %d: %pe\n", port, + ERR_PTR(ret)); =20 return; } @@ -1285,8 +1285,8 @@ static void rtl8365mb_get_ethtool_stats(struct dsa_sw= itch *ds, int port, u64 *da mib->length, &data[i]); if (ret) { dev_err(priv->dev, - "failed to read port %d counters: %d\n", port, - ret); + "failed to read port %d counters: %pe\n", port, + ERR_PTR(ret)); break; } } @@ -1638,7 +1638,8 @@ static irqreturn_t rtl8365mb_irq(int irq, void *data) return IRQ_HANDLED; =20 out_error: - dev_err(priv->dev, "failed to read interrupt status: %d\n", ret); + dev_err(priv->dev, "failed to read interrupt status: %pe\n", + ERR_PTR(ret)); =20 out_none: return IRQ_NONE; @@ -1712,8 +1713,8 @@ static int rtl8365mb_irq_setup(struct realtek_priv *p= riv) irq =3D of_irq_get(intc, 0); if (irq <=3D 0) { if (irq !=3D -EPROBE_DEFER) - dev_err(priv->dev, "failed to get parent irq: %d\n", - irq); + dev_err(priv->dev, "failed to get parent irq: %pe\n", + ERR_PTR(irq)); ret =3D irq ? irq : -EINVAL; goto out_put_node; } @@ -1776,7 +1777,8 @@ static int rtl8365mb_irq_setup(struct realtek_priv *p= riv) ret =3D request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT, "rtl8365mb", priv); if (ret) { - dev_err(priv->dev, "failed to request irq: %d\n", ret); + dev_err(priv->dev, "failed to request irq: %pe\n", + ERR_PTR(ret)); goto out_remove_irqdomain; } =20 @@ -1952,14 +1954,16 @@ static int rtl8365mb_setup(struct dsa_switch *ds) =20 ret =3D rtl8365mb_reset_chip(priv); if (ret) { - dev_err(priv->dev, "failed to reset chip: %d\n", ret); + dev_err(priv->dev, "failed to reset chip: %pe\n", + ERR_PTR(ret)); goto out_error; } =20 /* Configure switch to vendor-defined initial state */ ret =3D rtl8365mb_switch_init(priv); if (ret) { - dev_err(priv->dev, "failed to initialize switch: %d\n", ret); + dev_err(priv->dev, "failed to initialize switch: %pe\n", + ERR_PTR(ret)); goto out_error; } =20 @@ -2077,8 +2081,8 @@ static int rtl8365mb_detect(struct realtek_priv *priv) =20 ret =3D rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver); if (ret) { - dev_err(priv->dev, "failed to read chip id and version: %d\n", - ret); + dev_err(priv->dev, "failed to read chip id and version: %pe\n", + ERR_PTR(ret)); return ret; } =20 --=20 2.54.0 From nobody Mon May 25 08:11:40 2026 Received: from mail-qv1-f43.google.com (mail-qv1-f43.google.com [209.85.219.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 517A229992A for ; Sat, 16 May 2026 03:47:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903237; cv=none; b=SU59BafKGHQCyttKrGot1mm1+E2JDfuj4XIfewOsHsW+CfBCYxAh40MOFe1m6IACHgU32WVDnk3rbxOtrOkygdBFFRyuvqpJsiEtyR3OsJaqfWwVuEE1fcR2dJt/id2H51OvOaguoHsyYQtD0IltWA+7s14q8vZQAb1yAkRu7Ec= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903237; c=relaxed/simple; bh=ftm9A2pazaly3ujJmyyLWE2kx9nI+iOOSiT6EuIzSXc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Ji6dTqryW5knBdbuT9tMCJaRoD0d2CfkCj/eVyBjNTgRu6hM70ZtYVlfP4XdscdQN9Xj8Ft5vrHRdDfGVs5TTRKv3PuAiL6iHqKRa4OkyWuNu+ZRmYia4q4SFbZDXbzZkyYOZWzuMwpA6OzoxuS9ntlc7TcsIZGF/wx93VeI60k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=s7l99Ugm; arc=none smtp.client-ip=209.85.219.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="s7l99Ugm" Received: by mail-qv1-f43.google.com with SMTP id 6a1803df08f44-8b59772d441so8506196d6.0 for ; Fri, 15 May 2026 20:47:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778903235; x=1779508035; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=Z+EAZsJ8T3e8lYxhL4SWBYpxdBnvWEKX6PuId/d7ZxY=; b=s7l99UgmRyp6Lx1D5RJq3CyaCzpH5yfw8Qi69LOSSH37lJeYiNbbEAAkyNu+13/D0j 7TawNkrSU5CjEfkhpKg/Gp0szVF993evJpuyyDRx1lRedarYXZIshte4FJokgqoj37nB RAHafF7Vs6gaaTA+z+ThyZoY0Gda8qqYvejrReZLY8Ur3WXsnpQwWDBwVigFVYfMDSp/ 75vvqexnMwMmMs3Uqjn7EA0z6K/62Ra+dOfJs9UnrN0QoSG9+58EI2TVZn7p6iZXP6FK Fx5FrS8X5d/cQkIlM5GBerWQQVJ0kIea8nwbzuJqCKO3V0Tz/KEPrZQD0HClptpsBpgd REUg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778903235; x=1779508035; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=Z+EAZsJ8T3e8lYxhL4SWBYpxdBnvWEKX6PuId/d7ZxY=; b=EG/vIoz5q4evL1rXM6mqvXQ1kuOCcjwSYTL+QvgGzHmY3zBYU/t0tZD1FwmTNlOojo +ienWDaZQNhgftK2jb3seAVgJqFKwCKc3NavuJd/Sfr6xXjVacz2AMovTAlwnQL3s+W9 wpR/GrtLL0hA9eECCWxqvxuzbQ5D7ky8HQR/OAoOsF+GQdlnhzRo80KAqCfExdAkXJyu Yf9hJuhyJJGJs28PI7WWQUOzM3adGgrB6G4pKpEiJxmgBlw6M7XIhNRkfCXTB5+Z6ZLj bCQvXLawUjc+r2aUZaB2YxeQhOOEB7LmKyxBEEuAsB6FRxjuuV1amu6s3lkJCjknkTnL 7jBw== X-Forwarded-Encrypted: i=1; AFNElJ+azJvlf7ycTCuHFVYGml64pVYDk3jHKniUrm2iAlWeHe0HbiokPxRZevtEtUzMubsBW2tOoH4Br6z//64=@vger.kernel.org X-Gm-Message-State: AOJu0YxQ580qlpM+ZVqlBlwkf/W9oPJtH4jZKTIn51BdR4ed+gfusuhY 7TA8awfs6uXzOIIM3n4aOzPk3aha5EDPDaL2CtNp1zbdsvyhYkiWTdc9 X-Gm-Gg: Acq92OGSkI2VZ+NhRhxOEQaR6+6OSQ8O3IZSqopPISFSeqPlx5O5ng8BBBe0c/2Tngu L2kJ+/z1ipGGKmyOpfn7/5UT36FvL4b1SoQmjYRasTaTziUrz9gI79uko/Arz2YnvhMAcE0HWiH NFtmofCGCekZA/7ZB13mzNcv//0Z+wqeCkFHF7Ficx1A/hmo0IBYWkupq9JcCGc3Q0ow+U7BKdQ ZqQllXwJvfDctjY7lqZaeceEX2fNuy1ZeL5Sg8g7EucAqZmgCWLobhe76tLvEsefxsMT5F88BGl cfIg0HAMjxWSOaxYJ3xrhw5PH5gLBECKQvveDtXSNs4pCpHfJ+cLcGmuxIqpBUcN6YvffMFnutK wgd9NW++eWTjqxhmG/cHgqNt9jKAqVaj2sSuAUfB2r879Llqcky1gKMJmEctt0iyImBHKLD2Ttr CuGjlUq/I+rxv9pGvPJ1T1M19Fjtg99d42NMMl5ZVeJyTH8rlaqg== X-Received: by 2002:a05:6214:55c6:b0:8ca:e41:ab8 with SMTP id 6a1803df08f44-8ca0f63466bmr100980096d6.7.1778903235234; Fri, 15 May 2026 20:47:15 -0700 (PDT) Received: from tresc054937.tre-sc.gov.br ([187.65.210.13]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c908d1d2e7sm70874546d6.16.2026.05.15.20.47.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 20:47:14 -0700 (PDT) From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 00:46:18 -0300 Subject: [net-next PATCH v4 2/8] net: dsa: realtek: rtl8365mb: use dsa helpers for port iteration Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260516-realtek_forward-v4-2-8b6d6a1eefdc@gmail.com> References: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> In-Reply-To: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> To: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Linus Walleij , =?utf-8?q?Alvin_=C5=A0ipraga?= , Yury Norov , Rasmus Villemoes , Russell King Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Abdulkader Alrezej , Luiz Angelo Daros de Luca X-Mailer: b4 0.15.2 Use dsa_switch_for_each_*() whenever possible. For port setup(), a new blocking setup phase was added for all ports, including unused ones, before the user and CPU port setup. CPU isolation includes all user ports as traffic was being blocked in some scenarios (suggested by Abdulkader Alrezej). The driver bails out if a DSA port is detected as the driver was not tested in a cascading setup. Suggested-by: Abdulkader Alrezej Reviewed-by: Linus Walleij Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/rtl8365mb.c | 141 +++++++++++++++++++++++---------= ---- 1 file changed, 91 insertions(+), 50 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb.c b/drivers/net/dsa/realtek/= rtl8365mb.c index 9cb0f0b9beb6..d1de9158870c 100644 --- a/drivers/net/dsa/realtek/rtl8365mb.c +++ b/drivers/net/dsa/realtek/rtl8365mb.c @@ -1540,18 +1540,15 @@ static void rtl8365mb_stats_setup(struct realtek_pr= iv *priv) { struct rtl8365mb *mb =3D priv->chip_data; struct dsa_switch *ds =3D &priv->ds; - int i; + struct dsa_port *dp; =20 /* Per-chip global mutex to protect MIB counter access, since doing * so requires accessing a series of registers in a particular order. */ mutex_init(&mb->mib_lock); =20 - for (i =3D 0; i < priv->num_ports; i++) { - struct rtl8365mb_port *p =3D &mb->ports[i]; - - if (dsa_is_unused_port(ds, i)) - continue; + dsa_switch_for_each_available_port(dp, ds) { + struct rtl8365mb_port *p =3D &mb->ports[dp->index]; =20 /* Per-port spinlock to protect the stats64 data */ spin_lock_init(&p->stats_lock); @@ -1567,13 +1564,10 @@ static void rtl8365mb_stats_teardown(struct realtek= _priv *priv) { struct rtl8365mb *mb =3D priv->chip_data; struct dsa_switch *ds =3D &priv->ds; - int i; - - for (i =3D 0; i < priv->num_ports; i++) { - struct rtl8365mb_port *p =3D &mb->ports[i]; + struct dsa_port *dp; =20 - if (dsa_is_unused_port(ds, i)) - continue; + dsa_switch_for_each_available_port(dp, ds) { + struct rtl8365mb_port *p =3D &mb->ports[dp->index]; =20 cancel_delayed_work_sync(&p->mib_work); } @@ -1632,6 +1626,9 @@ static irqreturn_t rtl8365mb_irq(int irq, void *data) for_each_set_bit(line, &line_changes, priv->num_ports) { int child_irq =3D irq_find_mapping(priv->irqdomain, line); =20 + if (!child_irq) + continue; + handle_nested_irq(child_irq); } =20 @@ -1695,13 +1692,14 @@ static int rtl8365mb_irq_disable(struct realtek_pri= v *priv) static int rtl8365mb_irq_setup(struct realtek_priv *priv) { struct rtl8365mb *mb =3D priv->chip_data; + struct dsa_switch *ds =3D &priv->ds; struct device_node *intc; + struct dsa_port *dp; u32 irq_trig; int virq; int irq; u32 val; int ret; - int i; =20 intc =3D of_get_child_by_name(priv->dev->of_node, "interrupt-controller"); if (!intc) { @@ -1727,8 +1725,8 @@ static int rtl8365mb_irq_setup(struct realtek_priv *p= riv) goto out_put_node; } =20 - for (i =3D 0; i < priv->num_ports; i++) { - virq =3D irq_create_mapping(priv->irqdomain, i); + dsa_switch_for_each_available_port(dp, ds) { + virq =3D irq_create_mapping(priv->irqdomain, dp->index); if (!virq) { dev_err(priv->dev, "failed to create irq domain mapping\n"); @@ -1798,9 +1796,11 @@ static int rtl8365mb_irq_setup(struct realtek_priv *= priv) mb->irq =3D 0; =20 out_remove_irqdomain: - for (i =3D 0; i < priv->num_ports; i++) { - virq =3D irq_find_mapping(priv->irqdomain, i); - irq_dispose_mapping(virq); + dsa_switch_for_each_available_port(dp, ds) { + virq =3D irq_find_mapping(priv->irqdomain, dp->index); + + if (virq) + irq_dispose_mapping(virq); } =20 irq_domain_remove(priv->irqdomain); @@ -1815,8 +1815,9 @@ static int rtl8365mb_irq_setup(struct realtek_priv *p= riv) static void rtl8365mb_irq_teardown(struct realtek_priv *priv) { struct rtl8365mb *mb =3D priv->chip_data; + struct dsa_switch *ds =3D &priv->ds; + struct dsa_port *dp; int virq; - int i; =20 if (mb->irq) { free_irq(mb->irq, priv); @@ -1824,9 +1825,11 @@ static void rtl8365mb_irq_teardown(struct realtek_pr= iv *priv) } =20 if (priv->irqdomain) { - for (i =3D 0; i < priv->num_ports; i++) { - virq =3D irq_find_mapping(priv->irqdomain, i); - irq_dispose_mapping(virq); + dsa_switch_for_each_available_port(dp, ds) { + virq =3D irq_find_mapping(priv->irqdomain, dp->index); + + if (virq) + irq_dispose_mapping(virq); } =20 irq_domain_remove(priv->irqdomain); @@ -1944,10 +1947,11 @@ static int rtl8365mb_setup(struct dsa_switch *ds) { struct realtek_priv *priv =3D ds->priv; struct rtl8365mb_cpu *cpu; - struct dsa_port *cpu_dp; + u32 downports_mask =3D 0; + u32 upports_mask =3D 0; struct rtl8365mb *mb; + struct dsa_port *dp; int ret; - int i; =20 mb =3D priv->chip_data; cpu =3D &mb->cpu; @@ -1974,46 +1978,83 @@ static int rtl8365mb_setup(struct dsa_switch *ds) else if (ret) dev_info(priv->dev, "no interrupt support\n"); =20 - /* Configure CPU tagging */ - dsa_switch_for_each_cpu_port(cpu_dp, ds) { - cpu->mask |=3D BIT(cpu_dp->index); + /* Start with all ports blocked, including unused ports */ + dsa_switch_for_each_port(dp, ds) { + struct rtl8365mb_port *p =3D &mb->ports[dp->index]; =20 - if (cpu->trap_port =3D=3D RTL8365MB_MAX_NUM_PORTS) - cpu->trap_port =3D cpu_dp->index; - } - cpu->enable =3D cpu->mask > 0; - ret =3D rtl8365mb_cpu_config(priv); - if (ret) - goto out_teardown_irq; - - /* Configure ports */ - for (i =3D 0; i < priv->num_ports; i++) { - struct rtl8365mb_port *p =3D &mb->ports[i]; + if (dsa_port_is_dsa(dp)) { + dev_err(ds->dev, "Cascading (DSA links) not supported"); + return -EOPNOTSUPP; + } =20 - if (dsa_is_unused_port(ds, i)) - continue; + /* Set the initial STP state of all ports to DISABLED, otherwise + * ports will still forward frames to the CPU despite being + * administratively down by default. + */ + rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); =20 - /* Forward only to the CPU */ - ret =3D rtl8365mb_port_set_isolation(priv, i, cpu->mask); + /* Start with all port completely isolated */ + ret =3D rtl8365mb_port_set_isolation(priv, dp->index, 0); if (ret) goto out_teardown_irq; =20 /* Disable learning */ - ret =3D rtl8365mb_port_set_learning(priv, i, false); + ret =3D rtl8365mb_port_set_learning(priv, dp->index, false); if (ret) goto out_teardown_irq; =20 - /* Set the initial STP state of all ports to DISABLED, otherwise - * ports will still forward frames to the CPU despite being - * administratively down by default. - */ - rtl8365mb_port_stp_state_set(ds, i, BR_STATE_DISABLED); - /* Set up per-port private data */ p->priv =3D priv; - p->index =3D i; + p->index =3D dp->index; + + /* Collect CPU ports. If we support cascade switches, it should + * also include the upstream DSA ports. + */ + if (!dsa_port_is_cpu(dp)) + continue; + + upports_mask |=3D BIT(dp->index); + } + + /* Configure user ports */ + dsa_switch_for_each_port(dp, ds) { + if (!dsa_port_is_user(dp)) + continue; + + /* Forward only to the CPU */ + ret =3D rtl8365mb_port_set_isolation(priv, dp->index, upports_mask); + if (ret) + goto out_teardown_irq; + + /* If we support cascade switches, it should also include the + * downstream DSA ports. + */ + downports_mask |=3D BIT(dp->index); } =20 + /* Configure CPU tagging */ + /* If we support cascade switches, it should also include the upstream + * DSA ports. + */ + dsa_switch_for_each_cpu_port(dp, ds) { + /* Use the first CPU port as trap_port */ + if (cpu->trap_port =3D=3D RTL8365MB_MAX_NUM_PORTS) + cpu->trap_port =3D dp->index; + + /* Forward to all user ports */ + ret =3D rtl8365mb_port_set_isolation(priv, dp->index, + downports_mask); + if (ret) + goto out_teardown_irq; + } + + cpu->mask =3D upports_mask; + cpu->enable =3D cpu->mask > 0; + + ret =3D rtl8365mb_cpu_config(priv); + if (ret) + goto out_teardown_irq; + ret =3D rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN); if (ret) goto out_teardown_irq; --=20 2.54.0 From nobody Mon May 25 08:11:40 2026 Received: from mail-qv1-f44.google.com (mail-qv1-f44.google.com [209.85.219.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C9C51266581 for ; Sat, 16 May 2026 03:47:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903244; cv=none; b=gM5eumpc0Gzhc0KgoohRoM0pB8Scwtb/tfhTeGuJpbvh0kLlRlW8RpnIwb0tjOFH54NyeBRKw3BOgKiTyzFvbzJ2dtdPJOolrMJsWJBaL3lSlqFcKqIF7suv/INCSXMS8594mUxoeuHdeMt43M6O8K1v5TVi7BUJPRYzYO/hP2s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903244; c=relaxed/simple; bh=bHsLPOXrluehOuKUcFn4n7urdpC0Ou8B1BpSJskP9+U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=otUUoO/ALQHY1u7Dug6c6W9jDORdvW6yACsO8qukfI1Jqajo2qlDzSnIWCGlyCRXx3iKvtNOn1obC9K/WQQ9eKqEGD9fyFNtbjnDOkZ/56TRpMINyB771UQehT2lMfe2scGUrpJkJqgAaoME4IWkTeCyEaPpqQCsGQ3wBNbxrj0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=o0beEe2A; arc=none smtp.client-ip=209.85.219.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="o0beEe2A" Received: by mail-qv1-f44.google.com with SMTP id 6a1803df08f44-8bc3ef10cc4so8691506d6.1 for ; Fri, 15 May 2026 20:47:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778903242; x=1779508042; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=cbJtwuZVN1lk7Yk+XDCZlr7tUa9Ancx4oTQZLyWObYo=; b=o0beEe2A7fXs9Nm6ENNENOZGuK5D04lYQxF9bgj0MZS4TMajS5svXoRZje3MU5DWCZ aH+FUg1gS1bGS+h2dGkfkbb3PQi9pqqxSFV6ZkkvbFSMxiHvt3QmTJIgOBIMB0b7Siif BSmFUAty+LtxJXYwDnGXJbtrYRqJLfd4Up6vMBjf5GqwgpzqcpFGs79oB4mX7/ehCbTp HTI6jQZbKXx3Rm3DQs39Mvs1uMvbhTo7AkoI9WRyiiJC0p90VXrfSSfMkK61kDALrSgV 4haAwmPpM9T/rxjf0SH6Gb5rhgmPF55wyYk9AjMmaIWkhrhny+e/6o3BC18BdleteKao IlDA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778903242; x=1779508042; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=cbJtwuZVN1lk7Yk+XDCZlr7tUa9Ancx4oTQZLyWObYo=; b=OXwaRfUQH6j+AZEqEY29SMwCoW97auTMmIBUODcoMkWWdMEpOy+cJusVgtfgx4b6xP ZchhjaGMEH6Lqx4lFVudHd79kDriHv8dwS1eS4Vfzl8wRD2y/qovOLTG3ePflMQsYIPc tE1Eqp2//Ti3I4uClmF6L64BReSxCmNqmhDk+bxoHKFT6uCHTvrieYzstfrjqAbZjDw1 yezyz4jNg07Ep3vpXd12nuezqrqWAXQKLIl5vv4AZPH1WZzbz9uAWFD/H8K6DKKjDtIt S64qkoK2hclqK64CvpPtHpn2uZdMLhBc6JYIcYUraD9JsqTFF9i2EM0R5yLtrE3E0vw5 IjsA== X-Forwarded-Encrypted: i=1; AFNElJ9029hVnaeYS8Fmas8lbsxTy19P1tg2qHLZyrLWFUfYJ334GoulkeQfKVWxbezMI9aCXRlaVx5NL98dOdA=@vger.kernel.org X-Gm-Message-State: AOJu0Yyp6qh4uolrfGXXYKda+GZM/sz3MmkfmpPT8x5vj0BgKq+9PfSC DwbbMbK5S1FvmAk4r2gycjZCqj3RhlzmhHo0PS7gBib76WCPpiby1Wef X-Gm-Gg: Acq92OEjkhkHOrlBH/BbzYanvmfBPZx3QzcQgSTi4ZMPsCj6LeZpdhgrRiGA8zSRULZ TjvUqC4yNSnACdO21v5a84cxrDr9bTDYEXO5Jwtg6VB6GoB3j4H3CH7O16AhQ7l37BkyoWnC0BT 5xk3kejK5lGDD8PQMd2Qi3kaSLzQPFt3upkrQ8/hwZiHGTVGAylV/4W7HCQ4FW7ZZFkHzfxpi5/ 2El2zdWcWxkUCu5LwjaXanpHxnlMywiEWoZktyxIqKH+dPiBsHy/du7qy0ZuDeIkOkP6kuKxwbh xzDN1x0JYQYxyweDdkHXeb6KSvqYOFGxhJxnRBXi1VI5MdmX3Mh9ik9I5KoUOTDFxXPPLf+kmnL eb8hyOjwxGnYdritGkEFBAduIuq6baMs+/zwv7hwL2sRTuH0kUfkiUmkNW9TaFyUpQjoz0O3/oZ J2yJdjYisZifpEvE1fpSweY6+60GgtbPCmqK4iq9ngTmaW6r7DBw== X-Received: by 2002:ad4:4027:0:b0:8ae:5fcc:ebb5 with SMTP id 6a1803df08f44-8ca0f6d9122mr87816096d6.47.1778903241851; Fri, 15 May 2026 20:47:21 -0700 (PDT) Received: from tresc054937.tre-sc.gov.br ([187.65.210.13]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c908d1d2e7sm70874546d6.16.2026.05.15.20.47.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 20:47:21 -0700 (PDT) From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 00:46:19 -0300 Subject: [net-next PATCH v4 3/8] net: dsa: realtek: rtl8365mb: prepare for multiple source files Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260516-realtek_forward-v4-3-8b6d6a1eefdc@gmail.com> References: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> In-Reply-To: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> To: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Linus Walleij , =?utf-8?q?Alvin_=C5=A0ipraga?= , Yury Norov , Rasmus Villemoes , Russell King Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Mieczyslaw Nalewaj , Luiz Angelo Daros de Luca X-Mailer: b4 0.15.2 From: Alvin =C5=A0ipraga Rename rtl8365mb.c to rtl8365mb_main.c in preparation for subsequent commits which add additional source files to the driver. The trailing backslash in the Makefile is deliberate. It allows for new files to be added without clobbering git history. Co-developed-by: Alvin =C5=A0ipraga Signed-off-by: Alvin =C5=A0ipraga Reviewed-by: Linus Walleij Reviewed-by: Mieczyslaw Nalewaj Signed-off-by: Luiz Angelo Daros de Luca --- drivers/net/dsa/realtek/Makefile | 1 + drivers/net/dsa/realtek/{rtl8365mb.c =3D> rtl8365mb_main.c} | 0 2 files changed, 1 insertion(+) diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Mak= efile index 17367bcba496..3f986e04912f 100644 --- a/drivers/net/dsa/realtek/Makefile +++ b/drivers/net/dsa/realtek/Makefile @@ -16,3 +16,4 @@ ifdef CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS rtl8366-objs +=3D rtl8366rb-leds.o endif obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) +=3D rtl8365mb.o +rtl8365mb-objs :=3D rtl8365mb_main.o \ diff --git a/drivers/net/dsa/realtek/rtl8365mb.c b/drivers/net/dsa/realtek/= rtl8365mb_main.c similarity index 100% rename from drivers/net/dsa/realtek/rtl8365mb.c rename to drivers/net/dsa/realtek/rtl8365mb_main.c --=20 2.54.0 From nobody Mon May 25 08:11:40 2026 Received: from mail-qv1-f48.google.com (mail-qv1-f48.google.com [209.85.219.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8B73628A3FA for ; Sat, 16 May 2026 03:47:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903251; cv=none; b=BuKGn84Ocn93psL6k7qki7Lj8XQ3l6ihYlv0/KzW/lc2s8cY6PKBPrVAestDQ4BC4IsbBG2DJU2WQywLqN59Zm1I3xoZn5/E5p1bA7hYPDXX5pQAXnCi093Hz2PJVO6gcVf840PD0cI0uy//3NTItY50SfBHAA3o09BVK9O43zc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903251; c=relaxed/simple; bh=5mXi0QnLwvtmOOBRk6jeJvpBCQ/2EDrdaUjT2QhRi2A=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=X/8uIPykKWooVljseibQ0cFzkaTVvVBRY3G/vDPOUkqzItFGuxHS3LMaIg7NM0LBItHV7+FlGGSWIua/ZVmcV7rxlqon4XWFwwYLtdBqOLz/A48qPydM4LduVtZ+Dy9LT0dq9tNQetTLvDwWYkRaW/00O0e0gKjmdPVghZFOE1E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=AMVMPivi; arc=none smtp.client-ip=209.85.219.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="AMVMPivi" Received: by mail-qv1-f48.google.com with SMTP id 6a1803df08f44-8b7105dfb35so8720076d6.3 for ; Fri, 15 May 2026 20:47:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778903248; x=1779508048; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=DX9fAWgItNtEquVYu0mvpftc3qnM9bDW8Gg0bvb8u9M=; b=AMVMPivi6YvfU3BbK6rvYkOu3Lxb1bJ4tiSRuYImkA/ESrzCTCqVUJHVldpGNMx7TT Om39ExifO4YNeqYAqP73y+/d+vNRUW4+802yHb3ZkMtStxt1ak1g80KrB716hSYUuyUc 7JpnK8jLUuG4pLLjMPto7rVlerdfkOtj4UlxgT3mlQ0hr56ylCCebz0pKnuVW2MN4+vd 4Ej+pS+bykYY/v4XqWiv71wWM7ueaTG5BPyVelaueYJO/04hLOPlLnebmSHUM3vtxqDx gr4OmlCuX+H4Uh5V7GpKc/0uqbNcB1BtDfki7sAZ5WdD0Rvz9Ky3WoQmsc+M9osiObLu MyNg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778903248; x=1779508048; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=DX9fAWgItNtEquVYu0mvpftc3qnM9bDW8Gg0bvb8u9M=; b=lyiLJoLCEu1gMRyUggDZv7ZEAXCmeW2s5Upt+kTJSPNtBvwZs/TQIEEsuf6nE/9qjR EAH/RNOKi8pGZdmZDM2cFQneKQNhrJgbwcOaCBwVw3rnX1v5i1+cozxXhwYpErLO73mD SMpZFE5VciHxCZ/mBwD8eHXDrpNuaiF1TSS+iFmtXE0eFYtWuX1gDFUypTuTBCv0GWkK zvu7If+ygAx8//0Qme9GufKhxUSxWqLdl3A3lTOmKXGYrdi8i/QxtvGmFX6CRU7SxnyC gLvygqOaqjr73Ii0eZmooA0Sy7wJjzMkXbJLY7ube1urgwOk7jIa40iwmpEu6/6Wyde6 BJ2w== X-Forwarded-Encrypted: i=1; AFNElJ+/tuwF1BS8aMrqpJBIfit22fn0rOR4YqFyJQnCiZb/RrNgAiQtQopX2epnwQ6aSeOPHfnmkiO2QlsXXIY=@vger.kernel.org X-Gm-Message-State: AOJu0Yx7GI83mmU7OJBrkNPTqX65np+df4JKJQPgb3GS8YNot+gkyL3u pZ+rBIVodrQbAK8SdKvxIH5x1rSFCd0x/W6N2WC5LyGHltHMrAYRU8gr X-Gm-Gg: Acq92OEBF1Gy1F9VELDzfx8udmVPkgwDyfw6DosewbAFrYLFMFEq2zbqymeN6ia5F5c r7kgx02yg83moi738EJ5o3jbsY/2bUDyjU7tIEzwJb2xuphFg3xBFDnAy1Sbw5epTYUy+GngoVf fFoOsmPC3vGbApg3ixbw6xy/n9YFX6Yr3a8VHZSm7XaJHz70yEkwCFAygcawFE1AcB5xP5yI8YO 52KAJij8sj3DRVs9lfpPxrAMldqlmGaqCR8hGOW5vvsBzN7X/4F/BEZtLJ/m5BSuD+RSiGMeDOn CdniytsBZZVApvQYfe2OkIKSwOh1s08bRxNObJtCmLraUSmiV/DBOO4yELEt/kYbsixINz0W06g dNmNHEiVT7dNvVZwk2k4dnL3tQy/eBqWPX6qPKU4/KSyjLMh8/qmVz2JfF8VvZ5anayY75FZg3r UxUi8PES7EsFnTTtpGa5rr0aBIgjxsXN3lr434HFfJJoO9mC9aFw== X-Received: by 2002:a05:6214:4ec3:b0:8ca:f1c:6493 with SMTP id 6a1803df08f44-8ca0f631cb4mr109134476d6.1.1778903248471; Fri, 15 May 2026 20:47:28 -0700 (PDT) Received: from tresc054937.tre-sc.gov.br ([187.65.210.13]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c908d1d2e7sm70874546d6.16.2026.05.15.20.47.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 20:47:27 -0700 (PDT) From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 00:46:20 -0300 Subject: [net-next PATCH v4 4/8] net: dsa: realtek: rtl8365mb: add table lookup interface Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260516-realtek_forward-v4-4-8b6d6a1eefdc@gmail.com> References: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> In-Reply-To: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> To: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Linus Walleij , =?utf-8?q?Alvin_=C5=A0ipraga?= , Yury Norov , Rasmus Villemoes , Russell King Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Luiz Angelo Daros de Luca X-Mailer: b4 0.15.2 From: Alvin =C5=A0ipraga Add a generic table lookup interface to centralize access to the RTL8365MB internal tables. This interface abstracts the low-level table access logic and will be used by subsequent commits to implement FDB and VLAN operations. Co-developed-by: Alvin =C5=A0ipraga Signed-off-by: Alvin =C5=A0ipraga Reviewed-by: Linus Walleij Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/Makefile | 1 + drivers/net/dsa/realtek/rtl8365mb_table.c | 216 ++++++++++++++++++++++++++= ++++ drivers/net/dsa/realtek/rtl8365mb_table.h | 132 ++++++++++++++++++ 3 files changed, 349 insertions(+) diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Mak= efile index 3f986e04912f..99654c4c5a3d 100644 --- a/drivers/net/dsa/realtek/Makefile +++ b/drivers/net/dsa/realtek/Makefile @@ -17,3 +17,4 @@ rtl8366-objs +=3D rtl8366rb-leds.o endif obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) +=3D rtl8365mb.o rtl8365mb-objs :=3D rtl8365mb_main.o \ + rtl8365mb_table.o \ diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.c b/drivers/net/dsa/re= altek/rtl8365mb_table.c new file mode 100644 index 000000000000..01ce5e159b1c --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_table.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Look-up table query interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin =C5=A0ipraga + */ + +#include "rtl8365mb_table.h" +#include + +/* Table access control register */ +#define RTL8365MB_TABLE_CTRL_REG 0x0500 +/* Should be one of rtl8365mb_table enum members */ +#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0) +/* Should be one of rtl8365mb_table_op enum members */ +#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3) +/* Should be one of rtl8365mb_table_l2_method enum members */ +#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(7, 4) +/* NOTE: PORT_MASK is only 4 bit, which suggests that port-based + * look-up of the L2 table only works for physical port addresses + * 0~4. It could be that the Realtek driver is out-of-date and + * actually the mask is something like 0xFF00, but this is + * unconfirmed. + */ +#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8) + +/* Table access address register */ +#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501 +#define RTL8365MB_TABLE_ADDR_MASK GENMASK(13, 0) + +/* Table status register */ +#define RTL8365MB_TABLE_STATUS_REG 0x0502 +#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0) +/* set for L3, unset for L2 */ +#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11) +#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12) +#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13) +#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14) + +/* Table read/write registers */ +#define RTL8365MB_TABLE_WRITE_BASE 0x0510 +#define RTL8365MB_TABLE_WRITE_REG(_x) \ + (RTL8365MB_TABLE_WRITE_BASE + (_x)) +#define RTL8365MB_TABLE_READ_BASE 0x0520 +#define RTL8365MB_TABLE_READ_REG(_x) \ + (RTL8365MB_TABLE_READ_BASE + (_x)) +#define RTL8365MB_TABLE_ENTRY_MAX_SIZE 10 +#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0) +#define RTL8365MB_TABLE_WRITE_10TH_REG \ + RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1) + +static int rtl8365mb_table_poll_busy(struct realtek_priv *priv) +{ + u32 val; + + return regmap_read_poll_timeout(priv->map_nolock, + RTL8365MB_TABLE_STATUS_REG, val, + !FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val), + 10, 100); +} + +int rtl8365mb_table_query(struct realtek_priv *priv, + enum rtl8365mb_table table, + enum rtl8365mb_table_op op, u16 *addr, + enum rtl8365mb_table_l2_method method, + u16 port, u16 *data, size_t size) +{ + bool addr_as_input =3D true; + bool write_data =3D false; + int ret =3D 0; + u32 cmd; + u32 val; + u32 hit; + + /* Prepare target table and operation (read or write) */ + cmd =3D 0; + cmd |=3D FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table); + cmd |=3D FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op); + if (op =3D=3D RTL8365MB_TABLE_OP_READ && table =3D=3D RTL8365MB_TABLE_L2)= { + cmd |=3D FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method); + switch (method) { + case RTL8365MB_TABLE_L2_METHOD_MAC: + /* + * Method MAC requires as input the same L2 table format + * you'll get as result. However, it might only use mac + * address and FID/VID fields. + */ + write_data =3D true; + + /* METHOD_MAC does not use addr as input, but may return + * the matched index. + */ + addr_as_input =3D false; + + break; + case RTL8365MB_TABLE_L2_METHOD_ADDR: + case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: + case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: + case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: + break; + case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: + cmd |=3D FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port); + break; + default: + return -EINVAL; + } + } else if (op =3D=3D RTL8365MB_TABLE_OP_WRITE) { + write_data =3D true; + + /* Writing to L2 does not use addr as input, as the table index + * is derived from key fields. + */ + if (table =3D=3D RTL8365MB_TABLE_L2) + addr_as_input =3D false; + } + + /* To prevent concurrent access to the look-up tables, take the regmap + * lock manually and access via the map_nolock regmap. + */ + mutex_lock(&priv->map_lock); + + /* Write entry data if writing to the table (or L2_METHOD_MAC) */ + if (write_data) { + /* bulk write data up to 9th word */ + ret =3D regmap_bulk_write(priv->map_nolock, + RTL8365MB_TABLE_WRITE_BASE, + data, + min_t(size_t, size, + RTL8365MB_TABLE_ENTRY_MAX_SIZE - + 1)); + if (ret) + goto out; + + /* 10th register uses only 4 least significant bits */ + if (size =3D=3D RTL8365MB_TABLE_ENTRY_MAX_SIZE) { + val =3D FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK, + data[size - 1]); + ret =3D regmap_update_bits(priv->map_nolock, + RTL8365MB_TABLE_WRITE_10TH_REG, + RTL8365MB_TABLE_10TH_DATA_MASK, + val); + } + + if (ret) + goto out; + } + + /* Write address (if needed) */ + if (addr_as_input) { + ret =3D regmap_write(priv->map_nolock, + RTL8365MB_TABLE_ACCESS_ADDR_REG, + FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, + *addr)); + if (ret) + goto out; + } + + /* Execute */ + ret =3D regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd); + if (ret) + goto out; + + /* Poll for completion */ + ret =3D rtl8365mb_table_poll_busy(priv); + if (ret) + goto out; + + /* For both reads and writes to the L2 table, check status */ + if (table =3D=3D RTL8365MB_TABLE_L2) { + ret =3D regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG, + &val); + if (ret) + goto out; + + /* Did the query find an entry? */ + hit =3D FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val); + if (!hit) { + ret =3D -ENOENT; + goto out; + } + + /* If so, extract the address */ + *addr =3D 0; + *addr |=3D FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val); + *addr |=3D FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val) + << 11; + /* only set if it is a L3 address */ + *addr |=3D FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val) + << 12; + } + + /* Finally, get the table entry if we were reading */ + if (op =3D=3D RTL8365MB_TABLE_OP_READ) { + ret =3D regmap_bulk_read(priv->map_nolock, + RTL8365MB_TABLE_READ_BASE, + data, size); + if (ret) + goto out; + + /* For the biggest table entries, the uppermost table + * entry register has space for only one nibble. Mask + * out the remainder bits. Empirically I saw nothing + * wrong with omitting this mask, but it may prevent + * unwanted behaviour. FYI. + */ + if (size =3D=3D RTL8365MB_TABLE_ENTRY_MAX_SIZE) { + val =3D FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK, + data[size - 1]); + data[size - 1] =3D val; + } + } + +out: + mutex_unlock(&priv->map_lock); + + return ret; +} diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.h b/drivers/net/dsa/re= altek/rtl8365mb_table.h new file mode 100644 index 000000000000..8df8ba9ce0fe --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_table.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Look-up table query interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin =C5=A0ipraga + */ + +#ifndef _REALTEK_RTL8365MB_TABLE_H +#define _REALTEK_RTL8365MB_TABLE_H + +#include +#include + +#include "realtek.h" + +/** + * enum rtl8365mb_table - available switch tables + * @RTL8365MB_TABLE_ACL_RULE: ACL rules + * @RTL8365MB_TABLE_ACL_ACTION: ACL actions + * @RTL8365MB_TABLE_CVLAN: VLAN4k configurations + * @RTL8365MB_TABLE_L2: filtering database (2K hash table) + * @RTL8365MB_TABLE_IGMP_GROUP: IGMP group database (readonly) + * + * NOTE: Don't change the enum values. They must concur with the field + * described by @RTL8365MB_TABLE_CTRL_TABLE_MASK. + */ +enum rtl8365mb_table { + RTL8365MB_TABLE_ACL_RULE =3D 1, + RTL8365MB_TABLE_ACL_ACTION =3D 2, + RTL8365MB_TABLE_CVLAN =3D 3, + RTL8365MB_TABLE_L2 =3D 4, + RTL8365MB_TABLE_IGMP_GROUP =3D 5, +}; + +/** + * enum rtl8365mb_table_op - table query operation + * @RTL8365MB_TABLE_OP_READ: read an entry from the target table + * @RTL8365MB_TABLE_OP_WRITE: write an entry to the target table + * + * NOTE: Don't change the enum values. They must concur with the field + * described by @RTL8365MB_TABLE_CTRL_OP_MASK. + */ +enum rtl8365mb_table_op { + RTL8365MB_TABLE_OP_READ =3D 0, + RTL8365MB_TABLE_OP_WRITE =3D 1, +}; + +/** + * enum rtl8365mb_table_l2_method - look-up method for read queries of L2 = table + * @RTL8365MB_TABLE_L2_METHOD_MAC: look-up by source MAC address and FID (= or + * VID) + * @RTL8365MB_TABLE_L2_METHOD_ADDR: look-up by entry address + * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: look-up next entry starting from = the + * supplied address + * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: same as ADDR_NEXT but search o= nly + * unicast addresses + * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: same as ADDR_NEXT but search o= nly + * multicast addresses + * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: same as ADDR_NEXT_UC but + * search only entries with matching source port + * + * NOTE: Don't change the enum values. They must concur with the field + * described by @RTL8365MB_TABLE_CTRL_METHOD_MASK + */ +enum rtl8365mb_table_l2_method { + RTL8365MB_TABLE_L2_METHOD_MAC =3D 0, + RTL8365MB_TABLE_L2_METHOD_ADDR =3D 1, + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT =3D 2, + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC =3D 3, + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC =3D 4, + /* + * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L3 =3D 5, + * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L2L3 =3D 6, + */ + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT =3D 7, +}; + +/** + * rtl8365mb_table_query() - read from or write to a switch table + * @priv: driver context + * @table: target table, see &enum rtl8365mb_table + * @op: read or write operation, see &enum rtl8365mb_table_op + * @addr: table address. For indexed tables, this selects the entry to acc= ess. + * For L2 read queries, it is ignored as input for MAC-based lookup + * methods and used as input for address-based lookup methods. On + * successful L2 queries, it is updated with the matched entry addr= ess. + * @method: L2 table lookup method, see &enum rtl8365mb_table_l2_method. + * Ignored for non-L2 tables. + * @port: for L2 read queries using method + * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, restrict the search + * to entries associated with this source port. Ignored otherwise. + * @data: data buffer used to read from or write to the table. For L2 MAC + * lookups, this buffer provides the lookup key and receives the + * matched entry contents on success. + * @size: size of @data in 16-bit words. The caller must ensure that @size + * matches the target table's entry size and does not exceed + * RTL8365MB_TABLE_ENTRY_MAX_SIZE. + * + * This function provides unified access to the internal tables of the swi= tch. + * All tables except the L2 table are simple indexed tables, where @addr + * selects the entry and @op determines whether the access is a read or a + * write operation. + * + * The L2 table is a hash table and supports multiple lookup methods. For + * %RTL8365MB_TABLE_L2_METHOD_MAC, an entry is searched based on the MAC + * address and FID/VID fields provided in @data, using the same format as + * an L2 table entry. Address-based methods either read a specific entry + * (%RTL8365MB_TABLE_L2_METHOD_ADDR) or iterate over valid entries starting + * from @addr (%RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT and variants). When us= ing + * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, only entries associated w= ith + * the specified @port are considered. + * + * On successful L2 lookups, @addr is updated with the matched table addre= ss + * and @data contains the corresponding table entry. If no matching entry + * is found, -ENOENT is returned. + * + * The contents of @data are used as input when writing to tables or when + * specifying the lookup key for L2 MAC searches, and as output for all + * successful read operations. If an error occurs, the contents of @addr + * and @data are undefined. + * + * @size must match the size of the target table entry, expressed in 16-bit + * words. + * + * Return: 0 on success, or a negative error code on failure. + */ +int rtl8365mb_table_query(struct realtek_priv *priv, + enum rtl8365mb_table table, + enum rtl8365mb_table_op op, u16 *addr, + enum rtl8365mb_table_l2_method method, + u16 port, u16 *data, size_t size); + +#endif /* _REALTEK_RTL8365MB_TABLE_H */ --=20 2.54.0 From nobody Mon May 25 08:11:40 2026 Received: from mail-qk1-f179.google.com (mail-qk1-f179.google.com [209.85.222.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D290428726E for ; Sat, 16 May 2026 03:47:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903259; cv=none; b=Tczlp38Z9XcQfdkMJ1bMSQQy6y8ukvP2+TU/Pqfxfu9ktE8hR2bR26Rkaim90nOE2M3nIcYjVdy4uQRwr69n30DbN1qp3vChikPr8sZst1l1XVLV0w1l3cpVRjFDKObK4My5Ou7PqVng6OaRNe899qhwE96DsP8vibbpReFPZxs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903259; c=relaxed/simple; bh=d3e6yoM6MbyWADLEOBeC2nUM5NL9BWHRb+xSPbkpruU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nXYSDMFMCOEzzb09/y8jwDfxXZ/vKotaDvuwKja7gHe6vAe0gK8efRqeF2V7KGEzwp831NqRMm9XjLyBYCaKh/l6HUdDdN2ZMzle/KBNsn8mV8AybjPyErQX0/PajAv8FztY80a+gA0ePuvt19iiOHUVjxu5QBYCKCQd9hYpRzM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=O+Hi8/1n; arc=none smtp.client-ip=209.85.222.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="O+Hi8/1n" Received: by mail-qk1-f179.google.com with SMTP id af79cd13be357-9125d2a4d36so36660485a.3 for ; Fri, 15 May 2026 20:47:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778903256; x=1779508056; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=LVDbaW7fpz3Plo272XK0gem4BI3EHahy9a0Dqz3Jv1k=; b=O+Hi8/1nxP6aPtY6h5DF0wkEozwgPG5RtES7vpAWu3qtSD9dB7tam6K5XoodQ+aVts QZtmJPRG4oM2K8FWz7D4kwizerJkLHCHpVWeaEi9cYRzA1kAfA7ETIzwCUwNlgSM4aKG 7W+bsPct968DNQCIFB0CWFYmh3c8jJsyFTcxt1o9JkBwcPosVuLAZwPjEYnoxMoRql7x dAsd8jOmmv2JY5ZLiZBk+Weyuq2smd5OjHrukLKIwllHpQSx1NXfEJQdWljRLVo2/fXs 0u2Z4QLo8hb+YIjwo17NgU3pbYE1ctLFE4kpkiKoJc0InzJ1PrRq/05Pv055NEOmaI+R g5nQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778903256; x=1779508056; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=LVDbaW7fpz3Plo272XK0gem4BI3EHahy9a0Dqz3Jv1k=; b=aKi4GkQje09nXkEr2ljwPB0w4mSgWRSOaGzgE8FAieyzrJ7LbIinuwClAb5vETngYd 5tve61WcBVNaUIzrk/dDiG3hfz2R9z0SP54jq13HtWdaXlVpUv4Jg9tHXFRuSgkmwNNe WteN5rK5PMIZzTeY4R5JaFt7eFVl1LIZfP2Md/2sc1qh8iM0t5uzMyQXXh1rGt4TQSyf 3HVfVmxboE04xdTPMoxGrY7Ek8+W3LG6/wHAYXyWeXV6zdQIS9V1VJYMiQgQci4rODjH 3vBtzZOrQPCaFOrMN3vI6hDG3b5VYDPRE6RnSs7rDyfrA7dEFddybrwY7LfESPe94uOZ A+nw== X-Forwarded-Encrypted: i=1; AFNElJ/Don4qKm56P9tCwts5MQnctgYN3YsJbp2HsNfZWh3tM2Y8SAA/y5uNxNN8xk7YP+B5IuKE8O/sMtVQDAg=@vger.kernel.org X-Gm-Message-State: AOJu0YzJ5MD2cuQQ4jXrq/PglEU5PVCOaMxSxCo4BN21tjJYIw++nV0o Y6I5YAwdiBvUpF0KdGXFUPfDWJV8owItJq/uv9WcIT88Ot289PbUtRFz X-Gm-Gg: Acq92OGQ6bR+gcEzG8HjQqM31rXBTJZVYOO365gV3esLfYPSa/6yK3oCyxRCosZ0ge6 HHhDxUcjBOiGKZyJWIDtvNAVYJAqBNDldGwwyLv3UnhxmhmGBuFIdAglaoToZXH7tX2f19OgN+s wQ1K0a4Xr9x5LeZ3DCP79/k0Y166whyxZuZ4HOqSi3I9LbeBI6Eauv0pfPwp72dw7vawOXpV1U3 V+JFlElI4bHbauy8W5yLzlfS6c+hhn7zAObSxbtW+W3l5Fr+mXwKPNJ1R6DZ1js8mC11Y13sOtB 2Kz2fy6mB5sm5kyO68uLTYoZsY2t/aBTwRoLWBWFr5L4fFcT6/HCJR9R3jwNt+3Dpds/4ggG1gm uJvyXMdApLpK7OLJVs8b0/7hWQyNIu0nFDRiXHLrx6QLM6ykxndpStxbIMw+ZV3h+ShynWW7zyP 4PDjwtmemJHO4PU8DyCwMpAFcQ5fJ2g+XM71KAWHI= X-Received: by 2002:a05:620a:1792:b0:90d:e802:79e5 with SMTP id af79cd13be357-911d02ad42bmr1121951585a.57.1778903255437; Fri, 15 May 2026 20:47:35 -0700 (PDT) Received: from tresc054937.tre-sc.gov.br ([187.65.210.13]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c908d1d2e7sm70874546d6.16.2026.05.15.20.47.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 20:47:34 -0700 (PDT) From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 00:46:21 -0300 Subject: [net-next PATCH v4 5/8] net: dsa: realtek: rtl8365mb: add VLAN support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260516-realtek_forward-v4-5-8b6d6a1eefdc@gmail.com> References: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> In-Reply-To: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> To: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Linus Walleij , =?utf-8?q?Alvin_=C5=A0ipraga?= , Yury Norov , Rasmus Villemoes , Russell King Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Yury Norov , Abdulkader Alrezej , Luiz Angelo Daros de Luca X-Mailer: b4 0.15.2 From: Alvin =C5=A0ipraga Realtek RTL8365MB switches (a.k.a. RTL8367C family) use two different structures for VLANs: - VLAN4K: A full table with 4096 entries defining port membership and tagging. - VLANMC: A smaller table with 32 entries used primarily for PVID assignment. In this hardware, a port's PVID must point to an index in the VLANMC table rather than a VID directly. Since the VLANMC table is limited to 32 entries, the driver implements a dynamic allocation scheme to maximize resource usage: - VLAN4K is treated by the driver as the source of truth for membership. - A VLANMC entry is only allocated when a port is configured to use a specific VID as its PVID. - VLANMC entries are deleted when no longer needed as a PVID by any port. Although VLANMC has a members field, the switch only checks membership in the VLAN4K table. However, when a corresponding VLAN entry also exists in VLANMC, this driver keeps both membership configurations in sync. VLANMC index 0, although a valid entry, is reserved in this driver as a neutral PVID value for ports not using a specific PVID. In the subsequent RTL8367D switch family, VLANMC table was removed and PVID assignment was delegated to a dedicated set of registers. All ports start isolated, forwarding exclusively to CPU ports, and with VLAN transparent, ignoring VLAN membership. Once a member in a bridge, the port isolation is expanded to include the bridge members. When that bridge enables VLAN filtering, the VLAN transparent feature is disabled, letting the switch filter based on VLAN setup. The use of FIELD_PREP for reconstructing LO/HI values was suggested by Yury Norov. Fix for vlan_setup and vlan_filtering was suggested by Abdulkader Alrezej. Suggested-by: Yury Norov Suggested-by: Abdulkader Alrezej Co-developed-by: Alvin =C5=A0ipraga Signed-off-by: Alvin =C5=A0ipraga Reviewed-by: Linus Walleij Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/Makefile | 1 + drivers/net/dsa/realtek/rtl8365mb_main.c | 261 ++++++++++ drivers/net/dsa/realtek/rtl8365mb_vlan.c | 813 +++++++++++++++++++++++++++= ++++ drivers/net/dsa/realtek/rtl8365mb_vlan.h | 25 + 4 files changed, 1100 insertions(+) diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Mak= efile index 99654c4c5a3d..b7fc4e852fd8 100644 --- a/drivers/net/dsa/realtek/Makefile +++ b/drivers/net/dsa/realtek/Makefile @@ -18,3 +18,4 @@ endif obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) +=3D rtl8365mb.o rtl8365mb-objs :=3D rtl8365mb_main.o \ rtl8365mb_table.o \ + rtl8365mb_vlan.o \ diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/rea= ltek/rtl8365mb_main.c index d1de9158870c..90c3001d31f2 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -104,6 +104,7 @@ #include "realtek-smi.h" #include "realtek-mdio.h" #include "rtl83xx.h" +#include "rtl8365mb_vlan.h" =20 /* Family-specific data and limits */ #define RTL8365MB_PHYADDRMAX 7 @@ -292,6 +293,67 @@ #define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) =20 +/* Miscellaneous port configuration register, incl. VLAN egress mode */ +#define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E +#define RTL8365MB_PORT_MISC_CFG_REG(_p) \ + (RTL8365MB_PORT_MISC_CFG_REG_BASE + ((_p) << 5)) +#define RTL8365MB_PORT_MISC_CFG_SMALL_TAG_IPG_MASK 0x8000 +#define RTL8365MB_PORT_MISC_CFG_TX_ITFSP_MODE_MASK 0x4000 +#define RTL8365MB_PORT_MISC_CFG_FLOWCTRL_INDEP_MASK 0x2000 +#define RTL8365MB_PORT_MISC_CFG_DOT1Q_REMARK_ENABLE_MASK 0x1000 +#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_FLOWCTRL_MASK 0x0800 +#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_IFG_MASK 0x0400 +#define RTL8365MB_PORT_MISC_CFG_RX_SPC_MASK 0x0200 +#define RTL8365MB_PORT_MISC_CFG_CRC_SKIP_MASK 0x0100 +#define RTL8365MB_PORT_MISC_CFG_PKTGEN_TX_FIRST_MASK 0x0080 +#define RTL8365MB_PORT_MISC_CFG_MAC_LOOPBACK_MASK 0x0040 +/* See &rtl8365mb_vlan_egress_mode */ +#define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK 0x0030 +#define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK 0x000F + +/** + * enum rtl8365mb_vlan_egress_mode - port VLAN egress mode + * @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table= entry + * @RTL8365MB_VLAN_EGRESS_MODE_KEEP: the VLAN tag format of egressed packe= ts + * will remain the same as their ingressed format, but the priority and VID + * fields may be altered + * @RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG: always egress with priority tag + * @RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP: the VLAN tag format of egressed + * packets will remain the same as their ingressed format, and neither the + * priority nor VID fields can be altered + */ +enum rtl8365mb_vlan_egress_mode { + RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL =3D 0, + RTL8365MB_VLAN_EGRESS_MODE_KEEP =3D 1, + RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG =3D 2, + RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP =3D 3, +}; + +/* VLAN control register */ +#define RTL8365MB_VLAN_CTRL_REG 0x07A8 +#define RTL8365MB_VLAN_CTRL_EN_MASK 0x0001 + +/* VLAN ingress filter register */ +#define RTL8365MB_VLAN_INGRESS_REG 0x07A9 +#define RTL8365MB_VLAN_INGRESS_MASK GENMASK(10, 0) +#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_OFFSET(_p) (_p) +#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(_p) BIT(_p) + +/* VLAN "transparent" setting registers */ +#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE 0x09D0 +#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(_p) \ + (RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE + (_p)) + +/* Frame type filtering registers */ +#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa +#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \ + (RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE + ((port) >> 3)) +/* required as FIELD_PREP cannot use non-constant masks */ +#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port) \ + (0x3 << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port)) +#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port) \ + (((port) & 0x7) << 1) + /* MIB counter value registers */ #define RTL8365MB_MIB_COUNTER_BASE 0x1000 #define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x)) @@ -1196,6 +1258,195 @@ static void rtl8365mb_port_stp_state_set(struct dsa= _switch *ds, int port, val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port)); } =20 +static int rtl8365mb_port_set_transparent(struct realtek_priv *priv, + int igr_port, int egr_port, + bool enable) +{ + dev_dbg(priv->dev, "%s transparent VLAN from %d to %d\n", + enable ? "Enable" : "Disable", igr_port, egr_port); + + /* "Transparent" between the two ports means that packets forwarded by + * igr_port and egressed on egr_port will not be filtered by the usual + * VLAN membership settings. + */ + return regmap_update_bits(priv->map, + RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(egr_port), + BIT(igr_port), enable ? BIT(igr_port) : 0); +} + +static int rtl8365mb_port_set_ingress_filtering(struct realtek_priv *priv, + int port, bool enable) +{ + /* Ingress filtering enabled: Discard VLAN-tagged frames if the port is + * not a member of the VLAN with which the packet is associated. + * Untagged packets will also be discarded unless the port has a PVID + * programmed. Priority-tagged frames are treated as untagged frames. + * + * Ingress filtering disabled: Accept all tagged and untagged frames. + */ + return regmap_update_bits(priv->map, RTL8365MB_VLAN_INGRESS_REG, + RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port), + enable ? + RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port) : + 0); +} + +static int +rtl8365mb_port_set_vlan_egress_mode(struct realtek_priv *priv, int port, + enum rtl8365mb_vlan_egress_mode mode) +{ + u32 val; + + val =3D FIELD_PREP(RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, mode); + return regmap_update_bits(priv->map, + RTL8365MB_PORT_MISC_CFG_REG(port), + RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, val); +} + +static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port, + bool vlan_filtering, + struct netlink_ext_ack *extack) +{ + enum rtl8365mb_vlan_egress_mode mode; + struct realtek_priv *priv =3D ds->priv; + struct dsa_port *dp; + int ret; + + dev_dbg(priv->dev, "port %d: %s VLAN filtering\n", port, + vlan_filtering ? "enable" : "disable"); + + /* When vlan filter is enable/disabled in a bridge, this function is + * called for all member ports. We need to enable/disable ingress + * VLAN membership check. + */ + ret =3D rtl8365mb_port_set_ingress_filtering(priv, port, vlan_filtering); + if (ret) + return ret; + + /* However, we also enable/disable egress filtering because the switch + * still consider the egress interface VLAN membership to forward the + * traffic. We enable/disable that check disabling/enabling transparent + * VLAN between the ingress port and all other available ports. + */ + dsa_switch_for_each_available_port(dp, ds) { + /* port isolation will still keep traffic inside the bridge */ + ret =3D rtl8365mb_port_set_transparent(priv, port, dp->index, + !vlan_filtering); + if (ret) + goto undo_transparent; + } + + /* When VLAN filtering is disabled, preserve frames exactly as received. + * Otherwise, the VLAN egress pipeline may still alter tag state + * according to VLAN membership and untag configuration. + */ + if (vlan_filtering) + mode =3D RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL; + else + mode =3D RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP; + + ret =3D rtl8365mb_port_set_vlan_egress_mode(priv, port, mode); + if (ret) + goto undo_transparent; + + return ret; + +undo_transparent: + /* It will also try to undo the failed port if the error happened + * inside the transparent VLAN loop but that might be inocuous. + */ + dsa_switch_for_each_port_continue_reverse(dp, ds) { + if (dsa_port_is_unused((dp))) + continue; + + rtl8365mb_port_set_transparent(priv, port, dp->index, + vlan_filtering); + } + + rtl8365mb_port_set_ingress_filtering(priv, port, !vlan_filtering); + + return ret; +} + +static int rtl8365mb_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + bool untagged =3D !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); + bool pvid =3D !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); + struct realtek_priv *priv =3D ds->priv; + int ret; + + dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n", + vlan->vid, port, untagged ? "untagged" : "tagged", + pvid ? "PVID" : "no PVID"); + /* add port to vlan4k. It knows nothing about PVID */ + ret =3D rtl8365mb_vlan_4k_port_add(ds, port, vlan, extack); + if (ret) + return ret; + + /* Set PVID if needed */ + if (pvid) { + ret =3D rtl8365mb_vlan_pvid_port_add(ds, port, vlan, extack); + if (ret) + goto undo_vlan_4k; + } + + return 0; + +undo_vlan_4k: + (void)rtl8365mb_vlan_4k_port_del(ds, port, vlan); + return ret; +} + +static int rtl8365mb_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + bool untagged =3D !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); + bool pvid =3D !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); + struct realtek_priv *priv =3D ds->priv; + int ret1, ret2; + + dev_dbg(priv->dev, "del VLAN %d on port %d, %s, %s\n", + vlan->vid, port, untagged ? "untagged" : "tagged", + pvid ? "PVID" : "no PVID"); + + ret1 =3D rtl8365mb_vlan_pvid_port_del(ds, port, vlan); + ret2 =3D rtl8365mb_vlan_4k_port_del(ds, port, vlan); + + return ret1 ?: ret2; +} + +/* VLAN support is always enabled in the switch. + * + * Standalone forwarding relies on transparent VLAN mode combined with per= -port + * isolation masks restricting egress to CPU ports only. + * + */ +static int rtl8365mb_vlan_setup(struct dsa_switch *ds) +{ + struct realtek_priv *priv =3D ds->priv; + struct dsa_port *dp; + int ret; + + dsa_switch_for_each_available_port(dp, ds) { + /* Disable vlan-filtering for all ports */ + ret =3D rtl8365mb_port_vlan_filtering(ds, dp->index, false, NULL); + if (ret) { + dev_err(priv->dev, + "Failed to disable vlan filtering on port %d\n", + dp->index); + return ret; + } + } + + /* VLAN is always enabled. */ + ret =3D regmap_update_bits(priv->map, RTL8365MB_VLAN_CTRL_REG, + RTL8365MB_VLAN_CTRL_EN_MASK, + FIELD_PREP(RTL8365MB_VLAN_CTRL_EN_MASK, 1)); + return ret; +} + static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, bool enable) { @@ -2059,6 +2310,13 @@ static int rtl8365mb_setup(struct dsa_switch *ds) if (ret) goto out_teardown_irq; =20 + ds->configure_vlan_while_not_filtering =3D true; + + /* Set up VLAN */ + ret =3D rtl8365mb_vlan_setup(ds); + if (ret) + goto out_teardown_irq; + ret =3D rtl83xx_setup_user_mdio(ds); if (ret) { dev_err(priv->dev, "could not set up MDIO bus\n"); @@ -2169,6 +2427,9 @@ static const struct dsa_switch_ops rtl8365mb_switch_o= ps =3D { .teardown =3D rtl8365mb_teardown, .phylink_get_caps =3D rtl8365mb_phylink_get_caps, .port_stp_state_set =3D rtl8365mb_port_stp_state_set, + .port_vlan_add =3D rtl8365mb_port_vlan_add, + .port_vlan_del =3D rtl8365mb_port_vlan_del, + .port_vlan_filtering =3D rtl8365mb_port_vlan_filtering, .get_strings =3D rtl8365mb_get_strings, .get_ethtool_stats =3D rtl8365mb_get_ethtool_stats, .get_sset_count =3D rtl8365mb_get_sset_count, diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/rea= ltek/rtl8365mb_vlan.c new file mode 100644 index 000000000000..82add166ff91 --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c @@ -0,0 +1,813 @@ +// SPDX-License-Identifier: GPL-2.0 +/* VLAN configuration interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin =C5=A0ipraga + * + * VLAN configuration takes place in two separate domains of the switch: t= he + * VLAN4k table and the VLAN membership configuration (MC) database. While= the + * VLAN4k table is exhaustive and can be fully populated with 4096 VLAN + * configurations, the same does not hold for the VLAN membership configur= ation + * database, which is limited to 32 entries. + * + * The switch will normally only use the VLAN4k table when making forwardi= ng + * decisions. The VLAN membership configuration database is a vestigial AS= IC + * design and is only used for a few specific features in the rtl8365mb + * family. This means that the limit of 32 entries should not hinder us in + * programming a huge number of VLANs into the switch. + * + * One necessary use of the VLAN membership configuration database is for = the + * programming of a port-based VLAN ID (PVID). The PVID is programmed on a + * per-port basis via register field, which refers to a specific VLAN memb= ership + * configuration via an index 0~31. In order to maintain coherent behaviou= r on a + * port with a PVID, it is necessary to keep the VLAN configuration synchr= onized + * between the VLAN4k table and the VLAN membership configuration database. + * + * Since VLAN membership configs are a scarce resource, it will only be us= ed + * when strictly needed (i.e. a VLAN with members using PVID). Otherwise, = the + * VLAN4k will be enough. + * + * With some exceptions, the entries in both the VLAN4k table and the VLAN + * membership configuration database offer the same configuration options.= The + * differences are as follows: + * + * 1. VLAN4k entries can specify whether to use Independent or Shared VLAN + * Learning (IVL or SVL respectively). VLAN membership config entries + * cannot. This underscores the fact that VLAN membership configs are n= ot + * involved in the learning process of the ASIC. + * + * 2. VLAN membership config entries use an "enhanced VLAN ID" (efid), whi= ch has + * a range 0~8191 compared with the standard 0~4095 range of the VLAN4k + * table. This underscores the fact that VLAN membership configs can be= used + * to group ports on a layer beyond the standard VLAN configuration, wh= ich + * may be useful for ACL rules which specify alternative forwarding + * decisions. + * + * VLANMC index 0 is reserved as a neutral PVID, used for standalone ports. + * + */ + +#include "rtl8365mb_vlan.h" +#include "rtl8365mb_table.h" +#include +#include + +/* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */ +#define RTL8365MB_CVLAN_ENTRY_SIZE 3 /* 48-bits */ +#define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK GENMASK(7, 0) +#define RTL8365MB_CVLAN_MBR_LO_MASK GENMASK(7, 0) +#define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK GENMASK(15, 8) +#define RTL8365MB_CVLAN_UNTAG_LO_MASK GENMASK(7, 0) +#define RTL8365MB_CVLAN_ENTRY_D1_FID_MASK GENMASK(3, 0) +#define RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK GENMASK(4, 4) +#define RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK GENMASK(7, 5) +#define RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK GENMASK(8, 8) +#define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK GENMASK(13, 9) +#define RTL8365MB_CVLAN_METERIDX_LO_MASK GENMASK(4, 0) +#define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK GENMASK(14, 14) +/* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */ +#define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK GENMASK(2, 0) +#define RTL8365MB_CVLAN_MBR_HI_MASK GENMASK(10, 8) +/* extends RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK */ +#define RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK GENMASK(5, 3) +#define RTL8365MB_CVLAN_UNTAG_HI_MASK GENMASK(10, 8) +/* extends RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK */ +#define RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK GENMASK(6, 6) +#define RTL8365MB_CVLAN_METERIDX_HI_MASK GENMASK(5, 5) + +/* VLAN member configuration registers 0~31, u16[3] */ +#define RTL8365MB_VLAN_MC_BASE 0x0728 +#define RTL8365MB_VLAN_MC_ENTRY_SIZE 4 /* 64-bit */ +#define RTL8365MB_VLAN_MC_REG(index) \ + (RTL8365MB_VLAN_MC_BASE + \ + (RTL8365MB_VLAN_MC_ENTRY_SIZE * (index))) +#define RTL8365MB_VLAN_MC_D0_MBR_MASK GENMASK(10, 0) +#define RTL8365MB_VLAN_MC_D1_FID_MASK GENMASK(3, 0) + +#define RTL8365MB_VLAN_MC_D2_VBPEN_MASK GENMASK(0, 0) +#define RTL8365MB_VLAN_MC_D2_VBPRI_MASK GENMASK(3, 1) +#define RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK GENMASK(4, 4) +#define RTL8365MB_VLAN_MC_D2_METERIDX_MASK GENMASK(10, 5) +#define RTL8365MB_VLAN_MC_D3_EVID_MASK GENMASK(12, 0) + +/* Some limits for VLAN4k/VLAN membership config entries */ +#define RTL8365MB_PRIORITYMAX 7 +#define RTL8365MB_FIDMAX 15 +#define RTL8365MB_METERMAX 63 +#define RTL8365MB_VLAN_MCMAX 31 + +/* RTL8367S supports 4k vlans (vid<=3D4095) and 32 enhanced vlans + * for VIDs up to 8191 + */ +#define RTL8365MB_MAX_4K_VID 0x0FFF /* 4095 */ +#define RTL8365MB_MAX_MC_VID 0x1FFF /* 8191 */ + + /* Port-based VID registers 0~5 - each one holds an MC index for two port= s */ +#define RTL8365MB_VLAN_PVID_CTRL_BASE 0x0700 +#define RTL8365MB_VLAN_PVID_CTRL_REG(_p) \ + (RTL8365MB_VLAN_PVID_CTRL_BASE + ((_p) >> 1)) +#define RTL8365MB_VLAN_PVID_CTRL_PORT0_MCIDX_MASK 0x001F +#define RTL8365MB_VLAN_PVID_CTRL_PORT1_MCIDX_MASK 0x1F00 +#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p) \ + (((_p) & 1) << 3) +#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \ + (0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p)) + +/* Frame type filtering registers */ +#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa +#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \ + (RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE + ((port) >> 3)) +/* required as FIELD_PREP cannot use non-constant masks */ +#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port) \ + (0x3 << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port)) +#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port) \ + (((port) & 0x7) << 1) + +/** + * struct rtl8365mb_vlan4k - VLAN4k table entry + * @vid: VLAN ID (0~4095) + * @member: port mask of ports in this VLAN + * @untag: port mask of ports which untag on egress + * @fid: filter ID - only used with SVL (unused) + * @priority: priority classification (unused) + * @priority_en: enable priority (unused) + * @policing_en: enable policing (unused) + * @ivl_en: enable IVL instead of default SVL + * @meteridx: metering index (unused) + * + * This structure is used to get/set entries in the VLAN4k table. The + * VLAN4k table dictates the VLAN configuration for the switch for the + * vast majority of features. + */ +struct rtl8365mb_vlan4k { + u16 vid; + u16 member; + u16 untag; + u8 fid : 4; + u8 priority : 3; + u8 priority_en : 1; + u8 policing_en : 1; + u8 ivl_en : 1; + u8 meteridx : 6; +}; + +/** + * struct rtl8365mb_vlanmc - VLAN membership config + * @evid: Enhanced VLAN ID (0~8191) + * @member: port mask of ports in this VLAN + * @fid: filter ID - only used with SVL (unused) + * @priority: priority classification (unused) + * @priority_en: enable priority (unused) + * @policing_en: enable policing (unused) + * @meteridx: metering index (unused) + * + * This structure is used to get/set entries in the VLAN membership + * configuration database. This feature is largely vestigial, but + * still needed for at least the following features: + * - PVID configuration + * - ACL configuration + * - selection of VLAN by the CPU tag when VSEL=3D1, although the switch + * can also select VLAN based on the VLAN tag if VSEL=3D0 + * + * This is a low-level structure and it is recommended to interface with + * the VLAN membership config database via &struct rtl8365mb_vlanmc_entry. + */ +struct rtl8365mb_vlanmc { + u16 evid; + u16 member; + u8 fid : 4; + u8 priority : 3; + u8 priority_en : 1; + u8 policing_en : 1; + u8 meteridx : 6; +}; + +enum rtl8365mb_frame_ingress { + RTL8365MB_FRAME_TYPE_ANY_FRAME =3D 0, + RTL8365MB_FRAME_TYPE_TAGGED_ONLY, + RTL8365MB_FRAME_TYPE_UNTAGGED_ONLY, +}; + +static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid, + struct rtl8365mb_vlan4k *vlan4k) +{ + u16 data[RTL8365MB_CVLAN_ENTRY_SIZE]; + int val; + int ret; + + ret =3D rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, + RTL8365MB_TABLE_OP_READ, &vid, 0, 0, + data, ARRAY_SIZE(data)); + if (ret) + return ret; + + /* Unpack table entry */ + memset(vlan4k, 0, sizeof(*vlan4k)); + vlan4k->vid =3D vid; + + val =3D FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]); + vlan4k->member =3D FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val); + val =3D FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]); + vlan4k->member |=3D FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val); + + val =3D FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]); + vlan4k->untag =3D FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val); + val =3D FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]); + vlan4k->untag |=3D FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val); + + vlan4k->fid =3D FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]); + vlan4k->priority_en =3D + FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]); + vlan4k->priority =3D + FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]); + vlan4k->policing_en =3D + FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]); + + val =3D FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]); + val =3D FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val); + vlan4k->meteridx =3D val; + val =3D FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]); + val =3D FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val); + vlan4k->meteridx |=3D val; + + vlan4k->ivl_en =3D + FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]); + + return 0; +} + +static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv, + const struct rtl8365mb_vlan4k *vlan4k) +{ + u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] =3D { 0 }; + u16 vid; + int val; + + /* Pack table entry value */ + val =3D FIELD_GET(RTL8365MB_CVLAN_MBR_LO_MASK, vlan4k->member); + data[0] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, val); + + val =3D FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k->untag); + data[0] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val); + + data[1] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid); + data[1] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, + vlan4k->priority_en); + data[1] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, + vlan4k->priority); + data[1] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, + vlan4k->policing_en); + + /* FIELD_* does not play nice with struct bitfield. */ + val =3D vlan4k->meteridx; + val =3D FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val); + data[1] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val); + + data[1] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, + vlan4k->ivl_en); + + val =3D FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member); + data[2] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val); + + val =3D FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag); + data[2] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val); + + val =3D vlan4k->meteridx; + val =3D FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val); + data[2] |=3D FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val); + + vid =3D vlan4k->vid; + return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, + RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0, + data, ARRAY_SIZE(data)); +} + +#define RTL_VLAN_ERR(msg) \ + do { \ + const char *__msg =3D (msg); \ + \ + if (extack) \ + NL_SET_ERR_MSG_FMT_MOD(extack, "%s", __msg); \ + dev_err(priv->dev, "%s", __msg); \ + } while (0) + +static int +rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack, + bool include) +{ + struct realtek_priv *priv =3D ds->priv; + struct rtl8365mb_vlan4k vlan4k =3D {0}; + int ret; + + dev_dbg(priv->dev, "%s VLAN %d 4K on port %d\n", + include ? "add" : "del", + vlan->vid, port); + + if (vlan->vid > RTL8365MB_MAX_4K_VID) { + RTL_VLAN_ERR("VLAN ID greater than " + __stringify(RTL8365MB_MAX_4K_VID)); + return -EINVAL; + } + + ret =3D rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k); + if (ret) { + RTL_VLAN_ERR("Failed to read VLAN 4k table"); + return ret; + } + + if (include) + vlan4k.member |=3D BIT(port); + else + vlan4k.member &=3D ~BIT(port); + + if (include && (vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED)) + vlan4k.untag |=3D BIT(port); + else + vlan4k.untag &=3D ~BIT(port); + vlan4k.ivl_en =3D true; /* always use Independent VLAN Learning */ + + ret =3D rtl8365mb_vlan_4k_write(priv, &vlan4k); + if (ret) { + RTL_VLAN_ERR("Failed to write VLAN 4k table"); + return ret; + } + + return 0; +} + +int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + return rtl8365mb_vlan_4k_port_set(ds, port, vlan, extack, true); +} + +int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + return rtl8365mb_vlan_4k_port_set(ds, port, vlan, NULL, false); +} + +static int rtl8365mb_vlan_mc_read(struct realtek_priv *priv, u32 index, + struct rtl8365mb_vlanmc *vlanmc) +{ + u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE]; + int ret; + + ret =3D regmap_bulk_read(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, + RTL8365MB_VLAN_MC_ENTRY_SIZE); + if (ret) + return ret; + + vlanmc->member =3D FIELD_GET(RTL8365MB_VLAN_MC_D0_MBR_MASK, data[0]); + vlanmc->fid =3D FIELD_GET(RTL8365MB_VLAN_MC_D1_FID_MASK, data[1]); + vlanmc->meteridx =3D FIELD_GET(RTL8365MB_VLAN_MC_D2_METERIDX_MASK, + data[2]); + vlanmc->policing_en =3D FIELD_GET(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK, + data[2]); + vlanmc->priority =3D FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, data[2]); + vlanmc->evid =3D FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, data[3]); + + return 0; +} + +static int rtl8365mb_vlan_mc_write(struct realtek_priv *priv, u32 index, + const struct rtl8365mb_vlanmc *vlanmc) +{ + u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] =3D { 0 }; + int ret; + + data[0] |=3D FIELD_PREP(RTL8365MB_VLAN_MC_D0_MBR_MASK, vlanmc->member); + data[1] |=3D FIELD_PREP(RTL8365MB_VLAN_MC_D1_FID_MASK, vlanmc->fid); + data[2] |=3D FIELD_PREP(RTL8365MB_VLAN_MC_D2_METERIDX_MASK, + vlanmc->meteridx); + data[2] |=3D FIELD_PREP(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK, + vlanmc->policing_en); + data[2] |=3D + FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, vlanmc->priority); + data[2] |=3D FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPEN_MASK, + vlanmc->priority_en); + data[3] |=3D FIELD_PREP(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlanmc->evid); + + ret =3D regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, + RTL8365MB_VLAN_MC_ENTRY_SIZE); + + return ret; +} + +static int rtl8365mb_vlan_mc_erase(struct realtek_priv *priv, u32 index) +{ + u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] =3D { 0 }; + int ret; + + ret =3D regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, + RTL8365MB_VLAN_MC_ENTRY_SIZE); + + return ret; +} + +/** + * rtl8365mb_vlan_mc_find() - find VLANMC index by VID or the first free i= ndex + * + * @priv: realtek_priv pointer + * @vid: VLAN ID + * @index: found index + * @first_free: found free index + * + * If a VLAN MC entry using @vid was found, @index will return the matched= index + * and @first_free is undefined. If not found, @index will return 0 and + * @first_free will return the first found free index in VLAN MC or 0 if t= he + * table is full. + * + * Although 0 is a valid VLAN MC index, it is reserved for ports without P= VID, + * including standalone, non-member ports. It uses VID =3D=3D 0. + * + * Both @index and @first_free will be in the * 1..@RTL8365MB_VLAN_MCMAX r= ange. + * + * Return: Returns 0 on success, a negative error on failure. + * + */ +static int rtl8365mb_vlan_mc_find(struct realtek_priv *priv, u16 vid, + u8 *index, u8 *first_free) +{ + u32 vlan_entry_d3; + u8 vlanmc_idx; + u16 evid; + int ret; + + *index =3D 0; + *first_free =3D 0; + + /* look for existing entry or an empty one */ + /* By design, VlanMC[0] is reserved as a neutral PVID value for + * standalone ports. It always has EVID =3D=3D 0. That way, we assume that + * all entries after index 0 with VID =3D=3D 0 are empty. + **/ + for (vlanmc_idx =3D 1; vlanmc_idx <=3D RTL8365MB_VLAN_MCMAX; vlanmc_idx++= ) { + /* just read the 4th word, where the evid is */ + ret =3D regmap_read(priv->map, + RTL8365MB_VLAN_MC_REG(vlanmc_idx) + 3, + &vlan_entry_d3); + if (ret) + return ret; + + evid =3D FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlan_entry_d3); + + if (evid =3D=3D vid) { + *index =3D vlanmc_idx; + return 0; + } + + if (evid =3D=3D 0x0 && *first_free < 1) + *first_free =3D vlanmc_idx; + } + return 0; +} + +static int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, + int port, u8 *vlanmc_idx) +{ + u32 data; + int ret; + + ret =3D regmap_read(priv->map, RTL8365MB_VLAN_PVID_CTRL_REG(port), &data); + if (ret) + return ret; + + *vlanmc_idx =3D (data & RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port)) + >> RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port); + + return 0; +} + +/** + * rtl8365mb_vlan_mc_pvid_members() - Get a bitmap of vlan PVID members + * + * @ds: DSA switch + * @vlanmc_idx: the index of a VLAN in VLAN MC table + * @members: the returned bitmap of members that have PVID status + * + * This function iterates over DSA ports and creates a bitmap representati= on of + * those ports that have PVID pointing to this VLAN (identified by its tab= le + * index and not VID). If you need to get the table index from VID, see + * rtl8365mb_vlan_mc_find() + * + * Return: Returns 0 on success, a negative error on failure. + **/ +static int rtl8365mb_vlan_mc_pvid_members(struct dsa_switch *ds, + u8 vlanmc_idx, u16 *members) +{ + struct realtek_priv *priv =3D ds->priv; + struct dsa_port *dp; + u8 _vlanmc_idx; + int ret; + + *members =3D 0; + + dsa_switch_for_each_available_port(dp, ds) { + ret =3D rtl8365mb_vlan_port_get_pvid(priv, dp->index, + &_vlanmc_idx); + if (ret) + return ret; + + if (_vlanmc_idx =3D=3D vlanmc_idx) + *members |=3D BIT(dp->index); + } + + return 0; +} + +/** + * rtl8365mb_vlan_mc_port_set() - include or exclude a port from vlanMC + * @ds: dsa switch + * @port: the port number + * @vlan: the vlan to include/exclude @port + * @extack: optional extack to return errors + * @include: whether to include or exclude @port + * + * This function is used to include/exclude ports to the vlanMC table. + * + * VlanMC stands for VLAN membership config and it is used exclusively for + * PVID. If @vlan members are not using PVID, this function will either + * remove or not create a new vlanMC entry. + * + * vlanMC members are kept in sync with vlan4k, although the switch only + * checks membership in vlan4k table. + * + * Port PVID and accepted frame type are updated as well. + * + * Return: Returns 0 on success, a negative error on failure. + */ +static +int rtl8365mb_vlan_mc_port_set(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack, + bool include) +{ + bool pvid =3D !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); + struct realtek_priv *priv =3D ds->priv; + struct rtl8365mb_vlan4k vlan4k =3D {0}; + struct rtl8365mb_vlanmc vlanmc =3D {0}; + u16 pvid_members =3D 0; + u8 first_unused =3D 0; + u8 vlanmc_idx =3D 0; + int ret; + + dev_dbg(priv->dev, "%s VLAN %d MC on port %d\n", + include ? "add" : "del", + vlan->vid, port); + + if (vlan->vid > RTL8365MB_MAX_MC_VID) { + RTL_VLAN_ERR("VLAN ID greater than " + __stringify(RTL8365MB_MAX_MC_VID)); + return -EINVAL; + } + + /* look for existing entry or an empty slot */ + ret =3D rtl8365mb_vlan_mc_find(priv, vlan->vid, &vlanmc_idx, + &first_unused); + if (ret) { + RTL_VLAN_ERR("Failed to find a VLAN MC table index"); + return ret; + } + + if (vlanmc_idx) { + ret =3D rtl8365mb_vlan_mc_read(priv, vlanmc_idx, &vlanmc); + if (ret) { + RTL_VLAN_ERR("Failed to read VLAN MC table"); + return ret; + } + } else if (include) { + /* for now, vlan_mc is only required for PVID. Defer allocation + * until at least one port uses PVID. + */ + if (!pvid) { + dev_dbg(priv->dev, + "Not creating VlanMC for vlan %d until a port uses PVID (%d does not)\= n", + vlan->vid, port); + return 0; + } + + if (!first_unused) { + RTL_VLAN_ERR("All VLAN MC entries (" + __stringify(RTL8365MB_VLAN_MCMAX + 1) + ") are in use."); + return -E2BIG; + } + + /* Retrieve vlan4k members as we might have deferred VlanMC + * before. + */ + if (vlan->vid <=3D RTL8365MB_MAX_4K_VID) { + ret =3D rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k); + if (ret) { + RTL_VLAN_ERR("Failed to read VLAN 4k table"); + return ret; + } + } + + vlanmc_idx =3D first_unused; + vlanmc.evid =3D vlan->vid; + + /* for new vlan_mc, sync current vlan4k members, + * although only vlan4k members matter. + */ + vlanmc.member |=3D vlan4k.member; + } else /* excluding and VLANMC not found */ { + return 0; + } + + ret =3D rtl8365mb_vlan_mc_pvid_members(ds, vlanmc_idx, + &pvid_members); + if (ret) { + RTL_VLAN_ERR("Failed to read VLANMC PVID members"); + return ret; + } + dev_dbg(priv->dev, + "VLAN %d (idx: %d) PVID curr members: %08x\n", + vlan->vid, vlanmc_idx, pvid_members); + + /* here we either have an existing VLANMC (with PVID members) or the + * added port is using this VLAN as PVID + */ + if (include) { + vlanmc.member |=3D BIT(port); + if (pvid) + pvid_members |=3D BIT(port); + } else { + vlanmc.member &=3D ~BIT(port); + pvid_members &=3D ~BIT(port); + } + + /* just like we don't need to create a VLAN_MC when there is no port + * using it as PVID, we can erase it when there is no more port using + * it as PVID. + */ + if (!pvid_members) { + dev_dbg(priv->dev, + "Clearing VlanMC index %d previously used by VID %d\n", + vlanmc_idx, vlan->vid); + ret =3D rtl8365mb_vlan_mc_erase(priv, vlanmc_idx); + } else { + dev_dbg(priv->dev, + "Saving VlanMC index %d with VID %d\n", + vlanmc_idx, vlan->vid); + ret =3D rtl8365mb_vlan_mc_write(priv, vlanmc_idx, &vlanmc); + } + if (ret) { + RTL_VLAN_ERR("Failed to write vlan MC entry"); + return ret; + } + + return 0; +} + +static int rtl8365mb_vlan_port_set_pvid(struct realtek_priv *priv, + int port, u16 vlanmc_idx) +{ + int ret; + u32 val; + + dev_dbg(priv->dev, "set PVID IDX %d on port %d\n", vlanmc_idx, port); + + val =3D vlanmc_idx << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port); + ret =3D regmap_update_bits(priv->map, + RTL8365MB_VLAN_PVID_CTRL_REG(port), + RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port), + val); + if (ret) + return ret; + + return 0; +} + +static int +rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, + int port, + enum rtl8365mb_frame_ingress accepted_frame) +{ + u32 val; + + /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the + * port is a member of vlan PVID + */ + val =3D accepted_frame << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port); + + return regmap_update_bits(priv->map, + RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port), + RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port), + val); +} + +int rtl8365mb_vlan_pvid_port_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + bool pvid =3D !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); + enum rtl8365mb_frame_ingress accepted_frame; + struct realtek_priv *priv =3D ds->priv; + u8 _unused_first_free_idx; + u8 vlanmc_idx; + int ret; + + if (!pvid) + return 0; + + /* A VLAN can be added with PVID without removing from the old + * PVID VLAN. Clear PVID from any VLAN if needed. + */ + ret =3D rtl8365mb_vlan_pvid_port_del(ds, port, NULL); + if (ret) + return ret; + + /* Find or allocate a new vlan MC and add port to members, + * although members are not checked by the HW in vlan MC. + */ + ret =3D rtl8365mb_vlan_mc_port_set(ds, port, vlan, extack, true); + if (ret) + return ret; + + /* look for existing entry */ + ret =3D rtl8365mb_vlan_mc_find(priv, vlan->vid, &vlanmc_idx, + &_unused_first_free_idx); + if (ret) { + RTL_VLAN_ERR("Failed to find a VLAN MC table index"); + goto undo_vlan_mc_port_set; + } + + if (!vlanmc_idx) { + RTL_VLAN_ERR("VLAN should already exist in VLAN MC"); + ret =3D -ENOENT; + goto undo_vlan_mc_port_set; + } + + ret =3D rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx); + if (ret) { + RTL_VLAN_ERR("Failed to set port PVID"); + goto undo_vlan_mc_port_set; + } + + /* Changing accept frame is what enables PVID (if not enabled before) */ + accepted_frame =3D RTL8365MB_FRAME_TYPE_ANY_FRAME; + ret =3D rtl8365mb_vlan_port_set_framefilter(priv, port, accepted_frame); + if (ret) { + RTL_VLAN_ERR("Failed to set port frame filter"); + goto undo_vlan_pvid_port_set; + } + + return 0; + +undo_vlan_pvid_port_set: + (void)rtl8365mb_vlan_port_set_pvid(priv, port, 0); + +undo_vlan_mc_port_set: + (void)rtl8365mb_vlan_mc_port_set(ds, port, vlan, NULL, false); + return ret; +} + +int rtl8365mb_vlan_pvid_port_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + enum rtl8365mb_frame_ingress accepted_frame; + struct netlink_ext_ack *extack =3D NULL; + struct realtek_priv *priv =3D ds->priv; + struct rtl8365mb_vlanmc vlanmc =3D {0}; + u8 vlanmc_idx; + int ret; + + ret =3D rtl8365mb_vlan_port_get_pvid(priv, port, &vlanmc_idx); + if (ret) + return ret; + + /* Port is not using PVID. Nothing to remove. */ + if (!vlanmc_idx) + return 0; + + ret =3D rtl8365mb_vlan_mc_read(priv, vlanmc_idx, &vlanmc); + if (ret) { + RTL_VLAN_ERR("Failed to read VLAN MC table"); + return ret; + } + + /* We are leaving a non PVID vlan, Nothing to remove. */ + if (vlan && (vlanmc.evid !=3D vlan->vid)) + return 0; + + /* Changing accept frame is what really removes PVID */ + accepted_frame =3D RTL8365MB_FRAME_TYPE_TAGGED_ONLY; + ret =3D rtl8365mb_vlan_port_set_framefilter(priv, port, accepted_frame); + if (ret) { + RTL_VLAN_ERR("Failed to set port frame filter"); + return ret; + } + + ret =3D rtl8365mb_vlan_port_set_pvid(priv, port, 0); + if (ret) { + RTL_VLAN_ERR("Failed to set port PVID to 0"); + return ret; + } + + /* Clears the VLAN MC membership and maybe VLAN MC entry if empty */ + return rtl8365mb_vlan_mc_port_set(ds, port, vlan, NULL, false); +} diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/rea= ltek/rtl8365mb_vlan.h new file mode 100644 index 000000000000..ed63a0ec0d10 --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* VLAN configuration interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin =C5=A0ipraga + * + */ + +#ifndef _REALTEK_RTL8365MB_VLAN_H +#define _REALTEK_RTL8365MB_VLAN_H + +#include + +#include "realtek.h" + +int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack); +int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan); +int rtl8365mb_vlan_pvid_port_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack); +int rtl8365mb_vlan_pvid_port_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan); +#endif /* _REALTEK_RTL8365MB_VLAN_H */ --=20 2.54.0 From nobody Mon May 25 08:11:40 2026 Received: from mail-qv1-f50.google.com (mail-qv1-f50.google.com [209.85.219.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9E6D329992A for ; Sat, 16 May 2026 03:47:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903264; cv=none; b=nxkhEogM8ftUIL1mmyYKsYuB6a5APxsMHEUpOhDiFVdtMhMqTapo5a4y/ACJjYCobn7kZgfsyKeKeXirhS9te+3K9BgUtHNUU8Gcprf8p0+07a533ErWVCEymxSx6u7Im0CfQXNcuvvHecCt/7HilenDiodENJInSmEoj+43vPM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903264; c=relaxed/simple; bh=uqAn/p3Xuhr7ussV2ofCClM+ENxJccl1uBIW4tGfojI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=N23sGGjZKoyHWwWecE4Q3K067RxP53+bHRJSvo+6vwgDbH69xthfDPLVhGdZA1kRJED1NpFx9SRTKByjDsq2eErCWHPXimlYBweyZUpLiRPJ0RDiwVHLmjoFVwdTknKac0zwDTg7OjiKgy4rRqbY5DZxNWrlNnjcKOlEBqGnu0A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=IxY9uGSj; arc=none smtp.client-ip=209.85.219.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="IxY9uGSj" Received: by mail-qv1-f50.google.com with SMTP id 6a1803df08f44-8b7105dfb35so8721706d6.3 for ; Fri, 15 May 2026 20:47:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778903262; x=1779508062; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=LH1urpZdWyi/0tcsSLzpGgmN4hcoBNM2XecVgIljFjM=; b=IxY9uGSjQPPDDHyLoxmADE4mEFtxaeGRRBB7UL2kVJDwBRD7RiFrW2guJuzYoHc2U3 xQoyilG9TNey0ZItfmCmNa/OIM6CYEYxrJeziN+gZDvw0Bzr/jKtl2cMzq++NZObp0NR 9ZRxj+XXhVYt6zHVnbThpJa7K4UWA0g8qBh59z/ZstVv7Vf7nBsBnW60Dn4YWhwL98Ux FXz0NoTs2wcY9CzE0D2NsaC8vD3yWJ4zD5dTGmzWgZT+ZUYVXcW38HgBY2lUmSRhTIvT F0F0BY4OpZZPt7uwwj+sSjqo6U2PkjFOHIa0ZeJyaFw+XQY5iAfvhrMoEPgyOSfUnbIb 9SlA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778903262; x=1779508062; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=LH1urpZdWyi/0tcsSLzpGgmN4hcoBNM2XecVgIljFjM=; b=jB+SaEeOchz57ejKzIA/vZZM1dBZ1xet1SDJz/zOTDokSeNdo09L0x0mKmaDZZ1R6g H+6y/Y/Lq5s3wwtKtJ/FBf0hrn8kS4i5G3W05fs5WOVefERFn8grusUq8YZo6NNfEwLi aiY/ixXkpJdV4vDGLLl3pssgZrvSnqalbv/ezpmICMdtYwsQDg5da+yIeG3rdKfkg3SM 55OqT2KiCBUKFKn05a7QwH8dP7bVEsoAOJj8AkeBy0aPRBrqi2nlAUXTcJwsiR6jclKm 6n2XzMcNe+XS2i5duYN98rt/Eohx44rQic8iws3glOEdYP+3pzNFqGNeveaDxf1KhM2M Cl0Q== X-Forwarded-Encrypted: i=1; AFNElJ9MBHIP+bbcQU0jNimeVLEpEeiDVOnFrJSkHbtSJm4LTVZdQShzY8RKE162LYTRUVrIxV51YLmm+C2RayI=@vger.kernel.org X-Gm-Message-State: AOJu0YzwYCm4tkVhXr09eIRg0Nt61grqGX7YJ60z84PEt6+ig8VRTu0+ EWHtx8yStZnPkljADT9O7AZS/jxx40DLoB/QSbkNPTn/0fKWYUhIuW+o X-Gm-Gg: Acq92OG6DrukpNZpIiD7Ofjh1gSlZXwteJ/aqbDMuJCZHIrXy89GOp9TPejAtwBEyt0 JET6j4Ee6J1xb8mm3nglr81nNCqum1PQJJTKhEP3j8r8lhvkSOlQZk4ca431xaFrUoihHctrLiS w2TYw8nVq4OLsGS8ivIlMRElsYC4A5uUphULZM6q1SLMJnh83DzU7j0YiO33dPCKAib2U3lmfeF 1RX2NbH039eVC3QRlwp9yE4xm+3ufT2VzapQ3i588BfxklepgTeWdTPOsu8R2b4l5KKboXZJDOi HJD7SbqWbA8MqpQF/QJrrRe2P+IIQFMDkhwT0EI3eL+bWsKnUWJ3H7ml7uHom8AFE9qxortuW0m OuNQoAxUSgkNGOnwgLani6pCccB/5QN7ehZoY0qkTI5xjyUuLjav9OdPXUdt7rFyQx6kKqtdqDl qWmQ6bYxMhWJzkLdtvNGT99LX9Ky3liOi67FOU+eo= X-Received: by 2002:ad4:4183:0:b0:8ac:af21:5746 with SMTP id 6a1803df08f44-8ca0f706681mr79360286d6.42.1778903261487; Fri, 15 May 2026 20:47:41 -0700 (PDT) Received: from tresc054937.tre-sc.gov.br ([187.65.210.13]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c908d1d2e7sm70874546d6.16.2026.05.15.20.47.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 20:47:40 -0700 (PDT) From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 00:46:22 -0300 Subject: [net-next PATCH v4 6/8] net: dsa: realtek: rtl8365mb: add port_bridge_{join,leave} Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260516-realtek_forward-v4-6-8b6d6a1eefdc@gmail.com> References: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> In-Reply-To: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> To: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Linus Walleij , =?utf-8?q?Alvin_=C5=A0ipraga?= , Yury Norov , Rasmus Villemoes , Russell King Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Luiz Angelo Daros de Luca X-Mailer: b4 0.15.2 From: Alvin =C5=A0ipraga Implement hardware offloading of bridge functionality. This is achieved by using the per-port isolation registers, which contain a forwarding port mask. The switch will refuse to forward packets ingressed on a given port to a port which is not in its forwarding mask. For each bridge that is offloaded, use the DSA-provided bridge number for the Extended Filtering ID (EFID). When using Independent VLAN Learning (IVL), the forwarding database is keyed with the tuple {VID, MAC, EFID}. There are 8 EFIDs available (0~7), but we reserve the default EFID 0 for standalone ports where learning is disabled. This fits nicely because DSA indexes the bridge number starting from 1. Because of the limited number of EFIDs, we have to set the max_num_bridges property of our switch to 7: we can't offload more than that or we will fail to offer IVL as at least two bridges would end up having to share an EFID. Co-developed-by: Alvin =C5=A0ipraga Signed-off-by: Alvin =C5=A0ipraga Reviewed-by: Linus Walleij Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/realtek.h | 5 ++ drivers/net/dsa/realtek/rtl8365mb_main.c | 57 ++++++++++++- drivers/net/dsa/realtek/rtl83xx.c | 141 +++++++++++++++++++++++++++= ++++ drivers/net/dsa/realtek/rtl83xx.h | 7 ++ 4 files changed, 209 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/re= altek.h index c03485a80d93..0942f534834d 100644 --- a/drivers/net/dsa/realtek/realtek.h +++ b/drivers/net/dsa/realtek/realtek.h @@ -107,6 +107,11 @@ struct realtek_ops { int (*enable_vlan)(struct realtek_priv *priv, bool enable); int (*enable_vlan4k)(struct realtek_priv *priv, bool enable); int (*enable_port)(struct realtek_priv *priv, int port, bool enable); + int (*port_add_isolation)(struct realtek_priv *priv, int port, + u32 mask); + int (*port_remove_isolation)(struct realtek_priv *priv, int port, + u32 mask); + int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid); int (*phy_read)(struct realtek_priv *priv, int phy, int regnum); int (*phy_write)(struct realtek_priv *priv, int phy, int regnum, u16 val); diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/rea= ltek/rtl8365mb_main.c index 90c3001d31f2..b951a2d4c7e3 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -285,6 +285,15 @@ (RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport)) #define RTL8365MB_PORT_ISOLATION_MASK 0x07FF =20 +/* Extended filter ID registers - used to key forwarding database with IVL= */ +#define RTL8365MB_EFID_MASK GENMASK(2, 0) +#define RTL8365MB_PORT_EFID_REG_BASE 0x0A32 +#define RTL8365MB_PORT_EFID_REG(_p) \ + (RTL8365MB_PORT_EFID_REG_BASE + ((_p) >> 2)) +#define RTL8365MB_PORT_EFID_OFFSET(_p) (((_p) & 0x3) << 2) +#define RTL8365MB_PORT_EFID_MASK(_p) \ + (RTL8365MB_EFID_MASK << RTL8365MB_PORT_EFID_OFFSET(_p)) + /* MSTP port state registers - indexed by tree instance */ #define RTL8365MB_MSTI_CTRL_BASE 0x0A00 #define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \ @@ -1459,10 +1468,44 @@ static int rtl8365mb_port_set_learning(struct realt= ek_priv *priv, int port, enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); } =20 +static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port, + u32 efid) +{ + return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port), + RTL8365MB_PORT_EFID_MASK(port), + efid << RTL8365MB_PORT_EFID_OFFSET(port)); +} + +/* Port isolation manipulation functions. + * + * The port isolation register controls the forwarding mask of a given + * port. The switch will not forward packets ingressed on a given port + * to ports which are not enabled in its forwarding mask. + * + * The port forwarding mask has the highest priority in forwarding + * decisions. The only exception to this rule is when the switch + * receives a packet on its CPU port with ALLOW=3D0. In that case the TX + * field of the CPU tag will override the forwarding port mask. + */ static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int por= t, u32 mask) { - return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask); + return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), + mask); +} + +static int rtl8365mb_port_add_isolation(struct realtek_priv *priv, int por= t, + u32 mask) +{ + return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), + mask, mask); +} + +static int rtl8365mb_port_remove_isolation(struct realtek_priv *priv, int = port, + u32 mask) +{ + return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), + mask, 0); } =20 static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port, @@ -2249,6 +2292,11 @@ static int rtl8365mb_setup(struct dsa_switch *ds) if (ret) goto out_teardown_irq; =20 + /* Set the default EFID 0 for standalone mode */ + ret =3D rtl8365mb_port_set_efid(priv, dp->index, 0); + if (ret) + goto out_teardown_irq; + /* Disable learning */ ret =3D rtl8365mb_port_set_learning(priv, dp->index, false); if (ret) @@ -2310,6 +2358,8 @@ static int rtl8365mb_setup(struct dsa_switch *ds) if (ret) goto out_teardown_irq; =20 + /* The EFID is 3 bits, but EFID 0 is reserved for standalone ports */ + ds->max_num_bridges =3D FIELD_MAX(RTL8365MB_EFID_MASK); ds->configure_vlan_while_not_filtering =3D true; =20 /* Set up VLAN */ @@ -2426,6 +2476,8 @@ static const struct dsa_switch_ops rtl8365mb_switch_o= ps =3D { .setup =3D rtl8365mb_setup, .teardown =3D rtl8365mb_teardown, .phylink_get_caps =3D rtl8365mb_phylink_get_caps, + .port_bridge_join =3D rtl83xx_port_bridge_join, + .port_bridge_leave =3D rtl83xx_port_bridge_leave, .port_stp_state_set =3D rtl8365mb_port_stp_state_set, .port_vlan_add =3D rtl8365mb_port_vlan_add, .port_vlan_del =3D rtl8365mb_port_vlan_del, @@ -2445,6 +2497,9 @@ static const struct dsa_switch_ops rtl8365mb_switch_o= ps =3D { =20 static const struct realtek_ops rtl8365mb_ops =3D { .detect =3D rtl8365mb_detect, + .port_add_isolation =3D rtl8365mb_port_add_isolation, + .port_remove_isolation =3D rtl8365mb_port_remove_isolation, + .port_set_efid =3D rtl8365mb_port_set_efid, .phy_read =3D rtl8365mb_phy_read, .phy_write =3D rtl8365mb_phy_write, }; diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rt= l83xx.c index 2b9bd4462714..ae4945c917f2 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -325,6 +325,147 @@ void rtl83xx_reset_deassert(struct realtek_priv *priv) gpiod_set_value(priv->reset, false); } =20 +/** + * rtl83xx_port_bridge_join() - join a port to a bridge + * @ds: DSA switch instance + * @port: port index + * @bridge: bridge being joined + * @tx_forward_offload: if the switch can offload TX forwarding + * @extack: netlink extended ack for reporting errors + * + * This function handles joining a port to a bridge. It updates the port + * isolation masks and EFID. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, + struct dsa_bridge bridge, + bool *tx_forward_offload, + struct netlink_ext_ack *extack) +{ + struct realtek_priv *priv =3D ds->priv; + struct dsa_port *dp; + u32 mask =3D 0; + int ret; + + if (!priv->ops->port_add_isolation) + return -EOPNOTSUPP; + + dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port); + + /* Add this port to the isolation group of every other port + * offloading this bridge. + */ + dsa_switch_for_each_user_port(dp, ds) { + /* Handle this port after */ + if (dp->index =3D=3D port) + continue; + + /* Skip ports that are not in this bridge */ + if (!dsa_port_offloads_bridge(dp, &bridge)) + continue; + + ret =3D priv->ops->port_add_isolation(priv, dp->index, BIT(port)); + if (ret) + goto undo_isolation; + + mask |=3D BIT(dp->index); + } + + /* If we support cascade switches, it should also include the + * downstream DSA ports to the isolation group. + */ + + /* Add those ports to the isolation group of this port */ + ret =3D priv->ops->port_add_isolation(priv, port, mask); + if (ret) + goto undo_isolation; + + /* Use the bridge number as the EFID for this port */ + if (priv->ops->port_set_efid) { + ret =3D priv->ops->port_set_efid(priv, port, bridge.num); + if (ret) + goto undo_self_isolation; + } + + return 0; + +undo_self_isolation: + priv->ops->port_remove_isolation(priv, port, mask); + +undo_isolation: + dsa_switch_for_each_port_continue_reverse(dp, ds) { + if (dp->index =3D=3D port) + continue; + + if (!dsa_port_offloads_bridge(dp, &bridge)) + continue; + + if (!dsa_port_is_user((dp))) + continue; + + priv->ops->port_remove_isolation(priv, dp->index, BIT(port)); + } + + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_join, "REALTEK_DSA"); + +/** + * rtl83xx_port_bridge_leave() - leave a bridge + * @ds: DSA switch instance + * @port: port index + * @bridge: bridge being left + * + * This function handles removing a port from a bridge. It updates the port + * isolation masks and EFID. + * + * Context: Can sleep. + * Return: nothing + */ +void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge) +{ + struct realtek_priv *priv =3D ds->priv; + struct dsa_port *dp; + u32 mask =3D 0; + + if (!priv->ops->port_remove_isolation) + return; + + dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port); + + /* Remove this port from the isolation group of every other + * port offloading this bridge. + */ + dsa_switch_for_each_user_port(dp, ds) { + /* Handle this port after */ + if (dp->index =3D=3D port) + continue; + + /* Skip ports that are not in this bridge */ + if (!dsa_port_offloads_bridge(dp, &bridge)) + continue; + + priv->ops->port_remove_isolation(priv, dp->index, BIT(port)); + + mask |=3D BIT(dp->index); + } + + /* If we support cascade switches, it should also exclude the + * downstream DSA ports from the isolation group. + */ + + /* Remove those ports from the isolation group of this port */ + priv->ops->port_remove_isolation(priv, port, mask); + + /* Revert to the default EFID 0 for standalone mode */ + if (priv->ops->port_set_efid) + priv->ops->port_set_efid(priv, port, 0); +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, "REALTEK_DSA"); + MODULE_AUTHOR("Luiz Angelo Daros de Luca "); MODULE_AUTHOR("Linus Walleij "); MODULE_DESCRIPTION("Realtek DSA switches common module"); diff --git a/drivers/net/dsa/realtek/rtl83xx.h b/drivers/net/dsa/realtek/rt= l83xx.h index c8a0ff8fd75e..2481a1aaa226 100644 --- a/drivers/net/dsa/realtek/rtl83xx.h +++ b/drivers/net/dsa/realtek/rtl83xx.h @@ -21,4 +21,11 @@ void rtl83xx_remove(struct realtek_priv *priv); void rtl83xx_reset_assert(struct realtek_priv *priv); void rtl83xx_reset_deassert(struct realtek_priv *priv); =20 +int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, + struct dsa_bridge bridge, + bool *tx_forward_offload, + struct netlink_ext_ack *extack); +void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge); + #endif /* _RTL83XX_H */ --=20 2.54.0 From nobody Mon May 25 08:11:40 2026 Received: from mail-qv1-f47.google.com (mail-qv1-f47.google.com [209.85.219.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A26CF28A3FA for ; Sat, 16 May 2026 03:47:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903271; cv=none; b=KtKu1PbAE6NmkFk/IQTFO8shfAoD06RwUc781SO/G+c+wCJEaabRqV3kPTr8MHg5OmUUJi22gVwaHe+FkuC86Iag2m8CK2YfrX4G5Z9/Ihd6/YGvTmjHN4Ew3Lh/5uyFhji3mx2JnDEKl+XOYLRu9dCQhks4eky+IobsvENPcdk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903271; c=relaxed/simple; bh=oTXOel0h2fVclbmKs8LRMFrxQwBTC1VMYctK151SKB8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=JIMiB5Wky8JLArgoO0iPr2s5Xn+xp7oRRkmXOmvYMpqCEzj7Y9GjJHCt5zs2TsEDu4ARLX5rMToeTk6lxVI5w50PagpzTrmQNEhZMfOzRtewLGg1AOOFE2JvZMogB/s16XHQkdNd1/fEARLHMvLwGgnDheksNSLPNP+zpLhmeq8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=spxgDPqK; arc=none smtp.client-ip=209.85.219.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="spxgDPqK" Received: by mail-qv1-f47.google.com with SMTP id 6a1803df08f44-8b8e98fd885so8597176d6.0 for ; Fri, 15 May 2026 20:47:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778903268; x=1779508068; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=YSqat2OKh3/S58xiN8gYXeVCaFpP3DcTgzvtWxDjiFg=; b=spxgDPqKMArqIS0TkpzNqDa03ESnSRvrOv8IcnTU4YmUo/DoobZYjCl3Kj9zsQoI0h 0TaD4JDbWx9JVB04j/z9lY75ImAu3Cc6M64a4ZtzCW61uCu3vKrgSbD3bt1pmzMmDyyW o/pxXYAHZwTJqekF/dKVqYs1HNdiBply0AhWUwxnQUYEm/gFxuJqC2dTg3VpGmROnM16 KvahvO2CK//mP+4a4ecqAtX2JxRk9fQsvJwCr6a1QObY6+pKMpgDE0t7Bpp/fU30PBus exmESyDnHnGGXgeE8PeVEYIDkbgzyZ1mYIcflwY+tKzj6MaU3qWdMroMDC1L0uEvNNG+ SJ5g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778903268; x=1779508068; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=YSqat2OKh3/S58xiN8gYXeVCaFpP3DcTgzvtWxDjiFg=; b=dHjvsrF7Wmp85hYfiL8yPSw3nTEw+SnkG7rY9MuyGJyw89K9mj7wnK5vtY2n1/J/Mt cW5K30SFFKqkAjmb93BL/7b3gMvL/sXUWQsetpq8/MCiKSDfD6hJvPv1ZMrSQl++H9Gc DntuzHXOmuYadMIuuqYswgphXlK+qrQr7DipXwXFeFZBCqtDfOvlXeZaLbOTxyqakiRb 6xsVfGTyCdzetwPY2woKuiI53r6fi+AyZ7uG4sVExLq/0+zYfB+hKQ4gLgWXhSlB4uc6 tigk42JOqBObhSMkx6Eokz4KcWa28UvZg10NMcL0lmaRZen6MPZ/1sWVpDy1oRT38bLH XUcg== X-Forwarded-Encrypted: i=1; AFNElJ/Avk5DFE1/Vh3q2X/WmKlPlIbxE/jt6jp6r2vVvXyTEmFOmUIH5AUmgrZnpGXpiw6MUZx4tWbdsIJGEjA=@vger.kernel.org X-Gm-Message-State: AOJu0YzSDdsLPrfHnpYu1K5C/HI2axKcOO6HcnxqS6K7JiTHykyL2d9T 7hCEEU7kU2+1p4JB4UJRKkVK8iTaWvjtCa9pNZjhj81vUihScrPG/G0N X-Gm-Gg: Acq92OFWOY+/A9bYZveREQOLUAZCBFMajjVzqw8Xl1/w37ibDmUEW0GSE4pUav3uVJf uIo2MoM9i4qZdEzt+ENsLsq8ZzI2vNIfIulI1O68FVTGRyJWMyX9zOWZOiV5ayr/V3RKvWSLbpu 34tQudMC5N840YOhftRtZcKyvN5EVh8GhhR5ztPGjozvkVs8QMe1+JiD/JAdFdckYpezdyTdBas Utcx4naJ7hywbCYHBLIxTh1F0rA4BL896XZZgSLYSZ8avSD6XX6H+xW+q+EcID1fRDxasVifmSA hLzdR15RwOyX20ZnNxGtu4tE0tacYke6YIuylm+3T09cksEEviJEzi6ZE2nmKn44jecqaNGisah aENlDiaMP6DIszsqWccpY4tm1YdbNTTLVY7m4/GHyjxNLZrgLeeze5sE6skLxpaGaLjChM0U8Xp O99JaaPKTap8sn3R8JKHYdFyM8cN20UhdsqYGdzBo= X-Received: by 2002:a05:6214:ac5:b0:8ca:10aa:e1f9 with SMTP id 6a1803df08f44-8ca10aae3f5mr116609836d6.42.1778903267574; Fri, 15 May 2026 20:47:47 -0700 (PDT) Received: from tresc054937.tre-sc.gov.br ([187.65.210.13]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c908d1d2e7sm70874546d6.16.2026.05.15.20.47.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 20:47:46 -0700 (PDT) From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 00:46:23 -0300 Subject: [net-next PATCH v4 7/8] net: dsa: realtek: rtl8365mb: add FDB support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260516-realtek_forward-v4-7-8b6d6a1eefdc@gmail.com> References: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> In-Reply-To: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> To: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Linus Walleij , =?utf-8?q?Alvin_=C5=A0ipraga?= , Yury Norov , Rasmus Villemoes , Russell King Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Luiz Angelo Daros de Luca X-Mailer: b4 0.15.2 From: Alvin =C5=A0ipraga Implement support for FDB and MDB management for the RTL8365MB series switches. The hardware supports IVL by keying the forwarding database with the {VID, MAC, EFID} tuple. The Extended Filtering ID (EFID) is 3 bits wide, providing 8 unique filtering domains. This driver reserves EFID 0 for standalone ports, effectively limiting the hardware offload to a maximum of 7 bridges. Introduce a mutex lock (l2_lock) to protect concurrent L2 table updates. Add support for forwarding database operations, including unicast and multicast entry handling as well as fast aging support. Set DSA switch flags assisted_learning_on_cpu_port and fdb_isolation. Co-developed-by: Alvin =C5=A0ipraga Signed-off-by: Alvin =C5=A0ipraga Reviewed-by: Linus Walleij Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/Makefile | 1 + drivers/net/dsa/realtek/realtek.h | 29 ++ drivers/net/dsa/realtek/rtl8365mb_l2.c | 554 +++++++++++++++++++++++++++= ++++ drivers/net/dsa/realtek/rtl8365mb_l2.h | 32 ++ drivers/net/dsa/realtek/rtl8365mb_main.c | 21 +- drivers/net/dsa/realtek/rtl83xx.c | 294 ++++++++++++++++ drivers/net/dsa/realtek/rtl83xx.h | 16 + 7 files changed, 946 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Mak= efile index b7fc4e852fd8..6c329e046d0b 100644 --- a/drivers/net/dsa/realtek/Makefile +++ b/drivers/net/dsa/realtek/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) +=3D rtl8365mb.o rtl8365mb-objs :=3D rtl8365mb_main.o \ rtl8365mb_table.o \ rtl8365mb_vlan.o \ + rtl8365mb_l2.o \ diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/re= altek.h index 0942f534834d..1547ee71c2bd 100644 --- a/drivers/net/dsa/realtek/realtek.h +++ b/drivers/net/dsa/realtek/realtek.h @@ -45,6 +45,12 @@ struct rtl8366_vlan_4k { u8 fid; }; =20 +struct realtek_fdb_entry { + u8 mac_addr[ETH_ALEN]; + u16 vid; + bool is_static; +}; + struct realtek_priv { struct device *dev; struct reset_control *reset_ctl; @@ -54,6 +60,15 @@ struct realtek_priv { struct regmap *map; struct regmap *map_nolock; struct mutex map_lock; + /* l2_lock is used to prevent concurrent modifications of L2 table + * entries while another function is reading it. l2_(add,del)_mc + * is an example that first read current table entry and then + * create/update it. l2_(add|del)_uc uses a single table op and, + * internally, it might not need this lock. However, altering FDB + * may still collide, as well as l2_flush, with fdb_dump iterating + * over FDB. + */ + struct mutex l2_lock; struct mii_bus *user_mii_bus; struct mii_bus *bus; int mdio_addr; @@ -112,6 +127,19 @@ struct realtek_ops { int (*port_remove_isolation)(struct realtek_priv *priv, int port, u32 mask); int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid); + int (*l2_add_uc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], + u16 efid, u16 vid); + int (*l2_del_uc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], + u16 efid, u16 vid); + int (*l2_get_next_uc)(struct realtek_priv *priv, u16 *addr, + int port, struct realtek_fdb_entry *entry); + int (*l2_add_mc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], u16 vid); + int (*l2_del_mc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], u16 vid); + int (*l2_flush)(struct realtek_priv *priv, int port, u16 vid); int (*phy_read)(struct realtek_priv *priv, int phy, int regnum); int (*phy_write)(struct realtek_priv *priv, int phy, int regnum, u16 val); @@ -124,6 +152,7 @@ struct realtek_variant { unsigned int clk_delay; u8 cmd_read; u8 cmd_write; + u16 l2_table_size; size_t chip_data_sz; }; =20 diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realt= ek/rtl8365mb_l2.c new file mode 100644 index 000000000000..28cfbb3e344e --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Forwarding and multicast database interface for the rtl8365mb switch fa= mily + * + * Copyright (C) 2022 Alvin =C5=A0ipraga + */ + +#include + +#include "rtl8365mb_l2.h" +#include "rtl8365mb_table.h" +#include + +#define RTL8365MB_L2_ENTRY_SIZE 6 + +#define RTL8365MB_L2_UC_D0_MAC5_MSK GENMASK(7, 0) +#define RTL8365MB_L2_UC_D0_MAC4_MSK GENMASK(15, 8) +#define RTL8365MB_L2_UC_D1_MAC3_MSK GENMASK(7, 0) +#define RTL8365MB_L2_UC_D1_MAC2_MSK GENMASK(15, 8) +#define RTL8365MB_L2_UC_D2_MAC1_MSK GENMASK(7, 0) +#define RTL8365MB_L2_UC_D2_MAC0_MSK GENMASK(15, 8) +#define RTL8365MB_L2_UC_D3_VID_MSK GENMASK(11, 0) +#define RTL8365MB_L2_UC_D3_IVL_MSK GENMASK(13, 13) +#define RTL8365MB_L2_UC_D3_PORT_EXT_MSK GENMASK(15, 15) +#define RTL8365MB_L2_UC_PORT_HI_MSK GENMASK(3, 3) +#define RTL8365MB_L2_UC_D4_EFID_MSK GENMASK(2, 0) +#define RTL8365MB_L2_UC_D4_FID_MSK GENMASK(6, 3) +#define RTL8365MB_L2_UC_D4_SA_PRI_MSK GENMASK(7, 7) +#define RTL8365MB_L2_UC_D4_PORT_MSK GENMASK(10, 8) +#define RTL8365MB_L2_UC_PORT_LO_MSK GENMASK(2, 0) +#define RTL8365MB_L2_UC_D4_AGE_MSK GENMASK(13, 11) +#define RTL8365MB_L2_UC_D4_AUTH_MSK GENMASK(14, 14) +#define RTL8365MB_L2_UC_D4_SA_BLOCK_MSK GENMASK(15, 15) + +#define RTL8365MB_L2_UC_D5_DA_BLOCK_MSK GENMASK(0, 0) +#define RTL8365MB_L2_UC_D5_PRIORITY_MSK GENMASK(3, 1) +#define RTL8365MB_L2_UC_D5_FWD_PRI_MSK GENMASK(4, 4) +#define RTL8365MB_L2_UC_D5_STATIC_MSK GENMASK(5, 5) + +#define RTL8365MB_L2_MC_D0_MAC5_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_D0_MAC4_MSK GENMASK(15, 8) +#define RTL8365MB_L2_MC_D1_MAC3_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_D1_MAC2_MSK GENMASK(15, 8) +#define RTL8365MB_L2_MC_D2_MAC1_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_D2_MAC0_MSK GENMASK(15, 8) +#define RTL8365MB_L2_MC_D3_VID_MSK GENMASK(11, 0) +#define RTL8365MB_L2_MC_D3_IVL_MSK GENMASK(13, 13) +#define RTL8365MB_L2_MC_D3_MBR_HI1_MSK GENMASK(15, 14) +#define RTL8365MB_L2_MC_MBR_HI1_MSK GENMASK(9, 8) + +#define RTL8365MB_L2_MC_D4_MBR_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_MBR_LO_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_D4_IGMPIDX_MSK GENMASK(15, 8) + +#define RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK GENMASK(0, 0) +#define RTL8365MB_L2_MC_D5_PRIORITY_MSK GENMASK(3, 1) +#define RTL8365MB_L2_MC_D5_FWD_PRI_MSK GENMASK(4, 4) +#define RTL8365MB_L2_MC_D5_STATIC_MSK GENMASK(5, 5) +#define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7) +#define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10) + +/* Port flush command registers - writing a 1 to the port's MASK bit will + * initiate the flush procedure. Completion is signalled when the correspo= nding + * BUSY bit is 0. + */ +#define RTL8365MB_L2_FLUSH_PORT_REG 0x0A36 +#define RTL8365MB_L2_FLUSH_PORT_MSK_MSK GENMASK(7, 0) +#define RTL8365MB_L2_FLUSH_PORT_BUSY_MSK GENMASK(15, 8) + +#define RTL8365MB_L2_FLUSH_PORT_EXT_REG 0x0A35 +#define RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK GENMASK(2, 0) +#define RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK GENMASK(5, 3) + +#define RTL8365MB_L2_FLUSH_CTRL1_REG 0x0A37 +#define RTL8365MB_L2_FLUSH_CTRL1_VID_MSK GENMASK(11, 0) +#define RTL8365MB_L2_FLUSH_CTRL1_FID_MSK GENMASK(15, 12) + +#define RTL8365MB_L2_FLUSH_CTRL2_REG 0x0A38 +#define RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK GENMASK(1, 0) +#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT 0 +#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID 1 +#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_FID 2 +#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK GENMASK(2, 2) +#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC 0 +#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_BOTH 1 + +/* This flushes the entire LUT, reading it back it will turn 0 when the + * operation is complete + */ +#define RTL8365MB_L2_FLUSH_CTRL3_REG 0x0A39 +#define RTL8365MB_L2_FLUSH_CTRL3_MSK GENMASK(0, 0) + +struct rtl8365mb_l2_uc_key { + u8 mac_addr[ETH_ALEN]; + u16 vid; + u16 fid; + bool ivl; + u16 efid; +}; + +struct rtl8365mb_l2_uc { + struct rtl8365mb_l2_uc_key key; + u8 port; + u8 age; + u8 priority; + + bool sa_block; + bool da_block; + bool auth; + bool is_static; + bool sa_pri; + bool fwd_pri; +}; + +struct rtl8365mb_l2_mc_key { + u8 mac_addr[ETH_ALEN]; + union { + u16 vid; /* IVL */ + u16 fid; /* SVL */ + }; + bool ivl; +}; + +struct rtl8365mb_l2_mc { + struct rtl8365mb_l2_mc_key key; + u16 member; + u8 priority; + u8 igmpidx; + + bool is_static; + bool fwd_pri; + bool igmp_asic; +}; + +static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_u= c *uc) +{ + u32 val; + + uc->key.mac_addr[5] =3D FIELD_GET(RTL8365MB_L2_UC_D0_MAC5_MSK, data[0]); + uc->key.mac_addr[4] =3D FIELD_GET(RTL8365MB_L2_UC_D0_MAC4_MSK, data[0]); + uc->key.mac_addr[3] =3D FIELD_GET(RTL8365MB_L2_UC_D1_MAC3_MSK, data[1]); + uc->key.mac_addr[2] =3D FIELD_GET(RTL8365MB_L2_UC_D1_MAC2_MSK, data[1]); + uc->key.mac_addr[1] =3D FIELD_GET(RTL8365MB_L2_UC_D2_MAC1_MSK, data[2]); + uc->key.mac_addr[0] =3D FIELD_GET(RTL8365MB_L2_UC_D2_MAC0_MSK, data[2]); + uc->key.efid =3D FIELD_GET(RTL8365MB_L2_UC_D4_EFID_MSK, data[4]); + uc->key.vid =3D FIELD_GET(RTL8365MB_L2_UC_D3_VID_MSK, data[3]); + uc->key.ivl =3D FIELD_GET(RTL8365MB_L2_UC_D3_IVL_MSK, data[3]); + uc->key.fid =3D FIELD_GET(RTL8365MB_L2_UC_D4_FID_MSK, data[4]); + uc->age =3D FIELD_GET(RTL8365MB_L2_UC_D4_AGE_MSK, data[4]); + uc->auth =3D FIELD_GET(RTL8365MB_L2_UC_D4_AUTH_MSK, data[4]); + + val =3D FIELD_GET(RTL8365MB_L2_UC_D4_PORT_MSK, data[4]); + uc->port =3D FIELD_PREP(RTL8365MB_L2_UC_PORT_LO_MSK, val); + val =3D FIELD_GET(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, data[3]); + uc->port |=3D FIELD_PREP(RTL8365MB_L2_UC_PORT_HI_MSK, val); + + uc->sa_pri =3D FIELD_GET(RTL8365MB_L2_UC_D4_SA_PRI_MSK, data[4]); + uc->fwd_pri =3D FIELD_GET(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, data[5]); + uc->sa_block =3D FIELD_GET(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, data[4]); + uc->da_block =3D FIELD_GET(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, data[5]); + uc->priority =3D FIELD_GET(RTL8365MB_L2_UC_D5_PRIORITY_MSK, data[5]); + uc->is_static =3D FIELD_GET(RTL8365MB_L2_UC_D5_STATIC_MSK, data[5]); +} + +static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 = *data) +{ + u32 val; + + memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2); + data[0] |=3D + FIELD_PREP(RTL8365MB_L2_UC_D0_MAC5_MSK, uc->key.mac_addr[5]); + data[0] |=3D + FIELD_PREP(RTL8365MB_L2_UC_D0_MAC4_MSK, uc->key.mac_addr[4]); + data[1] |=3D + FIELD_PREP(RTL8365MB_L2_UC_D1_MAC3_MSK, uc->key.mac_addr[3]); + data[1] |=3D + FIELD_PREP(RTL8365MB_L2_UC_D1_MAC2_MSK, uc->key.mac_addr[2]); + data[2] |=3D + FIELD_PREP(RTL8365MB_L2_UC_D2_MAC1_MSK, uc->key.mac_addr[1]); + data[2] |=3D + FIELD_PREP(RTL8365MB_L2_UC_D2_MAC0_MSK, uc->key.mac_addr[0]); + data[3] |=3D FIELD_PREP(RTL8365MB_L2_UC_D3_VID_MSK, uc->key.vid); + data[3] |=3D FIELD_PREP(RTL8365MB_L2_UC_D3_IVL_MSK, uc->key.ivl); + + val =3D FIELD_GET(RTL8365MB_L2_UC_PORT_HI_MSK, uc->port); + data[3] |=3D FIELD_PREP(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, val); + + data[4] |=3D FIELD_PREP(RTL8365MB_L2_UC_D4_FID_MSK, uc->key.fid); + data[4] |=3D FIELD_PREP(RTL8365MB_L2_UC_D4_EFID_MSK, uc->key.efid); + data[4] |=3D FIELD_PREP(RTL8365MB_L2_UC_D4_AGE_MSK, uc->age); + data[4] |=3D FIELD_PREP(RTL8365MB_L2_UC_D4_AUTH_MSK, uc->auth); + + val =3D FIELD_GET(RTL8365MB_L2_UC_PORT_LO_MSK, uc->port); + data[4] |=3D FIELD_PREP(RTL8365MB_L2_UC_D4_PORT_MSK, val); + + data[4] |=3D FIELD_PREP(RTL8365MB_L2_UC_D4_SA_PRI_MSK, uc->sa_pri); + data[4] |=3D FIELD_PREP(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, uc->sa_block); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, uc->fwd_pri); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, uc->da_block); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_UC_D5_PRIORITY_MSK, uc->priority); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_UC_D5_STATIC_MSK, uc->is_static); +} + +static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_m= c *mc) +{ + u32 val; + + mc->key.mac_addr[5] =3D FIELD_GET(RTL8365MB_L2_MC_D0_MAC5_MSK, data[0]); + mc->key.mac_addr[4] =3D FIELD_GET(RTL8365MB_L2_MC_D0_MAC4_MSK, data[0]); + mc->key.mac_addr[3] =3D FIELD_GET(RTL8365MB_L2_MC_D1_MAC3_MSK, data[1]); + mc->key.mac_addr[2] =3D FIELD_GET(RTL8365MB_L2_MC_D1_MAC2_MSK, data[1]); + mc->key.mac_addr[1] =3D FIELD_GET(RTL8365MB_L2_MC_D2_MAC1_MSK, data[2]); + mc->key.mac_addr[0] =3D FIELD_GET(RTL8365MB_L2_MC_D2_MAC0_MSK, data[2]); + /* key.vid,key.fid shares the same memory space */ + mc->key.vid =3D FIELD_GET(RTL8365MB_L2_MC_D3_VID_MSK, data[3]); + mc->key.ivl =3D FIELD_GET(RTL8365MB_L2_MC_D3_IVL_MSK, data[3]); + mc->priority =3D FIELD_GET(RTL8365MB_L2_MC_D5_PRIORITY_MSK, data[5]); + mc->fwd_pri =3D FIELD_GET(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, data[5]); + mc->is_static =3D FIELD_GET(RTL8365MB_L2_MC_D5_STATIC_MSK, data[5]); + + val =3D FIELD_GET(RTL8365MB_L2_MC_D4_MBR_MSK, data[4]); + mc->member =3D FIELD_PREP(RTL8365MB_L2_MC_MBR_LO_MSK, val); + val =3D FIELD_GET(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, data[3]); + mc->member |=3D FIELD_PREP(RTL8365MB_L2_MC_MBR_HI1_MSK, val); + val =3D FIELD_GET(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, data[5]); + mc->member |=3D FIELD_PREP(RTL8365MB_L2_MC_MBR_HI2_MSK, val); + + mc->igmpidx =3D FIELD_GET(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, data[4]); + mc->igmp_asic =3D FIELD_GET(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, data[5]); +} + +static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 = *data) +{ + u32 val; + + memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2); + data[0] |=3D FIELD_PREP(RTL8365MB_L2_MC_D0_MAC5_MSK, mc->key.mac_addr[5]); + data[0] |=3D FIELD_PREP(RTL8365MB_L2_MC_D0_MAC4_MSK, mc->key.mac_addr[4]); + data[1] |=3D FIELD_PREP(RTL8365MB_L2_MC_D1_MAC3_MSK, mc->key.mac_addr[3]); + data[1] |=3D FIELD_PREP(RTL8365MB_L2_MC_D1_MAC2_MSK, mc->key.mac_addr[2]); + data[2] |=3D FIELD_PREP(RTL8365MB_L2_MC_D2_MAC1_MSK, mc->key.mac_addr[1]); + data[2] |=3D FIELD_PREP(RTL8365MB_L2_MC_D2_MAC0_MSK, mc->key.mac_addr[0]); + data[3] |=3D FIELD_PREP(RTL8365MB_L2_MC_D3_VID_MSK, mc->key.vid); + data[3] |=3D FIELD_PREP(RTL8365MB_L2_MC_D3_IVL_MSK, mc->key.ivl); + + val =3D FIELD_GET(RTL8365MB_L2_MC_MBR_HI1_MSK, mc->member); + data[3] |=3D FIELD_PREP(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, val); + + val =3D FIELD_GET(RTL8365MB_L2_MC_MBR_LO_MSK, mc->member); + data[4] |=3D FIELD_PREP(RTL8365MB_L2_MC_D4_MBR_MSK, val); + + data[4] |=3D FIELD_PREP(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, mc->igmpidx); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, mc->igmp_asic); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_MC_D5_PRIORITY_MSK, mc->priority); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, mc->fwd_pri); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_MC_D5_STATIC_MSK, mc->is_static); + + val =3D FIELD_GET(RTL8365MB_L2_MC_MBR_HI2_MSK, mc->member); + data[5] |=3D FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val); +} + +/** + * rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry + * @priv: realtek_priv pointer + * @addr: as input, the table index to start the walk + * as output, the found table index + * @port: restrict the walk on entries related to port + * @entry: returned L2 Unicast table entry + * + * This function get the next unicast L2 table entry starting from @addr + * and checking exclusively entries related to @port. If no more entries + * were found, the output @addr will be lower than the input @addr and @en= try + * will not be overwritten. + * + * Return: Returns 0 on success, a negative error on failure. + **/ +int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int por= t, + struct realtek_fdb_entry *entry) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] =3D { 0 }; + struct rtl8365mb_l2_uc uc; + int ret; + + ret =3D rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_READ, addr, + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, + port, data, RTL8365MB_L2_ENTRY_SIZE); + if (ret) + return ret; + + rtl8365mb_l2_data_to_uc(data, &uc); + + ether_addr_copy(entry->mac_addr, uc.key.mac_addr); + entry->vid =3D uc.key.vid; + entry->is_static =3D uc.is_static; + + return 0; +} + +int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 efid, u16 vid) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] =3D { 0 }; + struct rtl8365mb_l2_uc uc =3D { 0 }; + u16 addr; + int ret; + + memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN); + uc.key.efid =3D efid; + uc.key.fid =3D 0; + uc.key.ivl =3D true; + uc.key.vid =3D vid; + + uc.port =3D port; + /* do not let HW decrease age */ + uc.is_static =3D true; + /* age greater than 0 adds/updates entries */ + uc.age =3D 1; + rtl8365mb_l2_uc_to_data(&uc, data); + + /* add the new entry or update an existing one */ + ret =3D rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_WRITE, &addr, + 0, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + /* addr will hold the table index, but it is not used here */ + if (ret =3D=3D -ENOENT) { + /* -ENOENT means the just added entry was not found (and @addr + * does not hold the table index. Although any error will be + * treated equally by the caller, assume that the missing entry + * means the table is full (tested in real HW). + */ + return -ENOSPC; + } + return ret; +} + +int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 efid, u16 vid) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] =3D { 0 }; + struct rtl8365mb_l2_uc uc =3D { 0 }; + u16 addr; + int ret; + + memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN); + uc.key.efid =3D efid; + uc.key.fid =3D 0; + uc.key.ivl =3D true; + uc.key.vid =3D vid; + /* age 0 deletes the entry */ + uc.age =3D 0; + rtl8365mb_l2_uc_to_data(&uc, data); + + /* it looks like the switch will always add/update the entry, + * even when age is 0 or uc.key did not match an existing entry, + * just to immediately drop it because age is zero. You can still + * get the added/updated address from @addr + */ + ret =3D rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_WRITE, &addr, + 0, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + /* addr will hold the table index, but it is not used here */ + return ret; +} + +int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid) +{ + int mode =3D vid ? RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID : + RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT; + u32 val, mask; + int ret; + + mutex_lock(&priv->map_lock); + + /* Configure flushing mode; only flush dynamic entries */ + ret =3D regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL2_REG, + FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK, + mode) | + FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK, + RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC)); + if (ret) + goto out; + + ret =3D regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL1_REG, + FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL1_VID_MSK, vid)); + + if (ret) + goto out; + /* Now issue the flush command and wait for its completion. There are + * two registers for this purpose, and which one to use depends on the + * port number. The _EXT register is for ports 8 or higher. + */ + if (port < 8) { + val =3D FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_MSK_MSK, + BIT(port) & 0xFF); + ret =3D regmap_write(priv->map_nolock, + RTL8365MB_L2_FLUSH_PORT_REG, val); + if (ret) + goto out; + + mask =3D FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_BUSY_MSK, + BIT(port) & 0xFF); + ret =3D regmap_read_poll_timeout(priv->map_nolock, + RTL8365MB_L2_FLUSH_PORT_REG, + val, !(val & mask), 10, 100); + if (ret) + goto out; + } else { + val =3D FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK, + BIT(port) >> 8); + ret =3D regmap_write(priv->map_nolock, + RTL8365MB_L2_FLUSH_PORT_EXT_REG, val); + if (ret) + goto out; + + mask =3D FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK, + BIT(port) >> 8); + ret =3D regmap_read_poll_timeout(priv->map_nolock, + RTL8365MB_L2_FLUSH_PORT_EXT_REG, + val, !(val & mask), 10, 100); + if (ret) + goto out; + } + +out: + mutex_unlock(&priv->map_lock); + + return ret; +} + +int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 vid) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] =3D { 0 }; + struct rtl8365mb_l2_mc mc =3D { 0 }; + u16 addr; + int ret; + + memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); + mc.key.vid =3D vid; + mc.key.ivl =3D true; + /* Already set the port and is_static, although not used in OP_READ, + * data will be ready for OP_WRITE if it is a new entry. + */ + mc.member |=3D BIT(port); + mc.is_static =3D 1; + rtl8365mb_l2_mc_to_data(&mc, data); + + /* First look for an existing entry (to get existing port members) */ + ret =3D rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_READ, &addr, + RTL8365MB_TABLE_L2_METHOD_MAC, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + if (!ret) { + /* There is already an entry... */ + rtl8365mb_l2_data_to_mc(data, &mc); + dev_dbg(priv->dev, "l2_add_mc: found existing addr=3D%d member=3D0x%x ig= mpidx=3D0x%x static=3D%d\n", + addr, mc.member, mc.igmpidx, mc.is_static); + /* the port must be added as a member */ + mc.member |=3D BIT(port); + rtl8365mb_l2_mc_to_data(&mc, data); + } else if (ret =3D=3D -ENOENT) { + /* New entry, no need to update data again as it already + * includes the member. + * + * Multicast hardware entries do not support EFID (bridge + * isolation). However, traffic isolation is still maintained + * because the hardware applies the port isolation masks + * (pmasks) configured in bridge_join after the L2 lookup. + * Entries from different bridges will collide on the same + * MAC+VID slot with an OR'ed member mask, but packets will + * only exit through ports allowed by the source port's pmask. + */ + } else { + return ret; + } + + /* add the new entry or update an existing one */ + ret =3D rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_WRITE, &addr, + 0, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + /* addr will hold the table index, but it is not used here */ + if (ret =3D=3D -ENOENT) { + /* -ENOENT means the just added entry was not found (and @addr + * does not hold the table index. Although any error will be + * treated equally by the caller, assume that the missing entry + * means the table is full (tested in real HW). + */ + return -ENOSPC; + } + + return ret; +} + +int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 vid) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] =3D { 0 }; + struct rtl8365mb_l2_mc mc =3D { 0 }; + u16 addr; + int ret; + + memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); + mc.key.vid =3D vid; + mc.key.ivl =3D true; + rtl8365mb_l2_mc_to_data(&mc, data); + + /* First look for an existing entry (to get existing port members) */ + ret =3D rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_READ, &addr, + RTL8365MB_TABLE_L2_METHOD_MAC, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + if (ret =3D=3D -ENOENT) { + dev_dbg(priv->dev, "l2_del_mc: addr=3D%d vid=3D%d missing\n", + addr, vid); + /* Silently return success */ + return 0; + } + + if (ret) + /* Return on any other error */ + return ret; + + rtl8365mb_l2_data_to_mc(data, &mc); + dev_dbg(priv->dev, "l2_del_mc: found existing addr=3D%d member=3D0x%x igm= pidx=3D0x%x static=3D%d\n", + addr, mc.member, mc.igmpidx, mc.is_static); + /* the port must be removed as a member */ + mc.member &=3D ~BIT(port); + if (!mc.member) { + /* With no members, zero all non-key fields to delete the + * entry. However, setting is_static =3D 0 might be enough to + * signal the HW to release the slot. + */ + mc.is_static =3D 0; + mc.igmpidx =3D 0; + mc.priority =3D 0; + mc.fwd_pri =3D 0; + mc.igmp_asic =3D 0; + } + rtl8365mb_l2_mc_to_data(&mc, data); + + /* update the existing entry. */ + ret =3D rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_WRITE, &addr, + 0, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + return ret; +} diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realt= ek/rtl8365mb_l2.h new file mode 100644 index 000000000000..9470cf059ce5 --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Forwarding and multicast database interface for the rtl8365mb switch fa= mily + * + * Copyright (C) 2022 Alvin =C5=A0ipraga + */ + +#ifndef _REALTEK_RTL8365MB_L2_H +#define _REALTEK_RTL8365MB_L2_H + +#include +#include + +#include "realtek.h" + +int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int por= t, + struct realtek_fdb_entry *entry); +int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, + const unsigned char addr[static ETH_ALEN], + u16 efid, u16 vid); +int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, + const unsigned char addr[static ETH_ALEN], + u16 efid, u16 vid); +int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid); + +int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 vid); +int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 vid); + +#endif /* _REALTEK_RTL8365MB_L2_H */ diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/rea= ltek/rtl8365mb_main.c index b951a2d4c7e3..e335ce12870d 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -104,6 +104,7 @@ #include "realtek-smi.h" #include "realtek-mdio.h" #include "rtl83xx.h" +#include "rtl8365mb_l2.h" #include "rtl8365mb_vlan.h" =20 /* Family-specific data and limits */ @@ -111,8 +112,9 @@ #define RTL8365MB_NUM_PHYREGS 32 #define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) #define RTL8365MB_MAX_NUM_PORTS 11 -#define RTL8365MB_MAX_NUM_EXTINTS 3 +/* Valid for the whole family except RTL8370B, which has 4160 entries. */ #define RTL8365MB_LEARN_LIMIT_MAX 2112 +#define RTL8365MB_MAX_NUM_EXTINTS 3 =20 /* Chip identification registers */ #define RTL8365MB_CHIP_ID_REG 0x1300 @@ -2250,6 +2252,8 @@ static int rtl8365mb_setup(struct dsa_switch *ds) mb =3D priv->chip_data; cpu =3D &mb->cpu; =20 + mutex_init(&priv->l2_lock); + ret =3D rtl8365mb_reset_chip(priv); if (ret) { dev_err(priv->dev, "failed to reset chip: %pe\n", @@ -2358,6 +2362,8 @@ static int rtl8365mb_setup(struct dsa_switch *ds) if (ret) goto out_teardown_irq; =20 + ds->assisted_learning_on_cpu_port =3D true; + ds->fdb_isolation =3D true; /* The EFID is 3 bits, but EFID 0 is reserved for standalone ports */ ds->max_num_bridges =3D FIELD_MAX(RTL8365MB_EFID_MASK); ds->configure_vlan_while_not_filtering =3D true; @@ -2479,6 +2485,12 @@ static const struct dsa_switch_ops rtl8365mb_switch_= ops =3D { .port_bridge_join =3D rtl83xx_port_bridge_join, .port_bridge_leave =3D rtl83xx_port_bridge_leave, .port_stp_state_set =3D rtl8365mb_port_stp_state_set, + .port_fast_age =3D rtl83xx_port_fast_age, + .port_fdb_add =3D rtl83xx_port_fdb_add, + .port_fdb_del =3D rtl83xx_port_fdb_del, + .port_fdb_dump =3D rtl83xx_port_fdb_dump, + .port_mdb_add =3D rtl83xx_port_mdb_add, + .port_mdb_del =3D rtl83xx_port_mdb_del, .port_vlan_add =3D rtl8365mb_port_vlan_add, .port_vlan_del =3D rtl8365mb_port_vlan_del, .port_vlan_filtering =3D rtl8365mb_port_vlan_filtering, @@ -2500,6 +2512,12 @@ static const struct realtek_ops rtl8365mb_ops =3D { .port_add_isolation =3D rtl8365mb_port_add_isolation, .port_remove_isolation =3D rtl8365mb_port_remove_isolation, .port_set_efid =3D rtl8365mb_port_set_efid, + .l2_add_uc =3D rtl8365mb_l2_add_uc, + .l2_del_uc =3D rtl8365mb_l2_del_uc, + .l2_get_next_uc =3D rtl8365mb_l2_get_next_uc, + .l2_add_mc =3D rtl8365mb_l2_add_mc, + .l2_del_mc =3D rtl8365mb_l2_del_mc, + .l2_flush =3D rtl8365mb_l2_flush, .phy_read =3D rtl8365mb_phy_read, .phy_write =3D rtl8365mb_phy_write, }; @@ -2511,6 +2529,7 @@ const struct realtek_variant rtl8365mb_variant =3D { .clk_delay =3D 10, .cmd_read =3D 0xb9, .cmd_write =3D 0xb8, + .l2_table_size =3D RTL8365MB_LEARN_LIMIT_MAX, .chip_data_sz =3D sizeof(struct rtl8365mb), }; =20 diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rt= l83xx.c index ae4945c917f2..b26bd1444f5d 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -466,6 +466,300 @@ void rtl83xx_port_bridge_leave(struct dsa_switch *ds,= int port, } EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, "REALTEK_DSA"); =20 +/** + * rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port + * @ds: DSA switch instance + * @port: port index + * + * This function requests the switch to age out dynamic FDB entries learne= d on + * @port. + * + * Context: Can sleep. + * Return: Nothing. + */ +void rtl83xx_port_fast_age(struct dsa_switch *ds, int port) +{ + struct realtek_priv *priv =3D ds->priv; + int ret; + + if (!priv->ops->l2_flush) { + dev_warn_once(priv->dev, "l2_flush op not defined\n"); + return; + } + + dev_dbg(priv->dev, "fast_age port %d\n", port); + + mutex_lock(&priv->l2_lock); + ret =3D priv->ops->l2_flush(priv, port, 0); + mutex_unlock(&priv->l2_lock); + if (ret) + dev_err(priv->dev, "failed to fast age on port %d: %d\n", port, + ret); +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fast_age, "REALTEK_DSA"); + +/** + * rtl83xx_port_fdb_add() - add a static FDB entry to a port database + * @ds: DSA switch instance + * @port: port index + * @addr: MAC address to add + * @vid: VLAN ID associated with @addr + * @db: database where the entry should be added + * + * This function adds a static unicast FDB entry to the standalone port + * database or to a bridge database. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + struct realtek_priv *priv =3D ds->priv; + int efid; + int ret; + + if (!priv->ops->l2_add_uc) + return -EOPNOTSUPP; + + if (db.type !=3D DSA_DB_PORT && db.type !=3D DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + /* + * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while + * DSA_DB_PORT use the default EFID (0), not used by any bridge. + * Standalone user ports share EFID (0) but have hardware learning + * disabled. The CPU port also uses EFID (0), caused by + * ds->assisted_learning_on_cpu_port =3D true + */ + efid =3D db.type =3D=3D DSA_DB_BRIDGE ? db.bridge.num : 0; + + dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", + __func__, port, addr, efid, vid, db.type); + + mutex_lock(&priv->l2_lock); + ret =3D priv->ops->l2_add_uc(priv, port, addr, efid, vid); + mutex_unlock(&priv->l2_lock); + + if (ret) + dev_err(priv->dev, "fdb_add ERROR %pe\n", ERR_PTR(ret)); + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_add, "REALTEK_DSA"); + +/** + * rtl83xx_port_fdb_del() - delete a static FDB entry from a port database + * @ds: DSA switch instance + * @port: port index + * @addr: MAC address to delete + * @vid: VLAN ID associated with @addr + * @db: database where the entry should be removed + * + * This function deletes a static unicast FDB entry from the standalone po= rt + * database or from a bridge database. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + struct realtek_priv *priv =3D ds->priv; + int efid; + int ret; + + if (!priv->ops->l2_del_uc) + return -EOPNOTSUPP; + + if (db.type !=3D DSA_DB_PORT && db.type !=3D DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + /* + * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while + * DSA_DB_PORT use the default EFID (0), not used by any bridge. + */ + efid =3D db.type =3D=3D DSA_DB_BRIDGE ? db.bridge.num : 0; + + dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", + __func__, port, addr, efid, vid, db.type); + + mutex_lock(&priv->l2_lock); + ret =3D priv->ops->l2_del_uc(priv, port, addr, efid, vid); + mutex_unlock(&priv->l2_lock); + + if (ret) + dev_err(priv->dev, "fdb_del ERROR %pe\n", ERR_PTR(ret)); + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_del, "REALTEK_DSA"); + +/** + * rtl83xx_port_fdb_dump() - iterate over FDB entries associated with a po= rt + * @ds: DSA switch instance + * @port: port index + * @cb: callback invoked for each entry + * @data: opaque pointer passed to @cb + * + * This function walks the unicast FDB entries associated with @port and c= alls + * @cb for each matching entry. + * + * Context: Can sleep. + * Return: 0 on success, -ENOENT when the table walk reaches the end, or + * another negative value for failure. + */ +int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct realtek_fdb_entry entry =3D { 0 }; + struct realtek_priv *priv =3D ds->priv; + u16 l2_table_size =3D priv->variant->l2_table_size; + u16 start_addr, addr =3D 0; + int ret =3D 0; + + if (!priv->ops->l2_get_next_uc) + return -EOPNOTSUPP; + if (!l2_table_size) { + dev_warn_once(priv->dev, "l2_table_size not defined\n"); + return -EOPNOTSUPP; + } + + mutex_lock(&priv->l2_lock); + while (true) { + start_addr =3D addr; + + dev_dbg(priv->dev, "l2_get_next_uc, addr:%d, port:%d\n", + addr, port); + ret =3D priv->ops->l2_get_next_uc(priv, &addr, port, &entry); + dev_dbg(priv->dev, + "%s addr:%d mac:%pM vid:%d static:%d ret:%pe\n", + __func__, addr, entry.mac_addr, entry.vid, + entry.is_static, ERR_PTR(ret)); + + if (ret =3D=3D -ENOENT) { + /* If the table is empty, returns without errors. Note + * that the l2_get_next_uc overflow to the first match + * when it reaches the end of the table. + */ + ret =3D 0; + break; + } + + if (ret) + break; + + /* When the addr returned is before the requested one, it + * indicates that we reached the end. + */ + if (addr < start_addr) + break; + + ret =3D cb(entry.mac_addr, entry.vid, entry.is_static, data); + if (ret) + break; + + addr++; + + /* Avoid querying beyond the valid L2 table range. */ + if (addr >=3D l2_table_size) + break; + } + mutex_unlock(&priv->l2_lock); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_dump, "REALTEK_DSA"); + +/** + * rtl83xx_port_mdb_add() - add a multicast database entry to a port datab= ase + * @ds: DSA switch instance + * @port: port index + * @mdb: multicast database entry to add + * @db: database where the entry should be added + * + * This function adds a multicast database entry to the standalone port + * database or to a bridge database. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db) +{ + struct realtek_priv *priv =3D ds->priv; + const unsigned char *addr =3D mdb->addr; + u16 vid =3D mdb->vid; + int efid; + int ret; + + if (!priv->ops->l2_add_mc) + return -EOPNOTSUPP; + + if (db.type !=3D DSA_DB_PORT && db.type !=3D DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + efid =3D db.type =3D=3D DSA_DB_BRIDGE ? db.bridge.num : 0; + + dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", + __func__, port, addr, efid, vid, db.type); + + mutex_lock(&priv->l2_lock); + ret =3D priv->ops->l2_add_mc(priv, port, addr, vid); + mutex_unlock(&priv->l2_lock); + + if (ret) + dev_err(priv->dev, "mdb_add ERROR %pe\n", ERR_PTR(ret)); + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_add, "REALTEK_DSA"); + +/** + * rtl83xx_port_mdb_del() - delete a multicast database entry from a port + * database + * @ds: DSA switch instance + * @port: port index + * @mdb: multicast database entry to delete + * @db: database where the entry should be removed + * + * This function deletes a multicast database entry from the standalone po= rt + * database or from a bridge database. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db) +{ + struct realtek_priv *priv =3D ds->priv; + const unsigned char *addr =3D mdb->addr; + u16 vid =3D mdb->vid; + int efid; + int ret; + + if (!priv->ops->l2_del_mc) + return -EOPNOTSUPP; + + if (db.type !=3D DSA_DB_PORT && db.type !=3D DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + efid =3D db.type =3D=3D DSA_DB_BRIDGE ? db.bridge.num : 0; + + dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", + __func__, port, addr, efid, vid, db.type); + + mutex_lock(&priv->l2_lock); + ret =3D priv->ops->l2_del_mc(priv, port, addr, vid); + mutex_unlock(&priv->l2_lock); + + if (ret) + dev_err(priv->dev, "mdb_del ERROR %pe\n", ERR_PTR(ret)); + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA"); + MODULE_AUTHOR("Luiz Angelo Daros de Luca "); MODULE_AUTHOR("Linus Walleij "); MODULE_DESCRIPTION("Realtek DSA switches common module"); diff --git a/drivers/net/dsa/realtek/rtl83xx.h b/drivers/net/dsa/realtek/rt= l83xx.h index 2481a1aaa226..dcb819fe567f 100644 --- a/drivers/net/dsa/realtek/rtl83xx.h +++ b/drivers/net/dsa/realtek/rtl83xx.h @@ -28,4 +28,20 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int = port, void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, struct dsa_bridge bridge); =20 +void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); +int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db); +int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db); +int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data); +int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db); +int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db); + #endif /* _RTL83XX_H */ --=20 2.54.0 From nobody Mon May 25 08:11:40 2026 Received: from mail-qv1-f52.google.com (mail-qv1-f52.google.com [209.85.219.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 62F522BE7DB for ; Sat, 16 May 2026 03:47:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903277; cv=none; b=BbOVBYQ0aDBFt5H+5I8CvU0TlutsX4npSIVjGaQZl2rWQHLErSP44G4n+QGAhzT/2OucK8h1p/LnbQqiv8ss22Qr93FgwEmBLwxQERQZQQ5SzSCRXkNWuD2Bc06rdUj4hm1+2zE89QJEIexE0MrOJUbBl7zN8iMqvUEtPEMezio= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778903277; c=relaxed/simple; bh=Hm/I+ps4tHYyKw/yM63GI+GDgsRgGYRs/zq1/gVikqk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KpPplPrtrT0eOS70CE4YVn+1/ViBebb0DV9/5Mp70C0NiY1J3dOTGkTJmQhhPbnmq+vV65rjreE17PX5qO+PbjX0UcFMAoPpqIH5EMclyCd36sWoomSh24rojT+MJ4s5p9ipM31ChvHqCN95z8kFBbwZF4D113dG76JfzY8VcLc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=WTH0Dj5A; arc=none smtp.client-ip=209.85.219.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="WTH0Dj5A" Received: by mail-qv1-f52.google.com with SMTP id 6a1803df08f44-8b59772d441so8510946d6.0 for ; Fri, 15 May 2026 20:47:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778903274; x=1779508074; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=9LbwsA5l4+BlsCiNrv3M/ZlLdpurb9GJPfXL7pM8rE4=; b=WTH0Dj5Adv4U0qqUXabDmyklSHYp3iBs82E9Xgf4vyBapP+Cvql9WEUGUEejBbswLF dicWi26iRZqJoSjq3KAUUuIl+uIIgyjllovUC0uljOAtntlOorhXlFZoaHYI2YyINRpF P3plgv9tsHbQOOvtBRZ0hjCdJ6uqOJRqLoYbz3lAZ3EUX1eQGuOLWSLlBBnR76+mglFG pdAGhYHq+XN+Zgd07riLy2lnBWuJnghYkB2O0amKJIOZCBi0ANAYlXRRmZPPnpN/JFRk wCWhc828ivJ/xkw8SdU8DCaZoehA2CXSgwlnmScqRMD3BoUICmK/lJPZt5+HhJoJKpEK q3Vg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778903274; x=1779508074; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=9LbwsA5l4+BlsCiNrv3M/ZlLdpurb9GJPfXL7pM8rE4=; b=WeFClNlWwm42gnx3BcHYYDqVE2fKOlaCWYZu5/bU2xaWGUkbBiLQUQF3ofNRLQSGYI bgv4Va+v/gO1DcnNcygMzPOHq5dC0mN3qkj9+93NUh/9BUP3arcfzcGJmya23cSv0M1v /k1EunrR9GI1ArScg5Hfwz2wY1K3XTH7zCuAT22A3h5pSO0hBWtkyJIiAzQUbKXleyUG 3VgevPAwwkEYRti09IHEPGN9WREGumGE6EfUb/B0p0qWUtZ3IZVudu+qe91s1l0Qd0ye HynRWUihJeuW6zIOLN4hyALC0Nv3hEDvltIpwqBFLbi3W9Pmz0FWEvi/5Gi9UptI+aRB 14Ow== X-Forwarded-Encrypted: i=1; AFNElJ9ae+vuyD2XBoBgGvAUyf2QOKj3BRVDTKC4OvglEk9y/BFdj+s1wEqN325hpdVIDcfJT90oMmTDCvt9dDU=@vger.kernel.org X-Gm-Message-State: AOJu0YztG4Yu+F1VZeMAQlW/w2hAyhmfowm/P3WO+sc0WTAfXdgMvvB/ 3dyBkOgGLQ5WlU6voZ9FkqnmSq7K+hebkjbaNHq/TbsdSguLUkSwj23q X-Gm-Gg: Acq92OE3WLjJgts1Svulll23SbWrQi5ow+DTgcXuIkPCyf9hVtclRA+2qV9/rJaRTY6 9w/n6gX4KNvhihEzMmVX6Xvtrffkg4dJUnfh3V7Rs6f17G/JEE3icaSHEE2Pr0BHa9UsUK7Ant+ Abhud4Y3p+wIh5mj8v0QQWx+j2mr/AP11069aVGkz1mee0n0SjEKNx0euopqMFHNTdZmEQGT91M i7FuevVm3VMJ2gBbHWM5rB0I20hUWT8HjfO2e8ht4IuKii//NfXt7X85T2NurGAvrW02kjYeC+l jfUVM1UrhPSpkoZWM39vYwSCkKeevnh01h8ZkHHVsOQKPGORzLSaF3hqktMiSbR25pjw6UnbFmr 9q4DYBbtMKKLrY8m7PHNmw+j9pkVFQz0HA9QHIHJJ8kHTUHAVC+v5Fe5bSNqIgmgMf0gADKGYie YyWu8bzkzza7TQfqiv/i1Wcb9NBP4+UBFSbCtQAKI= X-Received: by 2002:ad4:4eeb:0:b0:8bd:761f:c498 with SMTP id 6a1803df08f44-8ca0f66d688mr114707676d6.20.1778903274412; Fri, 15 May 2026 20:47:54 -0700 (PDT) Received: from tresc054937.tre-sc.gov.br ([187.65.210.13]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c908d1d2e7sm70874546d6.16.2026.05.15.20.47.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 20:47:53 -0700 (PDT) From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 00:46:24 -0300 Subject: [net-next PATCH v4 8/8] net: dsa: realtek: rtl8365mb: add bridge port flags Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260516-realtek_forward-v4-8-8b6d6a1eefdc@gmail.com> References: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> In-Reply-To: <20260516-realtek_forward-v4-0-8b6d6a1eefdc@gmail.com> To: Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Linus Walleij , =?utf-8?q?Alvin_=C5=A0ipraga?= , Yury Norov , Rasmus Villemoes , Russell King Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Mieczyslaw Nalewaj , Luiz Angelo Daros de Luca X-Mailer: b4 0.15.2 From: Alvin =C5=A0ipraga Implement support for bridge port flags to control learning and flooding behavior. This patch maps hardware functionalities to the following bridge flags: - BR_LEARNING - BR_FLOOD - BR_MCAST_FLOOD - BR_BCAST_FLOOD By default, all flooding types are enabled during port setup to ensure standard bridge behavior. Co-developed-by: Alvin =C5=A0ipraga Signed-off-by: Alvin =C5=A0ipraga Reviewed-by: Linus Walleij Reviewed-by: Mieczyslaw Nalewaj Signed-off-by: Luiz Angelo Daros de Luca --- drivers/net/dsa/realtek/realtek.h | 8 +++ drivers/net/dsa/realtek/rtl8365mb_main.c | 69 +++++++++++++++++++++ drivers/net/dsa/realtek/rtl83xx.c | 101 +++++++++++++++++++++++++++= ++++ drivers/net/dsa/realtek/rtl83xx.h | 4 ++ 4 files changed, 182 insertions(+) diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/re= altek.h index 1547ee71c2bd..30ce59e4129a 100644 --- a/drivers/net/dsa/realtek/realtek.h +++ b/drivers/net/dsa/realtek/realtek.h @@ -140,6 +140,14 @@ struct realtek_ops { int (*l2_del_mc)(struct realtek_priv *priv, int port, const unsigned char addr[ETH_ALEN], u16 vid); int (*l2_flush)(struct realtek_priv *priv, int port, u16 vid); + int (*port_set_learning)(struct realtek_priv *priv, int port, + bool enable); + int (*port_set_ucast_flood)(struct realtek_priv *priv, int port, + bool enable); + int (*port_set_mcast_flood)(struct realtek_priv *priv, int port, + bool enable); + int (*port_set_bcast_flood)(struct realtek_priv *priv, int port, + bool enable); int (*phy_read)(struct realtek_priv *priv, int phy, int regnum); int (*phy_write)(struct realtek_priv *priv, int phy, int regnum, u16 val); diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/rea= ltek/rtl8365mb_main.c index e335ce12870d..7ee8acec05cf 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -304,6 +304,21 @@ #define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) =20 +/* Unknown unicast DA flooding port mask */ +#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890 +#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK 0x07FF + +/* Unknown multicast DA flooding port mask */ +#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891 +#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK 0x07FF + +/* Broadcast flooding port mask */ +#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892 +#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK 0x07FF + +#define RTL8365MB_SUPPORTED_BRIDGE_FLAGS \ + (BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD) + /* Miscellaneous port configuration register, incl. VLAN egress mode */ #define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E #define RTL8365MB_PORT_MISC_CFG_REG(_p) \ @@ -1470,6 +1485,49 @@ static int rtl8365mb_port_set_learning(struct realte= k_priv *priv, int port, enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); } =20 +static int rtl8365mb_port_set_ucast_flood(struct realtek_priv *priv, int p= ort, + bool enable) +{ + /* Frames with unknown unicast DA will be flooded to a programmable + * port mask that by default includes all ports. Add or remove + * the specified port from this port mask accordingly. + */ + return regmap_update_bits(priv->map, + RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG, + BIT(port), enable ? BIT(port) : 0); +} + +static int rtl8365mb_port_set_mcast_flood(struct realtek_priv *priv, int p= ort, + bool enable) +{ + return regmap_update_bits(priv->map, + RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG, + BIT(port), enable ? BIT(port) : 0); +} + +static int rtl8365mb_port_set_bcast_flood(struct realtek_priv *priv, int p= ort, + bool enable) +{ + return regmap_update_bits(priv->map, + RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG, + BIT(port), enable ? BIT(port) : 0); +} + +static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port, + struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack) +{ + struct realtek_priv *priv =3D ds->priv; + + dev_dbg(priv->dev, "pre_bridge_flags port:%d flags:%lx supported:%lx", + port, flags.mask, RTL8365MB_SUPPORTED_BRIDGE_FLAGS); + + if (flags.mask & ~RTL8365MB_SUPPORTED_BRIDGE_FLAGS) + return -EINVAL; + + return 0; +} + static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port, u32 efid) { @@ -2306,6 +2364,11 @@ static int rtl8365mb_setup(struct dsa_switch *ds) if (ret) goto out_teardown_irq; =20 + /* Enable all types of flooding */ + ret =3D rtl83xx_setup_port_flood_control(priv, dp->index); + if (ret) + goto out_teardown_irq; + /* Set up per-port private data */ p->priv =3D priv; p->index =3D dp->index; @@ -2484,6 +2547,8 @@ static const struct dsa_switch_ops rtl8365mb_switch_o= ps =3D { .phylink_get_caps =3D rtl8365mb_phylink_get_caps, .port_bridge_join =3D rtl83xx_port_bridge_join, .port_bridge_leave =3D rtl83xx_port_bridge_leave, + .port_pre_bridge_flags =3D rtl8365mb_port_pre_bridge_flags, + .port_bridge_flags =3D rtl83xx_port_bridge_flags, .port_stp_state_set =3D rtl8365mb_port_stp_state_set, .port_fast_age =3D rtl83xx_port_fast_age, .port_fdb_add =3D rtl83xx_port_fdb_add, @@ -2518,6 +2583,10 @@ static const struct realtek_ops rtl8365mb_ops =3D { .l2_add_mc =3D rtl8365mb_l2_add_mc, .l2_del_mc =3D rtl8365mb_l2_del_mc, .l2_flush =3D rtl8365mb_l2_flush, + .port_set_learning =3D rtl8365mb_port_set_learning, + .port_set_ucast_flood =3D rtl8365mb_port_set_ucast_flood, + .port_set_mcast_flood =3D rtl8365mb_port_set_mcast_flood, + .port_set_bcast_flood =3D rtl8365mb_port_set_bcast_flood, .phy_read =3D rtl8365mb_phy_read, .phy_write =3D rtl8365mb_phy_write, }; diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rt= l83xx.c index b26bd1444f5d..141aa8abb12b 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -3,6 +3,7 @@ #include #include #include +#include =20 #include "realtek.h" #include "rtl83xx.h" @@ -760,6 +761,106 @@ int rtl83xx_port_mdb_del(struct dsa_switch *ds, int p= ort, } EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA"); =20 +/** + * rtl83xx_port_bridge_flags() - set port bridge flags + * @ds: DSA switch instance + * @port: port index + * @flags: bridge port flags + * @extack: netlink extended ack for reporting errors + * + * This function handles setting bridge port flags like learning and flood= ing. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port, + struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack) +{ + struct realtek_priv *priv =3D ds->priv; + bool enable; + int ret; + + if (flags.mask & BR_LEARNING) { + if (!priv->ops->port_set_learning) + return -EOPNOTSUPP; + + enable =3D !!(flags.val & BR_LEARNING); + ret =3D priv->ops->port_set_learning(priv, port, enable); + if (ret) + return ret; + } + + if (flags.mask & BR_FLOOD) { + if (!priv->ops->port_set_ucast_flood) + return -EOPNOTSUPP; + + enable =3D !!(flags.val & BR_FLOOD); + ret =3D priv->ops->port_set_ucast_flood(priv, port, enable); + if (ret) + return ret; + } + + if (flags.mask & BR_MCAST_FLOOD) { + if (!priv->ops->port_set_mcast_flood) + return -EOPNOTSUPP; + + enable =3D !!(flags.val & BR_MCAST_FLOOD); + ret =3D priv->ops->port_set_mcast_flood(priv, port, enable); + if (ret) + return ret; + } + + if (flags.mask & BR_BCAST_FLOOD) { + if (!priv->ops->port_set_bcast_flood) + return -EOPNOTSUPP; + + enable =3D !!(flags.val & BR_BCAST_FLOOD); + ret =3D priv->ops->port_set_bcast_flood(priv, port, enable); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, "REALTEK_DSA"); + +/** + * rtl83xx_setup_port_flood_control() - setup default flood control for a = port + * @priv: realtek_priv pointer + * @port: port index + * + * This function enables flooding for a given port. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port) +{ + int ret; + + if (priv->ops->port_set_ucast_flood) { + ret =3D priv->ops->port_set_ucast_flood(priv, port, true); + if (ret) + return ret; + } + + if (priv->ops->port_set_mcast_flood) { + ret =3D priv->ops->port_set_mcast_flood(priv, port, true); + if (ret) + return ret; + } + + if (priv->ops->port_set_bcast_flood) { + ret =3D priv->ops->port_set_bcast_flood(priv, port, true); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, "REALTEK_DSA"); + MODULE_AUTHOR("Luiz Angelo Daros de Luca "); MODULE_AUTHOR("Linus Walleij "); MODULE_DESCRIPTION("Realtek DSA switches common module"); diff --git a/drivers/net/dsa/realtek/rtl83xx.h b/drivers/net/dsa/realtek/rt= l83xx.h index dcb819fe567f..d86447121276 100644 --- a/drivers/net/dsa/realtek/rtl83xx.h +++ b/drivers/net/dsa/realtek/rtl83xx.h @@ -27,6 +27,10 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int = port, struct netlink_ext_ack *extack); void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, struct dsa_bridge bridge); +int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port, + struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack); +int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port); =20 void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, --=20 2.54.0