* [PATCH net-next v3 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
@ 2023-11-14 10:55 ` Romain Gantois
2023-11-16 12:22 ` Krzysztof Kozlowski
2023-11-14 10:55 ` [PATCH net-next v3 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally Romain Gantois
` (7 subsequent siblings)
8 siblings, 1 reply; 37+ messages in thread
From: Romain Gantois @ 2023-11-14 10:55 UTC (permalink / raw)
To: davem, Rob Herring, Krzysztof Kozlowski
Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
Add the DT binding for the IPQESS Ethernet switch subsystem, that
integrates a modified QCA8K switch and an EDMA MAC controller. It inherits
from a basic ethernet switch binding and adds three regmaps, a phandle and
reset line for the PSGMII, a phandle to the MDIO bus, a clock, and 32
interrupts.
Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
.../bindings/net/qcom,ipq4019-ess.yaml | 152 ++++++++++++++++++
1 file changed, 152 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
diff --git a/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml b/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
new file mode 100644
index 000000000000..85dff85e50b5
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
@@ -0,0 +1,152 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/qcom,ipq4019-ess.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm IPQ4019 Ethernet Switch Subsystem
+
+maintainers:
+ - Romain Gantois <romain.gantois@bootlin.com>
+
+$ref: ethernet-switch.yaml#
+
+properties:
+ compatible:
+ const: qcom,ipq4019-ess
+
+ reg:
+ items:
+ - description: Base ESS registers, which configure the integrated QCA8K switch.
+ - description: ESS PSGMII-related registers, which control VCO calibration and link
+ modes.
+ - description: ESS EDMA controller registers. The EDMA controller is an Ethernet
+ controller connected to the integrated switch's CPU port.
+ reg-names:
+ items:
+ - const: base
+ - const: psgmii_phy
+ - const: edma
+
+ resets:
+ items:
+ - description: Handle to the PSGMII reset line.
+ - description: Handle to the ESS reset line.
+
+ reset-names:
+ items:
+ - const: psgmii
+ - const: ess
+
+ clocks:
+ maxItems: 1
+ description: Handle to the GCC ESS clock
+
+ mdio:
+ maxItems: 1
+ description: Handle to the IPQ4019 MDIO Controller
+
+ interrupts:
+ maxItems: 32
+ description: One interrupt per tx and rx queue, the first 16 are rx queues
+ and the last 16 are the tx queues
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - resets
+ - reset-names
+ - clocks
+ - mdio
+ - interrupts
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,gcc-ipq4019.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ switch: switch@c000000 {
+ compatible = "qcom,ipq4019-ess";
+ reg = <0xc000000 0x80000>, <0x98000 0x800>, <0xc080000 0x80000>;
+ reg-names = "base", "psgmii_phy", "edma";
+ resets = <&gcc ESS_PSGMII_ARES>, <&gcc ESS_RESET>;
+ reset-names = "psgmii", "ess";
+ clocks = <&gcc GCC_ESS_CLK>;
+ mdio = <&mdio>;
+ interrupts = <GIC_SPI 65 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 66 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 67 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 68 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 69 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 70 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 71 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 73 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 74 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 75 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 76 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 77 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 78 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 79 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 80 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 240 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 241 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 242 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 243 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 244 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 245 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 246 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 247 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 248 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 249 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 250 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 251 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 252 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 253 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 254 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 255 IRQ_TYPE_EDGE_RISING>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ swport1: port@1 { /* MAC1 */
+ reg = <1>;
+ label = "lan1";
+ phy-handle = <ðphy0>;
+ phy-mode = "psgmii";
+ };
+
+ swport2: port@2 { /* MAC2 */
+ reg = <2>;
+ label = "lan2";
+ phy-handle = <ðphy1>;
+ phy-mode = "psgmii";
+ };
+
+ swport3: port@3 { /* MAC3 */
+ reg = <3>;
+ label = "lan3";
+ phy-handle = <ðphy2>;
+ phy-mode = "psgmii";
+ };
+
+ swport4: port@4 { /* MAC4 */
+ reg = <4>;
+ label = "lan4";
+ phy-handle = <ðphy3>;
+ phy-mode = "psgmii";
+ };
+
+ swport5: port@5 { /* MAC5 */
+ reg = <5>;
+ label = "wan";
+ phy-handle = <ðphy4>;
+ phy-mode = "psgmii";
+ };
+ };
+ };
+
+...
--
2.42.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch
2023-11-14 10:55 ` [PATCH net-next v3 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch Romain Gantois
@ 2023-11-16 12:22 ` Krzysztof Kozlowski
0 siblings, 0 replies; 37+ messages in thread
From: Krzysztof Kozlowski @ 2023-11-16 12:22 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
devicetree, thomas.petazzoni, Andrew Lunn, Florian Fainelli,
Heiner Kallweit, Russell King, linux-arm-kernel, Vladimir Oltean,
Luka Perkov, Robert Marko, Andy Gross, Bjorn Andersson,
Konrad Dybcio
On 14/11/2023 11:55, Romain Gantois wrote:
> Add the DT binding for the IPQESS Ethernet switch subsystem, that
> integrates a modified QCA8K switch and an EDMA MAC controller. It inherits
> from a basic ethernet switch binding and adds three regmaps, a phandle and
> reset line for the PSGMII, a phandle to the MDIO bus, a clock, and 32
> interrupts.
>
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
> ---
> .../bindings/net/qcom,ipq4019-ess.yaml | 152 ++++++++++++++++++
> 1 file changed, 152 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
>
> diff --git a/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml b/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
> new file mode 100644
> index 000000000000..85dff85e50b5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
> @@ -0,0 +1,152 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/qcom,ipq4019-ess.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm IPQ4019 Ethernet Switch Subsystem
> +
> +maintainers:
> + - Romain Gantois <romain.gantois@bootlin.com>
> +
> +$ref: ethernet-switch.yaml#
> +
> +properties:
> + compatible:
> + const: qcom,ipq4019-ess
> +
> + reg:
> + items:
> + - description: Base ESS registers, which configure the integrated QCA8K switch.
> + - description: ESS PSGMII-related registers, which control VCO calibration and link
> + modes.
> + - description: ESS EDMA controller registers. The EDMA controller is an Ethernet
> + controller connected to the integrated switch's CPU port.
Blank line
> + reg-names:
> + items:
> + - const: base
> + - const: psgmii_phy
> + - const: edma
> +
> + resets:
> + items:
> + - description: Handle to the PSGMII reset line.
Don't describe Devicetree, so handle (assuming you speak about phandle)
is redundant, so:
PSGMII reset line
If it is some other handle, please explain.
But then isn't PSGMII reset property of MDIO or PHY? It looks like you
add here properties from the PHY...
> + - description: Handle to the ESS reset line.
ESS reset line
> +
> + reset-names:
> + items:
> + - const: psgmii
> + - const: ess
> +
> + clocks:
> + maxItems: 1
> + description: Handle to the GCC ESS clock
Drop description.
> +
> + mdio:
> + maxItems: 1
> + description: Handle to the IPQ4019 MDIO Controller
> +
> + interrupts:
> + maxItems: 32
> + description: One interrupt per tx and rx queue, the first 16 are rx queues
> + and the last 16 are the tx queues
> +
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next v3 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
2023-11-14 10:55 ` [PATCH net-next v3 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch Romain Gantois
@ 2023-11-14 10:55 ` Romain Gantois
2023-11-14 18:15 ` Andrew Lunn
2023-11-14 10:55 ` [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Romain Gantois
` (6 subsequent siblings)
8 siblings, 1 reply; 37+ messages in thread
From: Romain Gantois @ 2023-11-14 10:55 UTC (permalink / raw)
To: davem, Rob Herring, Krzysztof Kozlowski
Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
The Qualcomm QCA8K Ethernet switch is already supported in the kernel, as a
DSA switch. However, the Qualcomm IPQ4019 SoC contains an internal modified
QCA8K switch that does not fit into the DSA model, since it uses an
out-of-band tagging protocol.
Move the qca8k.h header file out of the QCA8K DSA driver so that the
upcoming IPQ4019 QCA8K switchdev driver can include it.
Refactor qca8k-common into a separate module so that the IPQESS driver can
be built as a module.
Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
drivers/net/dsa/qca/Kconfig | 10 +++++
drivers/net/dsa/qca/Makefile | 5 ++-
drivers/net/dsa/qca/qca8k-8xxx.c | 2 +-
drivers/net/dsa/qca/qca8k-common.c | 45 +++++++++++++++++--
drivers/net/dsa/qca/qca8k-leds.c | 2 +-
.../net/dsa/qca => include/linux/dsa}/qca8k.h | 1 +
6 files changed, 58 insertions(+), 7 deletions(-)
rename {drivers/net/dsa/qca => include/linux/dsa}/qca8k.h (99%)
diff --git a/drivers/net/dsa/qca/Kconfig b/drivers/net/dsa/qca/Kconfig
index de9da469908b..37b8d938a7fc 100644
--- a/drivers/net/dsa/qca/Kconfig
+++ b/drivers/net/dsa/qca/Kconfig
@@ -11,6 +11,7 @@ config NET_DSA_AR9331
config NET_DSA_QCA8K
tristate "Qualcomm Atheros QCA8K Ethernet switch family support"
select NET_DSA_TAG_QCA
+ select NET_DSA_QCA8K_LIB
select REGMAP
help
This enables support for the Qualcomm Atheros QCA8K Ethernet
@@ -24,3 +25,12 @@ config NET_DSA_QCA8K_LEDS_SUPPORT
help
This enabled support for LEDs present on the Qualcomm Atheros
QCA8K Ethernet switch chips.
+
+config NET_DSA_QCA8K_LIB
+ tristate "Qualcomm Atheros QCA8K hardware support library"
+ select REGMAP
+ help
+ This enables the hardware support library for the Qualcomm
+ Atheros QCA8K Ethernet switch. It is used by the switchdev-based
+ IPQ4019 integrated switch driver and by the DSA QCA8K switch
+ driver.
diff --git a/drivers/net/dsa/qca/Makefile b/drivers/net/dsa/qca/Makefile
index ce66b1984e5f..05990339c04e 100644
--- a/drivers/net/dsa/qca/Makefile
+++ b/drivers/net/dsa/qca/Makefile
@@ -1,7 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o
obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
-qca8k-y += qca8k-common.o qca8k-8xxx.o
+qca8k-y += qca8k-8xxx.o
ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
qca8k-y += qca8k-leds.o
endif
+
+obj-$(CONFIG_NET_DSA_QCA8K_LIB) += qca8k-lib.o
+qca8k-lib-y := qca8k-common.o
diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index ec57d9d52072..210667755b00 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -20,8 +20,8 @@
#include <linux/gpio/consumer.h>
#include <linux/etherdevice.h>
#include <linux/dsa/tag_qca.h>
+#include <linux/dsa/qca8k.h>
-#include "qca8k.h"
#include "qca8k_leds.h"
static void
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index 9243eff8918d..56663770382e 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -8,10 +8,9 @@
#include <linux/netdevice.h>
#include <net/dsa.h>
+#include <linux/dsa/qca8k.h>
#include <linux/if_bridge.h>
-#include "qca8k.h"
-
#define MIB_DESC(_s, _o, _n) \
{ \
.size = (_s), \
@@ -62,16 +61,19 @@ const struct qca8k_mib_desc ar8327_mib[] = {
MIB_DESC(1, 0xa8, "RXUnicast"),
MIB_DESC(1, 0xac, "TXUnicast"),
};
+EXPORT_SYMBOL_GPL(ar8327_mib);
int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
{
return regmap_read(priv->regmap, reg, val);
}
+EXPORT_SYMBOL_GPL(qca8k_read);
int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
{
return regmap_write(priv->regmap, reg, val);
}
+EXPORT_SYMBOL_GPL(qca8k_write);
int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
{
@@ -100,6 +102,7 @@ const struct regmap_access_table qca8k_readable_table = {
.yes_ranges = qca8k_readable_ranges,
.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
};
+EXPORT_SYMBOL_GPL(qca8k_readable_table);
static int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
{
@@ -462,6 +465,7 @@ int qca8k_mib_init(struct qca8k_priv *priv)
mutex_unlock(&priv->reg_mutex);
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_mib_init);
void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
{
@@ -476,6 +480,7 @@ void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
else
regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
}
+EXPORT_SYMBOL_GPL(qca8k_port_set_status);
void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset,
uint8_t *data)
@@ -489,6 +494,7 @@ void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset,
for (i = 0; i < priv->info->mib_count; i++)
ethtool_sprintf(&data, "%s", ar8327_mib[i].name);
}
+EXPORT_SYMBOL_GPL(qca8k_get_strings);
void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
uint64_t *data)
@@ -522,6 +528,7 @@ void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
data[i] |= (u64)hi << 32;
}
}
+EXPORT_SYMBOL_GPL(qca8k_get_ethtool_stats);
int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
@@ -532,6 +539,7 @@ int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
return priv->info->mib_count;
}
+EXPORT_SYMBOL_GPL(qca8k_get_sset_count);
int qca8k_set_mac_eee(struct dsa_switch *ds, int port,
struct ethtool_eee *eee)
@@ -556,6 +564,7 @@ int qca8k_set_mac_eee(struct dsa_switch *ds, int port,
mutex_unlock(&priv->reg_mutex);
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_set_mac_eee);
int qca8k_get_mac_eee(struct dsa_switch *ds, int port,
struct ethtool_eee *e)
@@ -563,6 +572,7 @@ int qca8k_get_mac_eee(struct dsa_switch *ds, int port,
/* Nothing to do on the port's MAC */
return 0;
}
+EXPORT_SYMBOL_GPL(qca8k_get_mac_eee);
static int qca8k_port_configure_learning(struct dsa_switch *ds, int port,
bool learning)
@@ -613,6 +623,7 @@ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
qca8k_port_configure_learning(ds, port, learning);
}
+EXPORT_SYMBOL_GPL(qca8k_port_stp_state_set);
int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
struct switchdev_brport_flags flags,
@@ -623,6 +634,7 @@ int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
return 0;
}
+EXPORT_SYMBOL_GPL(qca8k_port_pre_bridge_flags);
int qca8k_port_bridge_flags(struct dsa_switch *ds, int port,
struct switchdev_brport_flags flags,
@@ -639,6 +651,7 @@ int qca8k_port_bridge_flags(struct dsa_switch *ds, int port,
return 0;
}
+EXPORT_SYMBOL_GPL(qca8k_port_bridge_flags);
int qca8k_port_bridge_join(struct dsa_switch *ds, int port,
struct dsa_bridge bridge,
@@ -675,6 +688,7 @@ int qca8k_port_bridge_join(struct dsa_switch *ds, int port,
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_port_bridge_join);
void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_bridge bridge)
@@ -703,6 +717,7 @@ void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
}
+EXPORT_SYMBOL_GPL(qca8k_port_bridge_leave);
void qca8k_port_fast_age(struct dsa_switch *ds, int port)
{
@@ -712,6 +727,7 @@ void qca8k_port_fast_age(struct dsa_switch *ds, int port)
qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
mutex_unlock(&priv->reg_mutex);
}
+EXPORT_SYMBOL_GPL(qca8k_port_fast_age);
int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
{
@@ -732,6 +748,7 @@ int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
QCA8K_ATU_AGE_TIME_MASK,
QCA8K_ATU_AGE_TIME(val));
}
+EXPORT_SYMBOL_GPL(qca8k_set_ageing_time);
int qca8k_port_enable(struct dsa_switch *ds, int port,
struct phy_device *phy)
@@ -746,6 +763,7 @@ int qca8k_port_enable(struct dsa_switch *ds, int port,
return 0;
}
+EXPORT_SYMBOL_GPL(qca8k_port_enable);
void qca8k_port_disable(struct dsa_switch *ds, int port)
{
@@ -754,6 +772,7 @@ void qca8k_port_disable(struct dsa_switch *ds, int port)
qca8k_port_set_status(priv, port, 0);
priv->port_enabled_map &= ~BIT(port);
}
+EXPORT_SYMBOL_GPL(qca8k_port_disable);
int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
{
@@ -792,11 +811,13 @@ int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_port_change_mtu);
int qca8k_port_max_mtu(struct dsa_switch *ds, int port)
{
return QCA8K_MAX_MTU;
}
+EXPORT_SYMBOL_GPL(qca8k_port_max_mtu);
int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
u16 port_mask, u16 vid)
@@ -808,6 +829,7 @@ int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
return qca8k_fdb_add(priv, addr, port_mask, vid,
QCA8K_ATU_STATUS_STATIC);
}
+EXPORT_SYMBOL_GPL(qca8k_port_fdb_insert);
int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
@@ -818,6 +840,7 @@ int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
}
+EXPORT_SYMBOL_GPL(qca8k_port_fdb_add);
int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
@@ -831,6 +854,7 @@ int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
return qca8k_fdb_del(priv, addr, port_mask, vid);
}
+EXPORT_SYMBOL_GPL(qca8k_port_fdb_del);
int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
dsa_fdb_dump_cb_t *cb, void *data)
@@ -854,6 +878,7 @@ int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
return 0;
}
+EXPORT_SYMBOL_GPL(qca8k_port_fdb_dump);
int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
@@ -869,6 +894,7 @@ int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid,
QCA8K_ATU_STATUS_STATIC);
}
+EXPORT_SYMBOL_GPL(qca8k_port_mdb_add);
int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
@@ -883,10 +909,11 @@ int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
}
+EXPORT_SYMBOL_GPL(qca8k_port_mdb_del);
int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
- struct dsa_mall_mirror_tc_entry *mirror,
- bool ingress, struct netlink_ext_ack *extack)
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress, struct netlink_ext_ack *extack)
{
struct qca8k_priv *priv = ds->priv;
int monitor_port, ret;
@@ -938,6 +965,7 @@ int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
return 0;
}
+EXPORT_SYMBOL_GPL(qca8k_port_mirror_add);
void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
struct dsa_mall_mirror_tc_entry *mirror)
@@ -974,6 +1002,7 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
err:
dev_err(priv->dev, "Failed to del mirror port from %d", port);
}
+EXPORT_SYMBOL_GPL(qca8k_port_mirror_del);
int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
bool vlan_filtering,
@@ -994,6 +1023,7 @@ int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_port_vlan_filtering);
int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
@@ -1024,6 +1054,7 @@ int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_port_vlan_add);
int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
@@ -1037,6 +1068,7 @@ int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_port_vlan_del);
static bool qca8k_lag_can_offload(struct dsa_switch *ds,
struct dsa_lag lag,
@@ -1207,12 +1239,14 @@ int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag,
return qca8k_lag_refresh_portmap(ds, port, lag, false);
}
+EXPORT_SYMBOL_GPL(qca8k_port_lag_join);
int qca8k_port_lag_leave(struct dsa_switch *ds, int port,
struct dsa_lag lag)
{
return qca8k_lag_refresh_portmap(ds, port, lag, true);
}
+EXPORT_SYMBOL_GPL(qca8k_port_lag_leave);
int qca8k_read_switch_id(struct qca8k_priv *priv)
{
@@ -1242,3 +1276,6 @@ int qca8k_read_switch_id(struct qca8k_priv *priv)
return 0;
}
+EXPORT_SYMBOL_GPL(qca8k_read_switch_id);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/qca/qca8k-leds.c b/drivers/net/dsa/qca/qca8k-leds.c
index 90e30c2909e4..eca5abda5e77 100644
--- a/drivers/net/dsa/qca/qca8k-leds.c
+++ b/drivers/net/dsa/qca/qca8k-leds.c
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/property.h>
#include <linux/regmap.h>
+#include <linux/dsa/qca8k.h>
#include <net/dsa.h>
-#include "qca8k.h"
#include "qca8k_leds.h"
static u32 qca8k_phy_to_port(int phy)
diff --git a/drivers/net/dsa/qca/qca8k.h b/include/linux/dsa/qca8k.h
similarity index 99%
rename from drivers/net/dsa/qca/qca8k.h
rename to include/linux/dsa/qca8k.h
index 2ac7e88f8da5..3c75c3704fa0 100644
--- a/drivers/net/dsa/qca/qca8k.h
+++ b/include/linux/dsa/qca8k.h
@@ -13,6 +13,7 @@
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/dsa/tag_qca.h>
+#include <net/dsa.h>
#define QCA8K_ETHERNET_MDIO_PRIORITY 7
#define QCA8K_ETHERNET_PHY_PRIORITY 6
--
2.42.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally
2023-11-14 10:55 ` [PATCH net-next v3 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally Romain Gantois
@ 2023-11-14 18:15 ` Andrew Lunn
0 siblings, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2023-11-14 18:15 UTC (permalink / raw)
To: Romain Gantois
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
> int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
> - struct dsa_mall_mirror_tc_entry *mirror,
> - bool ingress, struct netlink_ext_ack *extack)
> + struct dsa_mall_mirror_tc_entry *mirror,
> + bool ingress, struct netlink_ext_ack *extack)
This looks like a whitespece change which should not be here.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
2023-11-14 10:55 ` [PATCH net-next v3 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch Romain Gantois
2023-11-14 10:55 ` [PATCH net-next v3 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally Romain Gantois
@ 2023-11-14 10:55 ` Romain Gantois
2023-11-14 19:10 ` Andrew Lunn
` (5 more replies)
2023-11-14 10:55 ` [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Romain Gantois
` (5 subsequent siblings)
8 siblings, 6 replies; 37+ messages in thread
From: Romain Gantois @ 2023-11-14 10:55 UTC (permalink / raw)
To: davem, Rob Herring, Krzysztof Kozlowski
Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
The Qualcomm IPQ4019 Ethernet Switch Subsystem for the IPQ4019 chip
includes an internal Ethernet switch based on the QCA8K IP.
The CPU-to-switch port data plane depends on the IPQESS EDMA Controller,
a simple 1G Ethernet controller. It is connected to the switch through an
internal link, and doesn't expose directly any external interface.
The EDMA controller has 16 RX and TX queues, with a very basic RSS fanout
configured at init time.
Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
MAINTAINERS | 7 +
drivers/net/dsa/qca/qca8k-common.c | 47 +-
drivers/net/ethernet/qualcomm/Kconfig | 14 +
drivers/net/ethernet/qualcomm/Makefile | 2 +
drivers/net/ethernet/qualcomm/ipqess/Makefile | 8 +
.../ethernet/qualcomm/ipqess/ipqess_edma.c | 1188 +++++++++++++++++
.../ethernet/qualcomm/ipqess/ipqess_edma.h | 488 +++++++
.../qualcomm/ipqess/ipqess_notifiers.h | 29 +
.../ethernet/qualcomm/ipqess/ipqess_port.c | 757 +++++++++++
.../ethernet/qualcomm/ipqess/ipqess_port.h | 62 +
.../ethernet/qualcomm/ipqess/ipqess_switch.c | 546 ++++++++
.../ethernet/qualcomm/ipqess/ipqess_switch.h | 36 +
include/linux/dsa/qca8k.h | 47 +-
13 files changed, 3217 insertions(+), 14 deletions(-)
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/Makefile
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 350d00657f6b..ff5b0565c301 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17965,6 +17965,13 @@ F: Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml
F: drivers/mailbox/qcom-ipcc.c
F: include/dt-bindings/mailbox/qcom-ipcc.h
+QUALCOMM IPQ4019 ESS DRIVER
+M: Romain Gantois <romain.gantois@bootlin.com>
+L: netdev@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
+F: drivers/net/ethernet/qualcomm/ipqess/
+
QUALCOMM IPQ4019 USB PHY DRIVER
M: Robert Marko <robert.marko@sartura.hr>
M: Luka Perkov <luka.perkov@sartura.hr>
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index 56663770382e..a66a821ce4d6 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -79,6 +79,19 @@ int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
{
return regmap_update_bits(priv->regmap, reg, mask, write_val);
}
+EXPORT_SYMBOL_GPL(qca8k_rmw);
+
+int qca8k_set_bits(struct qca8k_priv *priv, u32 reg, u32 bits)
+{
+ return regmap_set_bits(priv->regmap, reg, bits);
+}
+EXPORT_SYMBOL_GPL(qca8k_set_bits);
+
+int qca8k_clear_bits(struct qca8k_priv *priv, u32 reg, u32 bits)
+{
+ return regmap_clear_bits(priv->regmap, reg, bits);
+}
+EXPORT_SYMBOL_GPL(qca8k_clear_bits);
static const struct regmap_range qca8k_readable_ranges[] = {
regmap_reg_range(0x0000, 0x00e4), /* Global control */
@@ -164,8 +177,8 @@ static void qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask,
QCA8K_ATU_TABLE_SIZE);
}
-static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
- int port)
+int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
+ int port)
{
u32 reg;
int ret;
@@ -199,9 +212,10 @@ static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
return 0;
}
+EXPORT_SYMBOL_GPL(qca8k_fdb_access);
-static int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb,
- int port)
+int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb,
+ int port)
{
int ret;
@@ -212,6 +226,7 @@ static int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb,
return qca8k_fdb_read(priv, fdb);
}
+EXPORT_SYMBOL_GPL(qca8k_fdb_next);
static int qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac,
u16 port_mask, u16 vid, u8 aging)
@@ -226,8 +241,8 @@ static int qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac,
return ret;
}
-static int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
- u16 port_mask, u16 vid)
+int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
+ u16 port_mask, u16 vid)
{
int ret;
@@ -238,6 +253,7 @@ static int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_fdb_del);
void qca8k_fdb_flush(struct qca8k_priv *priv)
{
@@ -245,9 +261,10 @@ void qca8k_fdb_flush(struct qca8k_priv *priv)
qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
mutex_unlock(&priv->reg_mutex);
}
+EXPORT_SYMBOL_GPL(qca8k_fdb_flush);
-static int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
- const u8 *mac, u16 vid, u8 aging)
+int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
+ const u8 *mac, u16 vid, u8 aging)
{
struct qca8k_fdb fdb = { 0 };
int ret;
@@ -282,9 +299,10 @@ static int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
mutex_unlock(&priv->reg_mutex);
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_fdb_search_and_insert);
-static int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
- const u8 *mac, u16 vid)
+int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
+ const u8 *mac, u16 vid)
{
struct qca8k_fdb fdb = { 0 };
int ret;
@@ -324,6 +342,7 @@ static int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
mutex_unlock(&priv->reg_mutex);
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_fdb_search_and_del);
static int qca8k_vlan_access(struct qca8k_priv *priv,
enum qca8k_vlan_cmd cmd, u16 vid)
@@ -358,8 +377,8 @@ static int qca8k_vlan_access(struct qca8k_priv *priv,
return 0;
}
-static int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
- bool untagged)
+int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
+ bool untagged)
{
u32 reg;
int ret;
@@ -395,8 +414,9 @@ static int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_vlan_add);
-static int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
+int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
{
u32 reg, mask;
int ret, i;
@@ -438,6 +458,7 @@ static int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
return ret;
}
+EXPORT_SYMBOL_GPL(qca8k_vlan_del);
int qca8k_mib_init(struct qca8k_priv *priv)
{
diff --git a/drivers/net/ethernet/qualcomm/Kconfig b/drivers/net/ethernet/qualcomm/Kconfig
index 9210ff360fdc..008d20ec9eae 100644
--- a/drivers/net/ethernet/qualcomm/Kconfig
+++ b/drivers/net/ethernet/qualcomm/Kconfig
@@ -61,6 +61,20 @@ config QCOM_EMAC
low power, Receive-Side Scaling (RSS), and IEEE 1588-2008
Precision Clock Synchronization Protocol.
+config QCOM_IPQ4019_ESS
+ tristate "Qualcomm Atheros IPQ4019 Ethernet Switch Subsystem support"
+ depends on (OF && ARCH_QCOM) || COMPILE_TEST
+ select PHYLINK
+ select NET_DSA
+ select NET_DSA_QCA8K_LIB
+ select PAGE_POOL
+ help
+ This driver supports the Qualcomm Atheros IPQ40xx built-in
+ Ethernet Switch Subsystem.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ipqess.
+
source "drivers/net/ethernet/qualcomm/rmnet/Kconfig"
endif # NET_VENDOR_QUALCOMM
diff --git a/drivers/net/ethernet/qualcomm/Makefile b/drivers/net/ethernet/qualcomm/Makefile
index 9250976dd884..63c62704a62d 100644
--- a/drivers/net/ethernet/qualcomm/Makefile
+++ b/drivers/net/ethernet/qualcomm/Makefile
@@ -12,3 +12,5 @@ qcauart-objs := qca_uart.o
obj-y += emac/
obj-$(CONFIG_RMNET) += rmnet/
+
+obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess/
diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
new file mode 100644
index 000000000000..f389080cc5aa
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the IPQ ESS driver
+#
+
+obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
+
+ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
new file mode 100644
index 000000000000..008e92de9eb7
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
@@ -0,0 +1,1188 @@
+// SPDX-License-Identifier: GPL-2.0 OR ISC
+/* Copyright (c) 2014 - 2017, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017 - 2018, John Crispin <john@phrozen.org>
+ * Copyright (c) 2018 - 2019, Christian Lamparter <chunkeey@gmail.com>
+ * Copyright (c) 2020 - 2021, Gabor Juhos <j4g8y7@gmail.com>
+ * Copyright (c) 2021 - 2022, Maxime Chevallier <maxime.chevallier@bootlin.com>
+ * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/of_net.h>
+#include <linux/skbuff.h>
+#include <linux/if_vlan.h>
+#include <linux/reset.h>
+#include <linux/of_platform.h>
+#include <net/ip6_checksum.h>
+#include <net/dst_metadata.h>
+#include <net/page_pool/helpers.h>
+
+#include "ipqess_edma.h"
+#include "ipqess_port.h"
+#include "ipqess_switch.h"
+
+static void ipqess_edma_w32(struct ipqess_edma *edma, u32 reg, u32 val)
+{
+ writel(val, edma->hw_addr + reg);
+}
+
+static u32 ipqess_edma_r32(struct ipqess_edma *edma, u16 reg)
+{
+ return readl(edma->hw_addr + reg);
+}
+
+static void ipqess_edma_m32(struct ipqess_edma *edma, u32 mask, u32 val,
+ u16 reg)
+{
+ u32 _val = ipqess_edma_r32(edma, reg);
+
+ _val &= ~mask;
+ _val |= val;
+
+ ipqess_edma_w32(edma, reg, _val);
+}
+
+static int ipqess_edma_tx_ring_alloc(struct ipqess_edma *edma)
+{
+ struct device *dev = &edma->pdev->dev;
+ int i;
+
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++) {
+ struct ipqess_edma_tx_ring *tx_ring = &edma->tx_ring[i];
+ size_t size;
+ u32 idx;
+
+ tx_ring->edma = edma;
+ tx_ring->ring_id = i;
+ tx_ring->idx = i * 4;
+ tx_ring->count = IPQESS_EDMA_TX_RING_SIZE;
+ tx_ring->nq = netdev_get_tx_queue(edma->netdev, i);
+
+ size = sizeof(struct ipqess_edma_buf) * IPQESS_EDMA_TX_RING_SIZE;
+ tx_ring->buf = devm_kzalloc(dev, size, GFP_KERNEL);
+ if (!tx_ring->buf)
+ return -ENOMEM;
+
+ size = sizeof(struct ipqess_edma_tx_desc) * IPQESS_EDMA_TX_RING_SIZE;
+ tx_ring->desc = dmam_alloc_coherent(dev, size, &tx_ring->ring_dma,
+ GFP_KERNEL);
+ if (!tx_ring->desc)
+ return -ENOMEM;
+
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_TPD_BASE_ADDR_Q(tx_ring->idx),
+ (u32)tx_ring->ring_dma);
+
+ idx = ipqess_edma_r32(edma, IPQESS_EDMA_REG_TPD_IDX_Q(tx_ring->idx));
+ idx >>= IPQESS_EDMA_TPD_CONS_IDX_SHIFT; /* need u32 here */
+ idx &= 0xffff;
+ tx_ring->head = idx;
+ tx_ring->tail = idx;
+
+ ipqess_edma_m32(edma,
+ IPQESS_EDMA_TPD_PROD_IDX_MASK
+ << IPQESS_EDMA_TPD_PROD_IDX_SHIFT,
+ idx, IPQESS_EDMA_REG_TPD_IDX_Q(tx_ring->idx));
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_TX_SW_CONS_IDX_Q(tx_ring->idx),
+ idx);
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_TPD_RING_SIZE,
+ IPQESS_EDMA_TX_RING_SIZE);
+ }
+
+ return 0;
+}
+
+static int ipqess_edma_tx_unmap_and_free(struct device *dev,
+ struct ipqess_edma_buf *buf)
+{
+ int len = 0;
+
+ if (buf->flags & IPQESS_EDMA_DESC_SINGLE)
+ dma_unmap_single(dev, buf->dma, buf->length, DMA_TO_DEVICE);
+ else if (buf->flags & IPQESS_EDMA_DESC_PAGE)
+ dma_unmap_page(dev, buf->dma, buf->length, DMA_TO_DEVICE);
+
+ if (buf->flags & IPQESS_EDMA_DESC_LAST) {
+ len = buf->skb->len;
+ dev_kfree_skb_any(buf->skb);
+ }
+
+ buf->flags = 0;
+
+ return len;
+}
+
+static void ipqess_edma_tx_ring_free(struct ipqess_edma *edma)
+{
+ int i;
+
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++) {
+ int j;
+
+ if (edma->tx_ring[i].desc)
+ continue;
+
+ for (j = 0; j < IPQESS_EDMA_TX_RING_SIZE; j++) {
+ struct ipqess_edma_buf *buf = &edma->tx_ring[i].buf[j];
+
+ ipqess_edma_tx_unmap_and_free(&edma->pdev->dev, buf);
+ }
+
+ edma->tx_ring[i].buf = NULL;
+ }
+}
+
+static int ipqess_edma_rx_buf_prepare(struct ipqess_edma_buf *buf,
+ struct ipqess_edma_rx_ring *rx_ring)
+{
+ memset(buf->skb->data, 0, sizeof(struct ipqess_edma_rx_desc));
+
+ buf->dma = dma_map_single(rx_ring->ppdev, buf->skb->data,
+ IPQESS_EDMA_RX_HEAD_BUFF_SIZE,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(rx_ring->ppdev, buf->dma)) {
+ dev_kfree_skb_any(buf->skb);
+ buf->skb = NULL;
+ return -EFAULT;
+ }
+
+ buf->length = IPQESS_EDMA_RX_HEAD_BUFF_SIZE;
+ rx_ring->desc_dma[rx_ring->head] = buf->dma;
+ rx_ring->head = (rx_ring->head + 1) % IPQESS_EDMA_RX_RING_SIZE;
+
+ ipqess_edma_m32(rx_ring->edma, IPQESS_EDMA_RFD_PROD_IDX_BITS,
+ (rx_ring->head + IPQESS_EDMA_RX_RING_SIZE - 1)
+ % IPQESS_EDMA_RX_RING_SIZE,
+ IPQESS_EDMA_REG_RFD_IDX_Q(rx_ring->idx));
+
+ return 0;
+}
+
+/* locking is handled by the caller */
+static int ipqess_edma_rx_buf_alloc_napi(struct ipqess_edma_rx_ring *rx_ring)
+{
+ struct ipqess_edma_buf *buf = &rx_ring->buf[rx_ring->head];
+ struct page *new_page = page_pool_dev_alloc_pages(rx_ring->page_pool);
+
+ if (!new_page)
+ return -ENOMEM;
+
+ buf->skb = napi_build_skb(page_address(new_page),
+ SKB_HEAD_ALIGN(IPQESS_EDMA_RX_HEAD_BUFF_SIZE));
+ if (unlikely(!buf->skb)) {
+ page_pool_put_page(rx_ring->page_pool, new_page,
+ IPQESS_EDMA_RX_HEAD_BUFF_SIZE,
+ true);
+ return -ENOMEM;
+ }
+
+ skb_mark_for_recycle(buf->skb);
+
+ return ipqess_edma_rx_buf_prepare(buf, rx_ring);
+}
+
+static int ipqess_edma_rx_buf_alloc(struct ipqess_edma_rx_ring *rx_ring)
+{
+ struct ipqess_edma_buf *buf = &rx_ring->buf[rx_ring->head];
+ struct page *new_page = page_pool_dev_alloc_pages(rx_ring->page_pool);
+
+ if (!new_page)
+ return -ENOMEM;
+
+ buf->skb = build_skb(page_address(new_page),
+ SKB_HEAD_ALIGN(IPQESS_EDMA_RX_HEAD_BUFF_SIZE));
+ if (unlikely(!buf->skb)) {
+ page_pool_put_page(rx_ring->page_pool, new_page,
+ IPQESS_EDMA_RX_HEAD_BUFF_SIZE,
+ true);
+ return -ENOMEM;
+ }
+
+ skb_mark_for_recycle(buf->skb);
+ skb_reserve(buf->skb, NET_IP_ALIGN);
+
+ return ipqess_edma_rx_buf_prepare(buf, rx_ring);
+}
+
+static void ipqess_edma_refill_work(struct work_struct *work)
+{
+ struct ipqess_edma_rx_ring_refill *rx_refill =
+ container_of(work, struct ipqess_edma_rx_ring_refill,
+ refill_work);
+ struct ipqess_edma_rx_ring *rx_ring = rx_refill->rx_ring;
+ int refill = 0;
+
+ /* don't let this loop by accident. */
+ while (atomic_dec_and_test(&rx_ring->refill_count)) {
+ napi_disable(&rx_ring->napi_rx);
+ if (ipqess_edma_rx_buf_alloc(rx_ring)) {
+ refill++;
+ dev_dbg(rx_ring->ppdev,
+ "Not all buffers were reallocated");
+ }
+ napi_enable(&rx_ring->napi_rx);
+ }
+
+ if (atomic_add_return(refill, &rx_ring->refill_count))
+ schedule_work(&rx_refill->refill_work);
+}
+
+static int ipqess_edma_rx_ring_alloc(struct ipqess_edma *edma)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
+ .pool_size = IPQESS_EDMA_RX_RING_SIZE,
+ .nid = dev_to_node(&edma->pdev->dev),
+ .dev = &edma->pdev->dev,
+ .dma_dir = DMA_FROM_DEVICE,
+ .offset = 0,
+ .max_len = IPQESS_EDMA_RX_HEAD_BUFF_SIZE,
+ };
+ int i;
+
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++) {
+ struct ipqess_edma_rx_ring *rx_ring = &edma->rx_ring[i];
+ int j;
+
+ rx_ring->edma = edma;
+ rx_ring->ppdev = &edma->pdev->dev;
+ rx_ring->ring_id = i;
+ rx_ring->idx = i * 2;
+
+ rx_ring->buf =
+ devm_kzalloc(&edma->pdev->dev,
+ sizeof(struct ipqess_edma_buf)
+ * IPQESS_EDMA_RX_RING_SIZE,
+ GFP_KERNEL);
+
+ if (!rx_ring->buf)
+ return -ENOMEM;
+
+ rx_ring->desc_dma =
+ dmam_alloc_coherent(&edma->pdev->dev,
+ sizeof(dma_addr_t)
+ * IPQESS_EDMA_RX_RING_SIZE,
+ &rx_ring->ring_dma, GFP_KERNEL);
+
+ if (!rx_ring->desc_dma)
+ return -ENOMEM;
+
+ rx_ring->page_pool = page_pool_create(&pp_params);
+
+ /* Fill the entire ring once */
+ for (j = 0; j < IPQESS_EDMA_RX_RING_SIZE; j++)
+ if (ipqess_edma_rx_buf_alloc(rx_ring) < 0)
+ return -ENOMEM;
+
+ edma->rx_refill[i].rx_ring = &edma->rx_ring[i];
+ INIT_WORK(&edma->rx_refill[i].refill_work,
+ ipqess_edma_refill_work);
+
+ ipqess_edma_w32(edma,
+ IPQESS_EDMA_REG_RFD_BASE_ADDR_Q(rx_ring->idx),
+ (u32)(rx_ring->ring_dma));
+ }
+
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RX_DESC0,
+ (IPQESS_EDMA_RX_HEAD_BUFF_SIZE << IPQESS_EDMA_RX_BUF_SIZE_SHIFT) |
+ (IPQESS_EDMA_RX_RING_SIZE << IPQESS_EDMA_RFD_RING_SIZE_SHIFT));
+
+ return 0;
+}
+
+static void ipqess_edma_rx_ring_free(struct ipqess_edma *edma)
+{
+ int i;
+
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++) {
+ struct ipqess_edma_rx_ring *rx_ring = &edma->rx_ring[i];
+ int j;
+
+ cancel_work_sync(&edma->rx_refill[i].refill_work);
+ atomic_set(&rx_ring->refill_count, 0);
+
+ for (j = 0; j < IPQESS_EDMA_RX_RING_SIZE; j++) {
+ dma_unmap_single(&edma->pdev->dev,
+ rx_ring->buf[j].dma,
+ rx_ring->buf[j].length,
+ DMA_FROM_DEVICE);
+ dev_kfree_skb_any(rx_ring->buf[j].skb);
+ }
+ page_pool_destroy(rx_ring->page_pool);
+ }
+}
+
+static int ipqess_edma_redirect(struct ipqess_edma_rx_ring *rx_ring,
+ struct sk_buff *skb, int port_id)
+{
+ struct ipqess_port *port;
+
+ if (port_id == 0) {
+ /* The switch probably redirected an unknown frame to the CPU port
+ * (IGMP,BC,unknown MC, unknown UC)
+ */
+ return -EINVAL;
+ }
+
+ if (port_id < 0 || port_id > QCA8K_NUM_PORTS) {
+ dev_warn_ratelimited(rx_ring->edma->sw->priv->dev,
+ "received packet tagged with out-of-bounds port id %d\n",
+ port_id);
+ return -EINVAL;
+ }
+
+ port = rx_ring->edma->sw->port_list[port_id - 1];
+ if (!port) {
+ /* drop packets tagged from unregistered ports */
+ return -EINVAL;
+ }
+
+ skb->dev = port->netdev;
+ skb_push(skb, ETH_HLEN);
+ skb->pkt_type = PACKET_HOST;
+ skb->protocol = eth_type_trans(skb, skb->dev);
+
+ dev_sw_netstats_rx_add(skb->dev, skb->len);
+
+ napi_gro_receive(&rx_ring->napi_rx, skb);
+
+ return 0;
+}
+
+static int ipqess_edma_refill_rx_ring(struct ipqess_edma_rx_ring *rx_ring,
+ u32 num_desc)
+{
+ struct work_struct *refill_work = &rx_ring->edma->rx_refill[rx_ring->ring_id].refill_work;
+
+ num_desc += atomic_xchg(&rx_ring->refill_count, 0);
+ while (num_desc) {
+ if (ipqess_edma_rx_buf_alloc_napi(rx_ring)) {
+ num_desc = atomic_add_return(num_desc,
+ &rx_ring->refill_count);
+ if (num_desc >= DIV_ROUND_UP(IPQESS_EDMA_RX_RING_SIZE * 4, 7))
+ schedule_work(refill_work);
+
+ break;
+ }
+ num_desc--;
+ }
+
+ return num_desc;
+}
+
+static int ipqess_edma_rx_poll(struct ipqess_edma_rx_ring *rx_ring, int budget)
+{
+ u32 length = 0, num_desc, tail, rx_ring_tail;
+ int done = 0;
+ int port_id;
+
+ rx_ring_tail = rx_ring->tail;
+
+ tail = ipqess_edma_r32(rx_ring->edma,
+ IPQESS_EDMA_REG_RFD_IDX_Q(rx_ring->idx));
+ tail >>= IPQESS_EDMA_RFD_CONS_IDX_SHIFT;
+ tail &= IPQESS_EDMA_RFD_CONS_IDX_MASK;
+
+ while (done < budget) {
+ struct ipqess_edma_rx_desc *rd;
+ struct sk_buff *skb;
+
+ if (rx_ring_tail == tail)
+ break;
+
+ dma_unmap_single(rx_ring->ppdev,
+ rx_ring->buf[rx_ring_tail].dma,
+ rx_ring->buf[rx_ring_tail].length,
+ DMA_FROM_DEVICE);
+
+ skb = xchg(&rx_ring->buf[rx_ring_tail].skb, NULL);
+ rd = (struct ipqess_edma_rx_desc *)skb->data;
+ rx_ring_tail = IPQESS_EDMA_NEXT_IDX(rx_ring_tail,
+ IPQESS_EDMA_RX_RING_SIZE);
+
+ /* Check if RRD is valid */
+ if (!(rd->rrd7 & cpu_to_le16(IPQESS_EDMA_RRD_DESC_VALID))) {
+ num_desc = 1;
+ dev_kfree_skb_any(skb);
+ goto skip;
+ }
+
+ num_desc = le16_to_cpu(rd->rrd1) & IPQESS_EDMA_RRD_NUM_RFD_MASK;
+ length = le16_to_cpu(rd->rrd6) & IPQESS_EDMA_RRD_PKT_SIZE_MASK;
+
+ /* rrd data should not be included in the packet data */
+ skb_reserve(skb, IPQESS_EDMA_RRD_SIZE);
+ if (num_desc > 1) {
+ struct sk_buff *skb_prev = NULL;
+ int size_remaining;
+ int i;
+
+ skb->data_len = 0;
+ skb->tail += (IPQESS_EDMA_RX_HEAD_BUFF_SIZE
+ - IPQESS_EDMA_RRD_SIZE);
+ skb->len = length;
+ skb->truesize = length;
+ size_remaining =
+ length - (IPQESS_EDMA_RX_HEAD_BUFF_SIZE
+ - IPQESS_EDMA_RRD_SIZE);
+
+ for (i = 1; i < num_desc; i++) {
+ struct sk_buff *skb_temp =
+ rx_ring->buf[rx_ring_tail].skb;
+
+ dma_unmap_single(rx_ring->ppdev,
+ rx_ring->buf[rx_ring_tail].dma,
+ rx_ring->buf[rx_ring_tail].length,
+ DMA_FROM_DEVICE);
+
+ skb_put(skb_temp,
+ min(size_remaining, IPQESS_EDMA_RX_HEAD_BUFF_SIZE));
+ if (skb_prev)
+ skb_prev->next =
+ rx_ring->buf[rx_ring_tail].skb;
+ else
+ skb_shinfo(skb)->frag_list =
+ rx_ring->buf[rx_ring_tail].skb;
+ skb_prev = rx_ring->buf[rx_ring_tail].skb;
+ rx_ring->buf[rx_ring_tail].skb->next = NULL;
+
+ skb->data_len += rx_ring->buf[rx_ring_tail].skb->len;
+ size_remaining -= rx_ring->buf[rx_ring_tail].skb->len;
+
+ rx_ring_tail =
+ IPQESS_EDMA_NEXT_IDX(rx_ring_tail,
+ IPQESS_EDMA_RX_RING_SIZE);
+ }
+
+ } else {
+ skb_put(skb, length);
+ }
+
+ skb->dev = rx_ring->edma->netdev;
+ skb->protocol = eth_type_trans(skb, rx_ring->edma->netdev);
+ skb_record_rx_queue(skb, rx_ring->ring_id);
+
+ if (rd->rrd6 & cpu_to_le16(IPQESS_EDMA_RRD_CSUM_FAIL_MASK))
+ skb_checksum_none_assert(skb);
+ else
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ if (rd->rrd7 & cpu_to_le16(IPQESS_EDMA_RRD_CVLAN))
+ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
+ le16_to_cpu(rd->rrd4));
+ else if (rd->rrd1 & cpu_to_le16(IPQESS_EDMA_RRD_SVLAN))
+ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021AD),
+ le16_to_cpu(rd->rrd4));
+
+ port_id = FIELD_GET(IPQESS_EDMA_RRD_PORT_ID_MASK,
+ le16_to_cpu(rd->rrd1));
+
+ if (ipqess_edma_redirect(rx_ring, skb, port_id)) {
+ dev_kfree_skb_any(skb);
+ goto skip;
+ }
+
+ rx_ring->edma->stats.rx_packets++;
+ rx_ring->edma->stats.rx_bytes += length;
+
+ done++;
+skip:
+ num_desc = ipqess_edma_refill_rx_ring(rx_ring, num_desc);
+ }
+
+ ipqess_edma_w32(rx_ring->edma,
+ IPQESS_EDMA_REG_RX_SW_CONS_IDX_Q(rx_ring->idx),
+ rx_ring_tail);
+ rx_ring->tail = rx_ring_tail;
+
+ return done;
+}
+
+static int ipqess_edma_tx_complete(struct ipqess_edma_tx_ring *tx_ring,
+ int budget)
+{
+ int total = 0, ret;
+ int done = 0;
+ u32 tail;
+
+ tail = ipqess_edma_r32(tx_ring->edma,
+ IPQESS_EDMA_REG_TPD_IDX_Q(tx_ring->idx));
+ tail >>= IPQESS_EDMA_TPD_CONS_IDX_SHIFT;
+ tail &= IPQESS_EDMA_TPD_CONS_IDX_MASK;
+
+ do {
+ ret = ipqess_edma_tx_unmap_and_free(&tx_ring->edma->pdev->dev,
+ &tx_ring->buf[tx_ring->tail]);
+ tx_ring->tail = IPQESS_EDMA_NEXT_IDX(tx_ring->tail, tx_ring->count);
+
+ total += ret;
+ } while ((++done < budget) && (tx_ring->tail != tail));
+
+ ipqess_edma_w32(tx_ring->edma,
+ IPQESS_EDMA_REG_TX_SW_CONS_IDX_Q(tx_ring->idx),
+ tx_ring->tail);
+
+ if (netif_tx_queue_stopped(tx_ring->nq)) {
+ netdev_dbg(tx_ring->edma->netdev, "waking up tx queue %d\n",
+ tx_ring->idx);
+ netif_tx_wake_queue(tx_ring->nq);
+ }
+
+ netdev_tx_completed_queue(tx_ring->nq, done, total);
+
+ return done;
+}
+
+static int ipqess_edma_tx_napi(struct napi_struct *napi, int budget)
+{
+ struct ipqess_edma_tx_ring *tx_ring =
+ container_of(napi, struct ipqess_edma_tx_ring, napi_tx);
+ int work_done = 0;
+ u32 tx_status;
+
+ tx_status = ipqess_edma_r32(tx_ring->edma, IPQESS_EDMA_REG_TX_ISR);
+ tx_status &= BIT(tx_ring->idx);
+
+ work_done = ipqess_edma_tx_complete(tx_ring, budget);
+
+ ipqess_edma_w32(tx_ring->edma, IPQESS_EDMA_REG_TX_ISR, tx_status);
+
+ if (likely(work_done < budget)) {
+ if (napi_complete_done(napi, work_done))
+ ipqess_edma_w32(tx_ring->edma,
+ IPQESS_EDMA_REG_TX_INT_MASK_Q(tx_ring->idx),
+ 0x1);
+ }
+
+ return work_done;
+}
+
+static int ipqess_edma_rx_napi(struct napi_struct *napi, int budget)
+{
+ struct ipqess_edma_rx_ring *rx_ring =
+ container_of(napi, struct ipqess_edma_rx_ring, napi_rx);
+ struct ipqess_edma *edma = rx_ring->edma;
+ u32 rx_mask = BIT(rx_ring->idx);
+ int remaining_budget = budget;
+ int rx_done;
+ u32 status;
+
+ do {
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RX_ISR, rx_mask);
+ rx_done = ipqess_edma_rx_poll(rx_ring, remaining_budget);
+ remaining_budget -= rx_done;
+
+ status = ipqess_edma_r32(edma, IPQESS_EDMA_REG_RX_ISR);
+ } while (remaining_budget > 0 && (status & rx_mask));
+
+ if (remaining_budget <= 0)
+ return budget;
+
+ if (napi_complete_done(napi, budget - remaining_budget))
+ ipqess_edma_w32(edma,
+ IPQESS_EDMA_REG_RX_INT_MASK_Q(rx_ring->idx),
+ 0x1);
+
+ return budget - remaining_budget;
+}
+
+static irqreturn_t ipqess_edma_interrupt_tx(int irq, void *priv)
+{
+ struct ipqess_edma_tx_ring *tx_ring =
+ (struct ipqess_edma_tx_ring *)priv;
+
+ if (likely(napi_schedule_prep(&tx_ring->napi_tx))) {
+ __napi_schedule(&tx_ring->napi_tx);
+ ipqess_edma_w32(tx_ring->edma,
+ IPQESS_EDMA_REG_TX_INT_MASK_Q(tx_ring->idx),
+ 0x0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ipqess_edma_interrupt_rx(int irq, void *priv)
+{
+ struct ipqess_edma_rx_ring *rx_ring = (struct ipqess_edma_rx_ring *)priv;
+
+ if (likely(napi_schedule_prep(&rx_ring->napi_rx))) {
+ __napi_schedule(&rx_ring->napi_rx);
+ ipqess_edma_w32(rx_ring->edma,
+ IPQESS_EDMA_REG_RX_INT_MASK_Q(rx_ring->idx),
+ 0x0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void ipqess_edma_irq_enable(struct ipqess_edma *edma)
+{
+ int i;
+
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RX_ISR, 0xff);
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_TX_ISR, 0xffff);
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++) {
+ ipqess_edma_w32(edma,
+ IPQESS_EDMA_REG_RX_INT_MASK_Q(edma->rx_ring[i].idx),
+ 1);
+ ipqess_edma_w32(edma,
+ IPQESS_EDMA_REG_TX_INT_MASK_Q(edma->tx_ring[i].idx),
+ 1);
+ }
+}
+
+static void ipqess_edma_irq_disable(struct ipqess_edma *edma)
+{
+ int i;
+
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++) {
+ ipqess_edma_w32(edma,
+ IPQESS_EDMA_REG_RX_INT_MASK_Q(edma->rx_ring[i].idx),
+ 0);
+ ipqess_edma_w32(edma,
+ IPQESS_EDMA_REG_TX_INT_MASK_Q(edma->tx_ring[i].idx),
+ 0);
+ }
+}
+
+static u16 ipqess_edma_tx_desc_available(struct ipqess_edma_tx_ring *tx_ring)
+{
+ u16 count = 0;
+
+ if (tx_ring->tail <= tx_ring->head)
+ count = IPQESS_EDMA_TX_RING_SIZE;
+
+ count += tx_ring->tail - tx_ring->head - 1;
+
+ return count;
+}
+
+static int ipqess_edma_cal_txd_req(struct sk_buff *skb)
+{
+ int tpds;
+
+ /* one TPD for the header, and one for each fragments */
+ tpds = 1 + skb_shinfo(skb)->nr_frags;
+ if (skb_is_gso(skb) && skb_is_gso_v6(skb)) {
+ /* for LSOv2 one extra TPD is needed */
+ tpds++;
+ }
+
+ return tpds;
+}
+
+static struct ipqess_edma_buf *ipqess_edma_get_tx_buffer(struct ipqess_edma_tx_ring *tx_ring,
+ struct ipqess_edma_tx_desc *desc)
+{
+ return &tx_ring->buf[desc - tx_ring->desc];
+}
+
+static struct ipqess_edma_tx_desc *ipqess_edma_tx_desc_next(struct ipqess_edma_tx_ring *tx_ring)
+{
+ struct ipqess_edma_tx_desc *desc;
+
+ desc = &tx_ring->desc[tx_ring->head];
+ tx_ring->head = IPQESS_EDMA_NEXT_IDX(tx_ring->head, tx_ring->count);
+
+ return desc;
+}
+
+static void ipqess_edma_rollback_tx(struct ipqess_edma *eth,
+ struct ipqess_edma_tx_desc *first_desc,
+ int ring_id)
+{
+ struct ipqess_edma_tx_ring *tx_ring = ð->tx_ring[ring_id];
+ struct ipqess_edma_tx_desc *desc = NULL;
+ struct ipqess_edma_buf *buf;
+ u16 start_index, index;
+
+ start_index = first_desc - tx_ring->desc;
+
+ index = start_index;
+ while (index != tx_ring->head) {
+ desc = &tx_ring->desc[index];
+ buf = &tx_ring->buf[index];
+ ipqess_edma_tx_unmap_and_free(ð->pdev->dev, buf);
+ memset(desc, 0, sizeof(*desc));
+ if (++index == tx_ring->count)
+ index = 0;
+ }
+ tx_ring->head = start_index;
+}
+
+static int ipqess_edma_tx_map_and_fill(struct ipqess_edma_tx_ring *tx_ring,
+ struct sk_buff *skb, int port_id)
+{
+ struct ipqess_edma_tx_desc *desc = NULL, *first_desc = NULL;
+ u32 word1 = 0, word3 = 0, lso_word1 = 0, svlan_tag = 0;
+ struct platform_device *pdev = tx_ring->edma->pdev;
+ struct ipqess_edma_buf *buf = NULL;
+ u16 len;
+ int i;
+
+ word3 |= port_id << IPQESS_EDMA_TPD_PORT_BITMAP_SHIFT;
+ word3 |= BIT(IPQESS_EDMA_TPD_FROM_CPU_SHIFT);
+ word3 |= 0x3e << IPQESS_EDMA_TPD_PORT_BITMAP_SHIFT;
+
+ if (skb_is_gso(skb)) {
+ if (skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4) {
+ lso_word1 |= IPQESS_EDMA_TPD_IPV4_EN;
+ ip_hdr(skb)->check = 0;
+ tcp_hdr(skb)->check = ~csum_tcpudp_magic(ip_hdr(skb)->saddr,
+ ip_hdr(skb)->daddr,
+ 0, IPPROTO_TCP, 0);
+ } else if (skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6) {
+ lso_word1 |= IPQESS_EDMA_TPD_LSO_V2_EN;
+ ipv6_hdr(skb)->payload_len = 0;
+ tcp_hdr(skb)->check = ~csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+ &ipv6_hdr(skb)->daddr,
+ 0, IPPROTO_TCP, 0);
+ }
+
+ lso_word1 |= IPQESS_EDMA_TPD_LSO_EN |
+ ((skb_shinfo(skb)->gso_size & IPQESS_EDMA_TPD_MSS_MASK) <<
+ IPQESS_EDMA_TPD_MSS_SHIFT) |
+ (skb_transport_offset(skb) << IPQESS_EDMA_TPD_HDR_SHIFT);
+ } else if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) {
+ u8 css, cso;
+
+ cso = skb_checksum_start_offset(skb);
+ css = cso + skb->csum_offset;
+
+ word1 |= (IPQESS_EDMA_TPD_CUSTOM_CSUM_EN);
+ word1 |= (cso >> 1) << IPQESS_EDMA_TPD_HDR_SHIFT;
+ word1 |= ((css >> 1) << IPQESS_EDMA_TPD_CUSTOM_CSUM_SHIFT);
+ }
+
+ if (skb_vlan_tag_present(skb)) {
+ switch (skb->vlan_proto) {
+ case htons(ETH_P_8021Q):
+ word3 |= BIT(IPQESS_EDMA_TX_INS_CVLAN);
+ word3 |= skb_vlan_tag_get(skb) << IPQESS_EDMA_TX_CVLAN_TAG_SHIFT;
+ break;
+ case htons(ETH_P_8021AD):
+ word1 |= BIT(IPQESS_EDMA_TX_INS_SVLAN);
+ svlan_tag = skb_vlan_tag_get(skb);
+ break;
+ default:
+ dev_err(&pdev->dev, "no ctag or stag present\n");
+ goto vlan_tag_error;
+ }
+ }
+
+ if (eth_type_vlan(skb->protocol))
+ word1 |= IPQESS_EDMA_TPD_VLAN_TAGGED;
+
+ if (skb->protocol == htons(ETH_P_PPP_SES))
+ word1 |= IPQESS_EDMA_TPD_PPPOE_EN;
+
+ len = skb_headlen(skb);
+
+ first_desc = ipqess_edma_tx_desc_next(tx_ring);
+ desc = first_desc;
+ if (lso_word1 & IPQESS_EDMA_TPD_LSO_V2_EN) {
+ desc->addr = cpu_to_le32(skb->len);
+ desc->word1 = cpu_to_le32(word1 | lso_word1);
+ desc->svlan_tag = cpu_to_le16(svlan_tag);
+ desc->word3 = cpu_to_le32(word3);
+ desc = ipqess_edma_tx_desc_next(tx_ring);
+ }
+
+ buf = ipqess_edma_get_tx_buffer(tx_ring, desc);
+ buf->length = len;
+ buf->dma = dma_map_single(&pdev->dev, skb->data, len, DMA_TO_DEVICE);
+
+ if (dma_mapping_error(&pdev->dev, buf->dma))
+ goto dma_error;
+
+ desc->addr = cpu_to_le32(buf->dma);
+ desc->len = cpu_to_le16(len);
+
+ buf->flags |= IPQESS_EDMA_DESC_SINGLE;
+ desc->word1 = cpu_to_le32(word1 | lso_word1);
+ desc->svlan_tag = cpu_to_le16(svlan_tag);
+ desc->word3 = cpu_to_le32(word3);
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ len = skb_frag_size(frag);
+ desc = ipqess_edma_tx_desc_next(tx_ring);
+ buf = ipqess_edma_get_tx_buffer(tx_ring, desc);
+ buf->length = len;
+ buf->flags |= IPQESS_EDMA_DESC_PAGE;
+ buf->dma = skb_frag_dma_map(&pdev->dev, frag, 0, len,
+ DMA_TO_DEVICE);
+
+ if (dma_mapping_error(&pdev->dev, buf->dma))
+ goto dma_error;
+
+ desc->addr = cpu_to_le32(buf->dma);
+ desc->len = cpu_to_le16(len);
+ desc->svlan_tag = cpu_to_le16(svlan_tag);
+ desc->word1 = cpu_to_le32(word1 | lso_word1);
+ desc->word3 = cpu_to_le32(word3);
+ }
+ desc->word1 |= cpu_to_le32(1 << IPQESS_EDMA_TPD_EOP_SHIFT);
+ buf->skb = skb;
+ buf->flags |= IPQESS_EDMA_DESC_LAST;
+
+ return 0;
+
+dma_error:
+ ipqess_edma_rollback_tx(tx_ring->edma, first_desc, tx_ring->ring_id);
+ dev_err(&pdev->dev, "TX DMA map failed\n");
+
+vlan_tag_error:
+ return -ENOMEM;
+}
+
+static void ipqess_edma_kick_tx(struct ipqess_edma_tx_ring *tx_ring)
+{
+ /* Ensure that all TPDs has been written completely */
+ dma_wmb();
+
+ /* update software producer index */
+ ipqess_edma_w32(tx_ring->edma, IPQESS_EDMA_REG_TPD_IDX_Q(tx_ring->idx),
+ tx_ring->head);
+}
+
+netdev_tx_t ipqess_edma_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct ipqess_edma *edma = port->edma;
+ struct ipqess_edma_tx_ring *tx_ring;
+ int port_id = port->index;
+ int tx_num;
+ int avail;
+ int ret;
+
+ tx_ring = &edma->tx_ring[skb_get_queue_mapping(skb)];
+ tx_num = ipqess_edma_cal_txd_req(skb);
+ avail = ipqess_edma_tx_desc_available(tx_ring);
+ if (avail < tx_num) {
+ netdev_dbg(netdev,
+ "stopping tx queue %d, avail=%d req=%d im=%x\n",
+ tx_ring->idx, avail, tx_num,
+ ipqess_edma_r32(edma, IPQESS_EDMA_REG_TX_INT_MASK_Q(tx_ring->idx)));
+ netif_tx_stop_queue(tx_ring->nq);
+ ipqess_edma_w32(tx_ring->edma,
+ IPQESS_EDMA_REG_TX_INT_MASK_Q(tx_ring->idx),
+ 0x1);
+ ipqess_edma_kick_tx(tx_ring);
+ return NETDEV_TX_BUSY;
+ }
+
+ ret = ipqess_edma_tx_map_and_fill(tx_ring, skb, port_id);
+ if (ret) {
+ dev_kfree_skb_any(skb);
+ edma->stats.tx_errors++;
+ return ret;
+ }
+
+ edma->stats.tx_packets++;
+ edma->stats.tx_bytes += skb->len;
+ netdev_tx_sent_queue(tx_ring->nq, skb->len);
+
+ if (!netdev_xmit_more() || netif_xmit_stopped(tx_ring->nq))
+ ipqess_edma_kick_tx(tx_ring);
+
+ return NETDEV_TX_OK;
+}
+EXPORT_SYMBOL(ipqess_edma_xmit);
+
+static void ipqess_edma_hw_stop(struct ipqess_edma *edma)
+{
+ int i;
+
+ /* disable all RX queue IRQs */
+ for (i = 0; i < IPQESS_EDMA_MAX_RX_QUEUE; i++)
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RX_INT_MASK_Q(i), 0);
+
+ /* disable all TX queue IRQs */
+ for (i = 0; i < IPQESS_EDMA_MAX_TX_QUEUE; i++)
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_TX_INT_MASK_Q(i), 0);
+
+ /* disable all other IRQs */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_MISC_IMR, 0);
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_WOL_IMR, 0);
+
+ /* clear the IRQ status registers */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RX_ISR, 0xff);
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_TX_ISR, 0xffff);
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_MISC_ISR, 0x1fff);
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_WOL_ISR, 0x1);
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_WOL_CTRL, 0);
+
+ /* disable RX and TX queues */
+ ipqess_edma_m32(edma, IPQESS_EDMA_RXQ_CTRL_EN_MASK, 0,
+ IPQESS_EDMA_REG_RXQ_CTRL);
+ ipqess_edma_m32(edma, IPQESS_EDMA_TXQ_CTRL_TXQ_EN, 0,
+ IPQESS_EDMA_REG_TXQ_CTRL);
+}
+
+static int ipqess_edma_hw_init(struct ipqess_edma *edma)
+{
+ int i, err;
+ u32 tmp;
+
+ ipqess_edma_hw_stop(edma);
+
+ ipqess_edma_m32(edma, BIT(IPQESS_EDMA_INTR_SW_IDX_W_TYP_SHIFT),
+ IPQESS_EDMA_INTR_SW_IDX_W_TYPE
+ << IPQESS_EDMA_INTR_SW_IDX_W_TYP_SHIFT,
+ IPQESS_EDMA_REG_INTR_CTRL);
+
+ /* enable IRQ delay slot */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_IRQ_MODRT_TIMER_INIT,
+ (IPQESS_EDMA_TX_IMT
+ << IPQESS_EDMA_IRQ_MODRT_TX_TIMER_SHIFT) |
+ (IPQESS_EDMA_RX_IMT
+ << IPQESS_EDMA_IRQ_MODRT_RX_TIMER_SHIFT));
+
+ /* Set Customer and Service VLAN TPIDs */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_VLAN_CFG,
+ (ETH_P_8021Q << IPQESS_EDMA_VLAN_CFG_CVLAN_TPID_SHIFT)
+ | (ETH_P_8021AD << IPQESS_EDMA_VLAN_CFG_SVLAN_TPID_SHIFT));
+
+ /* Configure the TX Queue bursting */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_TXQ_CTRL,
+ (IPQESS_EDMA_TPD_BURST << IPQESS_EDMA_TXQ_NUM_TPD_BURST_SHIFT)
+ | (IPQESS_EDMA_TXF_BURST << IPQESS_EDMA_TXQ_TXF_BURST_NUM_SHIFT)
+ | IPQESS_EDMA_TXQ_CTRL_TPD_BURST_EN);
+
+ /* Set RSS type */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RSS_TYPE,
+ IPQESS_EDMA_RSS_TYPE_IPV4TCP
+ | IPQESS_EDMA_RSS_TYPE_IPV6_TCP
+ | IPQESS_EDMA_RSS_TYPE_IPV4_UDP
+ | IPQESS_EDMA_RSS_TYPE_IPV6UDP
+ | IPQESS_EDMA_RSS_TYPE_IPV4
+ | IPQESS_EDMA_RSS_TYPE_IPV6);
+
+ /* Set RFD ring burst and threshold */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RX_DESC1,
+ (IPQESS_EDMA_RFD_BURST << IPQESS_EDMA_RXQ_RFD_BURST_NUM_SHIFT)
+ | (IPQESS_EDMA_RFD_THR << IPQESS_EDMA_RXQ_RFD_PF_THRESH_SHIFT)
+ | (IPQESS_EDMA_RFD_LTHR << IPQESS_EDMA_RXQ_RFD_LOW_THRESH_SHIFT));
+
+ /* Set Rx FIFO
+ * - threshold to start to DMA data to host
+ */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RXQ_CTRL,
+ IPQESS_EDMA_FIFO_THRESH_128_BYTE
+ | IPQESS_EDMA_RXQ_CTRL_RMV_VLAN);
+
+ err = ipqess_edma_rx_ring_alloc(edma);
+ if (err)
+ return err;
+
+ err = ipqess_edma_tx_ring_alloc(edma);
+ if (err)
+ goto err_rx_ring_free;
+
+ /* Load all of ring base address above into the dma engine */
+ ipqess_edma_m32(edma, 0, BIT(IPQESS_EDMA_LOAD_PTR_SHIFT),
+ IPQESS_EDMA_REG_TX_SRAM_PART);
+
+ /* Disable TX FIFO low watermark and high watermark */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_TXF_WATER_MARK, 0);
+
+ /* Configure RSS indirection table.
+ * 128 hash will be configured in the following
+ * pattern: hash{0,1,2,3} = {Q0,Q2,Q4,Q6} respectively
+ * and so on
+ */
+ for (i = 0; i < IPQESS_EDMA_NUM_IDT; i++)
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_RSS_IDT(i),
+ IPQESS_EDMA_RSS_IDT_VALUE);
+
+ /* Configure load balance mapping table.
+ * 4 table entry will be configured according to the
+ * following pattern: load_balance{0,1,2,3} = {Q0,Q1,Q3,Q4}
+ * respectively.
+ */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_LB_RING, IPQESS_EDMA_LB_REG_VALUE);
+
+ /* Configure Virtual queue for Tx rings */
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_VQ_CTRL0, IPQESS_EDMA_VQ_REG_VALUE);
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_VQ_CTRL1, IPQESS_EDMA_VQ_REG_VALUE);
+
+ /* Configure Max AXI Burst write size to 128 bytes*/
+ ipqess_edma_w32(edma, IPQESS_EDMA_REG_AXIW_CTRL_MAXWRSIZE,
+ IPQESS_EDMA_AXIW_MAXWRSIZE_VALUE);
+
+ /* Enable TX queues */
+ ipqess_edma_m32(edma, 0, IPQESS_EDMA_TXQ_CTRL_TXQ_EN,
+ IPQESS_EDMA_REG_TXQ_CTRL);
+
+ /* Enable RX queues */
+ tmp = 0;
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++)
+ tmp |= IPQESS_EDMA_RXQ_CTRL_EN(edma->rx_ring[i].idx);
+
+ ipqess_edma_m32(edma, IPQESS_EDMA_RXQ_CTRL_EN_MASK, tmp,
+ IPQESS_EDMA_REG_RXQ_CTRL);
+
+ return 0;
+
+err_rx_ring_free:
+
+ ipqess_edma_rx_ring_free(edma);
+ return err;
+}
+
+static void ipqess_edma_reset(struct ipqess_edma *edma)
+{
+ reset_control_assert(edma->edma_rst);
+
+ mdelay(10);
+
+ reset_control_deassert(edma->edma_rst);
+
+ /* Waiting for all inner tables to be flushed and reinitialized.
+ * This takes between 5 and 10 ms
+ */
+
+ mdelay(10);
+}
+
+int ipqess_edma_init(struct platform_device *pdev, struct device_node *np)
+{
+ struct ipqess_switch *sw = platform_get_drvdata(pdev);
+ struct net_device *netdev;
+ struct ipqess_edma *edma;
+ struct ipqess_port *port;
+ int i, err = 0;
+ int qid;
+
+ edma = devm_kzalloc(&pdev->dev, sizeof(*edma), GFP_KERNEL);
+ if (!edma)
+ return -ENOMEM;
+
+ edma->pdev = pdev;
+
+ spin_lock_init(&edma->stats_lock);
+
+ edma->hw_addr = devm_platform_ioremap_resource_byname(pdev, "edma");
+ if (IS_ERR(edma->hw_addr)) {
+ err = PTR_ERR(edma->hw_addr);
+ goto err_edma;
+ }
+
+ edma->edma_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(edma->edma_clk)) {
+ err = PTR_ERR(edma->edma_clk);
+ goto err_edma;
+ }
+
+ err = clk_prepare_enable(edma->edma_clk);
+ if (err)
+ goto err_edma;
+
+ edma->edma_rst = devm_reset_control_get(&pdev->dev, "ess");
+ if (IS_ERR(edma->edma_rst)) {
+ err = PTR_ERR(edma->edma_rst);
+ goto err_clk;
+ }
+
+ ipqess_edma_reset(edma);
+
+ for (i = 0; i < IPQESS_EDMA_MAX_TX_QUEUE; i++) {
+ edma->tx_irq[i] = platform_get_irq(pdev, i);
+ scnprintf(edma->tx_irq_names[i], sizeof(edma->tx_irq_names[i]),
+ "%s:txq%d", pdev->name, i);
+ }
+
+ for (i = 0; i < IPQESS_EDMA_MAX_RX_QUEUE; i++) {
+ edma->rx_irq[i] = platform_get_irq(pdev,
+ i + IPQESS_EDMA_MAX_TX_QUEUE);
+ scnprintf(edma->rx_irq_names[i], sizeof(edma->rx_irq_names[i]),
+ "%s:rxq%d", pdev->name, i);
+ }
+
+ netdev = sw->napi_leader;
+ sw->edma = edma;
+ edma->sw = sw;
+ edma->netdev = netdev;
+
+ err = ipqess_edma_hw_init(edma);
+ if (err)
+ goto err_clk;
+
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++) {
+ netif_napi_add_tx(netdev, &edma->tx_ring[i].napi_tx,
+ ipqess_edma_tx_napi);
+ netif_napi_add(netdev, &edma->rx_ring[i].napi_rx,
+ ipqess_edma_rx_napi);
+ }
+
+ for (i = 0; i < IPQESS_EDMA_NETDEV_QUEUES; i++) {
+ qid = edma->tx_ring[i].idx;
+ err = devm_request_irq(&netdev->dev, edma->tx_irq[qid],
+ ipqess_edma_interrupt_tx, 0,
+ edma->tx_irq_names[qid],
+ &edma->tx_ring[i]);
+ if (err)
+ goto err_clk;
+
+ qid = edma->rx_ring[i].idx;
+ err = devm_request_irq(&netdev->dev, edma->rx_irq[qid],
+ ipqess_edma_interrupt_rx, 0,
+ edma->rx_irq_names[qid],
+ &edma->rx_ring[i]);
+ if (err)
+ goto err_clk;
+
+ napi_enable(&edma->tx_ring[i].napi_tx);
+ napi_enable(&edma->rx_ring[i].napi_rx);
+ }
+
+ ipqess_edma_irq_enable(edma);
+ netif_tx_start_all_queues(netdev);
+
+ if (err)
+ goto err_hw_stop;
+
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ port = sw->port_list[i];
+ if (port)
+ port->edma = edma;
+ }
+
+ return 0;
+
+err_hw_stop:
+ ipqess_edma_hw_stop(edma);
+
+ ipqess_edma_tx_ring_free(edma);
+ ipqess_edma_rx_ring_free(edma);
+err_clk:
+ clk_disable_unprepare(edma->edma_clk);
+err_edma:
+ devm_kfree(&pdev->dev, edma);
+
+ return err;
+}
+
+void ipqess_edma_uninit(struct ipqess_edma *edma)
+{
+ struct qca8k_priv *priv = edma->sw->priv;
+ u32 val;
+
+ ipqess_edma_irq_disable(edma);
+ ipqess_edma_hw_stop(edma);
+
+ ipqess_edma_tx_ring_free(edma);
+ ipqess_edma_rx_ring_free(edma);
+
+ /* This register read fixes a bug where
+ * the switch ID is incorrect at the next probe.
+ * The source of this issue is unknown.
+ */
+ qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val);
+
+ clk_disable_unprepare(edma->edma_clk);
+}
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h
new file mode 100644
index 000000000000..172193d7126b
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h
@@ -0,0 +1,488 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR ISC) */
+/* Copyright (c) 2014 - 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017 - 2018, John Crispin <john@phrozen.org>
+ * Copyright (c) 2018 - 2019, Christian Lamparter <chunkeey@gmail.com>
+ * Copyright (c) 2020 - 2021, Gabor Juhos <j4g8y7@gmail.com>
+ * Copyright (c) 2021 - 2022, Maxime Chevallier <maxime.chevallier@bootlin.com>
+ * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
+ *
+ */
+
+#ifndef _IPQESS_EDMA_H_
+#define _IPQESS_EDMA_H_
+
+#include "ipqess_switch.h"
+
+#define IPQESS_EDMA_NETDEV_QUEUES 4
+
+#define IPQESS_EDMA_TPD_EOP_SHIFT 31
+
+#define IPQESS_EDMA_PORT_ID_SHIFT 12
+#define IPQESS_EDMA_PORT_ID_MASK 0x7
+
+/* tpd word 3 bit 18-28 */
+#define IPQESS_EDMA_TPD_PORT_BITMAP_SHIFT 18
+
+#define IPQESS_EDMA_TPD_FROM_CPU_SHIFT 25
+
+#define IPQESS_EDMA_RX_RING_SIZE 128
+#define IPQESS_EDMA_RX_HEAD_BUFF_SIZE 1540
+#define IPQESS_EDMA_RRD_SIZE 16
+#define IPQESS_EDMA_NEXT_IDX(X, Y) (((X) + 1) & ((Y) - 1))
+#define IPQESS_EDMA_TX_DMA_BUF_LEN 0x3fff
+#define IPQESS_EDMA_TX_RING_SIZE 128
+#define IPQESS_EDMA_MAX_RX_QUEUE 8
+#define IPQESS_EDMA_MAX_TX_QUEUE 16
+
+/* Configurations */
+#define IPQESS_EDMA_INTR_CLEAR_TYPE 0
+#define IPQESS_EDMA_INTR_SW_IDX_W_TYPE 0
+#define IPQESS_EDMA_FIFO_THRESH_TYPE 0
+#define IPQESS_EDMA_RSS_TYPE 0
+#define IPQESS_EDMA_RX_IMT 0x0020
+#define IPQESS_EDMA_TX_IMT 0x0050
+#define IPQESS_EDMA_TPD_BURST 5
+#define IPQESS_EDMA_TXF_BURST 0x100
+#define IPQESS_EDMA_RFD_BURST 8
+#define IPQESS_EDMA_RFD_THR 16
+#define IPQESS_EDMA_RFD_LTHR 0
+
+/* Flags used in transmit direction */
+#define IPQESS_EDMA_DESC_LAST 0x1
+#define IPQESS_EDMA_DESC_SINGLE 0x2
+#define IPQESS_EDMA_DESC_PAGE 0x4
+
+struct ipqess_edma_tx_desc {
+ __le16 len;
+ __le16 svlan_tag;
+ __le32 word1;
+ __le32 addr;
+ __le32 word3;
+} __aligned(16) __packed;
+
+struct ipqess_edma_rx_desc {
+ __le16 rrd0;
+ __le16 rrd1;
+ __le16 rrd2;
+ __le16 rrd3;
+ __le16 rrd4;
+ __le16 rrd5;
+ __le16 rrd6;
+ __le16 rrd7;
+} __aligned(16) __packed;
+
+struct ipqess_edma_buf {
+ struct sk_buff *skb;
+ dma_addr_t dma;
+ u16 length;
+ u32 flags;
+};
+
+struct ipqess_edma_tx_ring {
+ struct ipqess_edma_tx_desc *desc;
+ struct ipqess_edma_buf *buf;
+ struct napi_struct napi_tx;
+ struct ipqess_edma *edma;
+ struct netdev_queue *nq;
+ dma_addr_t ring_dma;
+ int ring_id;
+ u16 count;
+ u32 idx;
+ u16 head;
+ u16 tail;
+};
+
+struct ipqess_edma_rx_ring {
+ struct ipqess_edma_buf *buf;
+ struct page_pool *page_pool;
+ struct napi_struct napi_rx;
+ struct ipqess_edma *edma;
+ atomic_t refill_count;
+ struct device *ppdev;
+ dma_addr_t *desc_dma;
+ dma_addr_t ring_dma;
+ int ring_id;
+ u32 idx;
+ u16 head;
+ u16 tail;
+};
+
+struct ipqess_edma_rx_ring_refill {
+ struct ipqess_edma_rx_ring *rx_ring;
+ struct work_struct refill_work;
+};
+
+#define IPQESS_EDMA_IRQ_NAME_LEN 32
+
+struct ipqess_edma {
+ void __iomem *hw_addr;
+
+ /* sw port device chosen as napi leader */
+ struct net_device *netdev;
+
+ struct clk *edma_clk;
+ struct reset_control *edma_rst;
+
+ struct ipqess_edma_rx_ring rx_ring[IPQESS_EDMA_NETDEV_QUEUES];
+
+ struct platform_device *pdev;
+
+ struct notifier_block netdev_notifier;
+ int dsa_ports;
+
+ struct ipqess_edma_tx_ring tx_ring[IPQESS_EDMA_NETDEV_QUEUES];
+
+ struct ipqess_switch *sw;
+
+ /* Protects stats */
+ spinlock_t stats_lock;
+ struct net_device_stats stats;
+
+ struct ipqess_edma_rx_ring_refill rx_refill[IPQESS_EDMA_NETDEV_QUEUES];
+ u32 tx_irq[IPQESS_EDMA_MAX_TX_QUEUE];
+ char tx_irq_names[IPQESS_EDMA_MAX_TX_QUEUE][IPQESS_EDMA_IRQ_NAME_LEN];
+ u32 rx_irq[IPQESS_EDMA_MAX_RX_QUEUE];
+ char rx_irq_names[IPQESS_EDMA_MAX_TX_QUEUE][IPQESS_EDMA_IRQ_NAME_LEN];
+};
+
+int ipqess_edma_init(struct platform_device *pdev, struct device_node *np);
+void ipqess_edma_uninit(struct ipqess_edma *edma);
+
+netdev_tx_t ipqess_edma_xmit(struct sk_buff *skb, struct net_device *netdev);
+
+/* register definition */
+#define IPQESS_EDMA_REG_MAS_CTRL 0x0
+#define IPQESS_EDMA_REG_TIMEOUT_CTRL 0x004
+#define IPQESS_EDMA_REG_DBG0 0x008
+#define IPQESS_EDMA_REG_DBG1 0x00C
+#define IPQESS_EDMA_REG_SW_CTRL0 0x100
+#define IPQESS_EDMA_REG_SW_CTRL1 0x104
+
+/* Interrupt Status Register */
+#define IPQESS_EDMA_REG_RX_ISR 0x200
+#define IPQESS_EDMA_REG_TX_ISR 0x208
+#define IPQESS_EDMA_REG_MISC_ISR 0x210
+#define IPQESS_EDMA_REG_WOL_ISR 0x218
+
+#define IPQESS_EDMA_MISC_ISR_RX_URG_Q(x) (1 << (x))
+
+#define IPQESS_EDMA_MISC_ISR_AXIR_TIMEOUT 0x00000100
+#define IPQESS_EDMA_MISC_ISR_AXIR_ERR 0x00000200
+#define IPQESS_EDMA_MISC_ISR_TXF_DEAD 0x00000400
+#define IPQESS_EDMA_MISC_ISR_AXIW_ERR 0x00000800
+#define IPQESS_EDMA_MISC_ISR_AXIW_TIMEOUT 0x00001000
+
+#define IPQESS_EDMA_WOL_ISR 0x00000001
+
+/* Interrupt Mask Register */
+#define IPQESS_EDMA_REG_MISC_IMR 0x214
+#define IPQESS_EDMA_REG_WOL_IMR 0x218
+
+#define IPQESS_EDMA_RX_IMR_NORMAL_MASK 0x1
+#define IPQESS_EDMA_TX_IMR_NORMAL_MASK 0x1
+#define IPQESS_EDMA_MISC_IMR_NORMAL_MASK 0x80001FFF
+#define IPQESS_EDMA_WOL_IMR_NORMAL_MASK 0x1
+
+/* Edma receive consumer index */
+#define IPQESS_EDMA_REG_RX_SW_CONS_IDX_Q(x) (0x220 + ((x) * 4))
+ /* x is the queue id */
+
+/* Edma transmit consumer index */
+#define IPQESS_EDMA_REG_TX_SW_CONS_IDX_Q(x) (0x240 + ((x) * 4))
+ /* x is the queue id */
+
+/* IRQ Moderator Initial Timer Register */
+#define IPQESS_EDMA_REG_IRQ_MODRT_TIMER_INIT 0x280
+#define IPQESS_EDMA_IRQ_MODRT_TIMER_MASK 0xFFFF
+#define IPQESS_EDMA_IRQ_MODRT_RX_TIMER_SHIFT 0
+#define IPQESS_EDMA_IRQ_MODRT_TX_TIMER_SHIFT 16
+
+/* Interrupt Control Register */
+#define IPQESS_EDMA_REG_INTR_CTRL 0x284
+#define IPQESS_EDMA_INTR_CLR_TYP_SHIFT 0
+#define IPQESS_EDMA_INTR_SW_IDX_W_TYP_SHIFT 1
+#define IPQESS_EDMA_INTR_CLEAR_TYPE_W1 0
+#define IPQESS_EDMA_INTR_CLEAR_TYPE_R 1
+
+/* RX Interrupt Mask Register */
+#define IPQESS_EDMA_REG_RX_INT_MASK_Q(x) (0x300 + ((x) * 4)) /* x = queue id */
+
+/* TX Interrupt mask register */
+#define IPQESS_EDMA_REG_TX_INT_MASK_Q(x) (0x340 + ((x) * 4)) /* x = queue id */
+
+/* Load Ptr Register
+ * Software sets this bit after the initialization of the head and tail
+ */
+#define IPQESS_EDMA_REG_TX_SRAM_PART 0x400
+#define IPQESS_EDMA_LOAD_PTR_SHIFT 16
+
+/* TXQ Control Register */
+#define IPQESS_EDMA_REG_TXQ_CTRL 0x404
+#define IPQESS_EDMA_TXQ_CTRL_IP_OPTION_EN 0x10
+#define IPQESS_EDMA_TXQ_CTRL_TXQ_EN 0x20
+#define IPQESS_EDMA_TXQ_CTRL_ENH_MODE 0x40
+#define IPQESS_EDMA_TXQ_CTRL_LS_8023_EN 0x80
+#define IPQESS_EDMA_TXQ_CTRL_TPD_BURST_EN 0x100
+#define IPQESS_EDMA_TXQ_CTRL_LSO_BREAK_EN 0x200
+#define IPQESS_EDMA_TXQ_NUM_TPD_BURST_MASK 0xF
+#define IPQESS_EDMA_TXQ_TXF_BURST_NUM_MASK 0xFFFF
+#define IPQESS_EDMA_TXQ_NUM_TPD_BURST_SHIFT 0
+#define IPQESS_EDMA_TXQ_TXF_BURST_NUM_SHIFT 16
+
+#define IPQESS_EDMA_REG_TXF_WATER_MARK 0x408 /* In 8-bytes */
+#define IPQESS_EDMA_TXF_WATER_MARK_MASK 0x0FFF
+#define IPQESS_EDMA_TXF_LOW_WATER_MARK_SHIFT 0
+#define IPQESS_EDMA_TXF_HIGH_WATER_MARK_SHIFT 16
+#define IPQESS_EDMA_TXQ_CTRL_BURST_MODE_EN 0x80000000
+
+/* WRR Control Register */
+#define IPQESS_EDMA_REG_WRR_CTRL_Q0_Q3 0x40c
+#define IPQESS_EDMA_REG_WRR_CTRL_Q4_Q7 0x410
+#define IPQESS_EDMA_REG_WRR_CTRL_Q8_Q11 0x414
+#define IPQESS_EDMA_REG_WRR_CTRL_Q12_Q15 0x418
+
+/* Weight round robin(WRR), it takes queue as input, and computes
+ * starting bits where we need to write the weight for a particular
+ * queue
+ */
+#define IPQESS_EDMA_WRR_SHIFT(x) (((x) * 5) % 20)
+
+/* Tx Descriptor Control Register */
+#define IPQESS_EDMA_REG_TPD_RING_SIZE 0x41C
+#define IPQESS_EDMA_TPD_RING_SIZE_SHIFT 0
+#define IPQESS_EDMA_TPD_RING_SIZE_MASK 0xFFFF
+
+/* Transmit descriptor base addredma */
+#define IPQESS_EDMA_REG_TPD_BASE_ADDR_Q(x) (0x420 + ((x) * 4))
+ /* x = queue id */
+
+/* TPD Index Register */
+#define IPQESS_EDMA_REG_TPD_IDX_Q(x) (0x460 + ((x) * 4)) /* x = queue id */
+
+#define IPQESS_EDMA_TPD_PROD_IDX_BITS 0x0000FFFF
+#define IPQESS_EDMA_TPD_CONS_IDX_BITS 0xFFFF0000
+#define IPQESS_EDMA_TPD_PROD_IDX_MASK 0xFFFF
+#define IPQESS_EDMA_TPD_CONS_IDX_MASK 0xFFFF
+#define IPQESS_EDMA_TPD_PROD_IDX_SHIFT 0
+#define IPQESS_EDMA_TPD_CONS_IDX_SHIFT 16
+
+/* TX Virtual Queue Mapping Control Register */
+#define IPQESS_EDMA_REG_VQ_CTRL0 0x4A0
+#define IPQESS_EDMA_REG_VQ_CTRL1 0x4A4
+
+/* Virtual QID shift, it takes queue as input, and computes
+ * Virtual QID position in virtual qid control register
+ */
+#define IPQESS_EDMA_VQ_ID_SHIFT(i) (((i) * 3) % 24)
+
+/* Virtual Queue Default Value */
+#define IPQESS_EDMA_VQ_REG_VALUE 0x240240
+
+/* Tx side Port Interface Control Register */
+#define IPQESS_EDMA_REG_PORT_CTRL 0x4A8
+#define IPQESS_EDMA_PAD_EN_SHIFT 15
+
+/* Tx side VLAN Configuration Register */
+#define IPQESS_EDMA_REG_VLAN_CFG 0x4AC
+
+#define IPQESS_EDMA_VLAN_CFG_SVLAN_TPID_SHIFT 0
+#define IPQESS_EDMA_VLAN_CFG_SVLAN_TPID_MASK 0xffff
+#define IPQESS_EDMA_VLAN_CFG_CVLAN_TPID_SHIFT 16
+#define IPQESS_EDMA_VLAN_CFG_CVLAN_TPID_MASK 0xffff
+
+#define IPQESS_EDMA_TX_CVLAN 16
+#define IPQESS_EDMA_TX_INS_CVLAN 17
+#define IPQESS_EDMA_TX_CVLAN_TAG_SHIFT 0
+
+#define IPQESS_EDMA_TX_SVLAN 14
+#define IPQESS_EDMA_TX_INS_SVLAN 15
+#define IPQESS_EDMA_TX_SVLAN_TAG_SHIFT 16
+
+/* Tx Queue Packet Statistic Register */
+#define IPQESS_EDMA_REG_TX_STAT_PKT_Q(x) (0x700 + ((x) * 8)) /* x = queue id */
+
+#define IPQESS_EDMA_TX_STAT_PKT_MASK 0xFFFFFF
+
+/* Tx Queue Byte Statistic Register */
+#define IPQESS_EDMA_REG_TX_STAT_BYTE_Q(x) (0x704 + ((x) * 8)) /* x = queue id */
+
+/* Load Balance Based Ring Offset Register */
+#define IPQESS_EDMA_REG_LB_RING 0x800
+#define IPQESS_EDMA_LB_RING_ENTRY_MASK 0xff
+#define IPQESS_EDMA_LB_RING_ID_MASK 0x7
+#define IPQESS_EDMA_LB_RING_PROFILE_ID_MASK 0x3
+#define IPQESS_EDMA_LB_RING_ENTRY_BIT_OFFSET 8
+#define IPQESS_EDMA_LB_RING_ID_OFFSET 0
+#define IPQESS_EDMA_LB_RING_PROFILE_ID_OFFSET 3
+#define IPQESS_EDMA_LB_REG_VALUE 0x6040200
+
+/* Load Balance Priority Mapping Register */
+#define IPQESS_EDMA_REG_LB_PRI_START 0x804
+#define IPQESS_EDMA_REG_LB_PRI_END 0x810
+#define IPQESS_EDMA_LB_PRI_REG_INC 4
+#define IPQESS_EDMA_LB_PRI_ENTRY_BIT_OFFSET 4
+#define IPQESS_EDMA_LB_PRI_ENTRY_MASK 0xf
+
+/* RSS Priority Mapping Register */
+#define IPQESS_EDMA_REG_RSS_PRI 0x820
+#define IPQESS_EDMA_RSS_PRI_ENTRY_MASK 0xf
+#define IPQESS_EDMA_RSS_RING_ID_MASK 0x7
+#define IPQESS_EDMA_RSS_PRI_ENTRY_BIT_OFFSET 4
+
+/* RSS Indirection Register */
+#define IPQESS_EDMA_REG_RSS_IDT(x) (0x840 + ((x) * 4))
+ /* x = No. of indirection table */
+#define IPQESS_EDMA_NUM_IDT 16
+#define IPQESS_EDMA_RSS_IDT_VALUE 0x64206420
+
+/* Default RSS Ring Register */
+#define IPQESS_EDMA_REG_DEF_RSS 0x890
+#define IPQESS_EDMA_DEF_RSS_MASK 0x7
+
+/* RSS Hash Function Type Register */
+#define IPQESS_EDMA_REG_RSS_TYPE 0x894
+#define IPQESS_EDMA_RSS_TYPE_NONE 0x01
+#define IPQESS_EDMA_RSS_TYPE_IPV4TCP 0x02
+#define IPQESS_EDMA_RSS_TYPE_IPV6_TCP 0x04
+#define IPQESS_EDMA_RSS_TYPE_IPV4_UDP 0x08
+#define IPQESS_EDMA_RSS_TYPE_IPV6UDP 0x10
+#define IPQESS_EDMA_RSS_TYPE_IPV4 0x20
+#define IPQESS_EDMA_RSS_TYPE_IPV6 0x40
+#define IPQESS_EDMA_RSS_HASH_MODE_MASK 0x7f
+
+#define IPQESS_EDMA_REG_RSS_HASH_VALUE 0x8C0
+
+#define IPQESS_EDMA_REG_RSS_TYPE_RESULT 0x8C4
+
+#define IPQESS_EDMA_HASH_TYPE_START 0
+#define IPQESS_EDMA_HASH_TYPE_END 5
+#define IPQESS_EDMA_HASH_TYPE_SHIFT 12
+
+#define IPQESS_EDMA_RFS_FLOW_ENTRIES 1024
+#define IPQESS_EDMA_RFS_FLOW_ENTRIES_MASK (IPQESS_EDMA_RFS_FLOW_ENTRIES - 1)
+#define IPQESS_EDMA_RFS_EXPIRE_COUNT_PER_CALL 128
+
+/* RFD Base Addredma Register */
+#define IPQESS_EDMA_REG_RFD_BASE_ADDR_Q(x) (0x950 + ((x) * 4))
+ /* x = queue id */
+
+/* RFD Index Register */
+#define IPQESS_EDMA_REG_RFD_IDX_Q(x) (0x9B0 + ((x) * 4)) /* x = queue id */
+
+#define IPQESS_EDMA_RFD_PROD_IDX_BITS 0x00000FFF
+#define IPQESS_EDMA_RFD_CONS_IDX_BITS 0x0FFF0000
+#define IPQESS_EDMA_RFD_PROD_IDX_MASK 0xFFF
+#define IPQESS_EDMA_RFD_CONS_IDX_MASK 0xFFF
+#define IPQESS_EDMA_RFD_PROD_IDX_SHIFT 0
+#define IPQESS_EDMA_RFD_CONS_IDX_SHIFT 16
+
+/* Rx Descriptor Control Register */
+#define IPQESS_EDMA_REG_RX_DESC0 0xA10
+#define IPQESS_EDMA_RFD_RING_SIZE_MASK 0xFFF
+#define IPQESS_EDMA_RX_BUF_SIZE_MASK 0xFFFF
+#define IPQESS_EDMA_RFD_RING_SIZE_SHIFT 0
+#define IPQESS_EDMA_RX_BUF_SIZE_SHIFT 16
+
+#define IPQESS_EDMA_REG_RX_DESC1 0xA14
+#define IPQESS_EDMA_RXQ_RFD_BURST_NUM_MASK 0x3F
+#define IPQESS_EDMA_RXQ_RFD_PF_THRESH_MASK 0x1F
+#define IPQESS_EDMA_RXQ_RFD_LOW_THRESH_MASK 0xFFF
+#define IPQESS_EDMA_RXQ_RFD_BURST_NUM_SHIFT 0
+#define IPQESS_EDMA_RXQ_RFD_PF_THRESH_SHIFT 8
+#define IPQESS_EDMA_RXQ_RFD_LOW_THRESH_SHIFT 16
+
+/* RXQ Control Register */
+#define IPQESS_EDMA_REG_RXQ_CTRL 0xA18
+#define IPQESS_EDMA_FIFO_THRESH_TYPE_SHIF 0
+#define IPQESS_EDMA_FIFO_THRESH_128_BYTE 0x0
+#define IPQESS_EDMA_FIFO_THRESH_64_BYTE 0x1
+#define IPQESS_EDMA_RXQ_CTRL_RMV_VLAN 0x00000002
+#define IPQESS_EDMA_RXQ_CTRL_EN_MASK GENMASK(15, 8)
+#define IPQESS_EDMA_RXQ_CTRL_EN(__qid) BIT(8 + (__qid))
+
+/* AXI Burst Size Config */
+#define IPQESS_EDMA_REG_AXIW_CTRL_MAXWRSIZE 0xA1C
+#define IPQESS_EDMA_AXIW_MAXWRSIZE_VALUE 0x0
+
+/* Rx Statistics Register */
+#define IPQESS_EDMA_REG_RX_STAT_BYTE_Q(x) (0xA30 + ((x) * 4))
+ /* x = queue id */
+#define IPQESS_EDMA_REG_RX_STAT_PKT_Q(x) (0xA50 + ((x) * 4))
+ /* x = queue id */
+
+/* WoL Pattern Length Register */
+#define IPQESS_EDMA_REG_WOL_PATTERN_LEN0 0xC00
+#define IPQESS_EDMA_WOL_PT_LEN_MASK 0xFF
+#define IPQESS_EDMA_WOL_PT0_LEN_SHIFT 0
+#define IPQESS_EDMA_WOL_PT1_LEN_SHIFT 8
+#define IPQESS_EDMA_WOL_PT2_LEN_SHIFT 16
+#define IPQESS_EDMA_WOL_PT3_LEN_SHIFT 24
+
+#define IPQESS_EDMA_REG_WOL_PATTERN_LEN1 0xC04
+#define IPQESS_EDMA_WOL_PT4_LEN_SHIFT 0
+#define IPQESS_EDMA_WOL_PT5_LEN_SHIFT 8
+#define IPQESS_EDMA_WOL_PT6_LEN_SHIFT 16
+
+/* WoL Control Register */
+#define IPQESS_EDMA_REG_WOL_CTRL 0xC08
+#define IPQESS_EDMA_WOL_WK_EN 0x00000001
+#define IPQESS_EDMA_WOL_MG_EN 0x00000002
+#define IPQESS_EDMA_WOL_PT0_EN 0x00000004
+#define IPQESS_EDMA_WOL_PT1_EN 0x00000008
+#define IPQESS_EDMA_WOL_PT2_EN 0x00000010
+#define IPQESS_EDMA_WOL_PT3_EN 0x00000020
+#define IPQESS_EDMA_WOL_PT4_EN 0x00000040
+#define IPQESS_EDMA_WOL_PT5_EN 0x00000080
+#define IPQESS_EDMA_WOL_PT6_EN 0x00000100
+
+/* MAC Control Register */
+#define IPQESS_EDMA_REG_MAC_CTRL0 0xC20
+#define IPQESS_EDMA_REG_MAC_CTRL1 0xC24
+
+/* WoL Pattern Register */
+#define IPQESS_EDMA_REG_WOL_PATTERN_START 0x5000
+#define IPQESS_EDMA_PATTERN_PART_REG_OFFSET 0x40
+
+/* TX descriptor fields */
+#define IPQESS_EDMA_TPD_HDR_SHIFT 0
+#define IPQESS_EDMA_TPD_PPPOE_EN 0x00000100
+#define IPQESS_EDMA_TPD_IP_CSUM_EN 0x00000200
+#define IPQESS_EDMA_TPD_TCP_CSUM_EN 0x0000400
+#define IPQESS_EDMA_TPD_UDP_CSUM_EN 0x00000800
+#define IPQESS_EDMA_TPD_CUSTOM_CSUM_EN 0x00000C00
+#define IPQESS_EDMA_TPD_LSO_EN 0x00001000
+#define IPQESS_EDMA_TPD_LSO_V2_EN 0x00002000
+/* The VLAN_TAGGED bit is not used in the publicly available
+ * drivers. The definition has been stolen from the Atheros
+ * 'alx' driver (drivers/net/ethernet/atheros/alx/hw.h). It
+ * seems that it has the same meaning in regard to the EDMA
+ * hardware.
+ */
+#define IPQESS_EDMA_TPD_VLAN_TAGGED 0x00004000
+#define IPQESS_EDMA_TPD_IPV4_EN 0x00010000
+#define IPQESS_EDMA_TPD_MSS_MASK 0x1FFF
+#define IPQESS_EDMA_TPD_MSS_SHIFT 18
+#define IPQESS_EDMA_TPD_CUSTOM_CSUM_SHIFT 18
+
+/* RRD descriptor fields */
+#define IPQESS_EDMA_RRD_NUM_RFD_MASK 0x000F
+#define IPQESS_EDMA_RRD_PKT_SIZE_MASK 0x3FFF
+#define IPQESS_EDMA_RRD_SRC_PORT_NUM_MASK 0x4000
+#define IPQESS_EDMA_RRD_SVLAN 0x8000
+#define IPQESS_EDMA_RRD_FLOW_COOKIE_MASK 0x07FF
+
+#define IPQESS_EDMA_RRD_PKT_SIZE_MASK 0x3FFF
+#define IPQESS_EDMA_RRD_CSUM_FAIL_MASK 0xC000
+#define IPQESS_EDMA_RRD_CVLAN 0x0001
+#define IPQESS_EDMA_RRD_DESC_VALID 0x8000
+
+#define IPQESS_EDMA_RRD_PRIORITY_SHIFT 4
+#define IPQESS_EDMA_RRD_PRIORITY_MASK 0x7
+#define IPQESS_EDMA_RRD_PORT_TYPE_SHIFT 7
+#define IPQESS_EDMA_RRD_PORT_TYPE_MASK 0x1F
+
+#define IPQESS_EDMA_RRD_PORT_ID_MASK 0x7000
+
+#define IPQESS_EDMA_MAX_MTU 9000
+
+#endif
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h
new file mode 100644
index 000000000000..47f06a757cf7
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 OR ISC */
+
+#ifndef IPQESS_NOTIFIERS_H
+#define IPQESS_NOTIFIERS_H
+
+#include <linux/if_ether.h>
+
+struct ipqess_switchdev_event_work {
+ struct net_device *netdev;
+ struct net_device *orig_netdev;
+ struct work_struct work;
+ unsigned long event;
+ /* Specific for SWITCHDEV_FDB_ADD_TO_DEVICE and
+ * SWITCHDEV_FDB_DEL_TO_DEVICE
+ */
+ unsigned char addr[ETH_ALEN];
+ u16 vid;
+ bool host_addr;
+};
+
+extern struct notifier_block ipqess_switchdev_notifier;
+extern struct notifier_block ipqess_switchdev_blocking_notifier;
+
+int ipqess_notifiers_register(void);
+void ipqess_notifiers_unregister(void);
+
+void ipqess_flush_workqueue(void);
+
+#endif
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
new file mode 100644
index 000000000000..f0f5fe3a7c24
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
@@ -0,0 +1,757 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Handling of a single switch port
+ *
+ * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
+ * Based on net/dsa
+ */
+
+#include <linux/if_bridge.h>
+#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
+#include <linux/of_net.h>
+
+#include "ipqess_port.h"
+#include "ipqess_edma.h"
+#include "ipqess_switch.h"
+#include "ipqess_notifiers.h"
+
+#define ipqess_port_from_pl_state(config, pl_config)\
+container_of(config, struct ipqess_port, pl_config)
+
+static struct device_type ipqess_port_type = {
+ .name = "switch",
+};
+
+/* netdev ops */
+
+static void ipqess_port_fast_age(const struct ipqess_port *port)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+
+ mutex_lock(&priv->reg_mutex);
+ qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port->index);
+ mutex_unlock(&priv->reg_mutex);
+}
+
+static void ipqess_port_stp_state_set(struct ipqess_port *port,
+ u8 state)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+ u32 stp_state;
+ int err;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
+ break;
+ case BR_STATE_BLOCKING:
+ stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
+ break;
+ case BR_STATE_LISTENING:
+ stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
+ break;
+ case BR_STATE_LEARNING:
+ stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
+ break;
+ case BR_STATE_FORWARDING:
+ default:
+ stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
+ break;
+ }
+
+ err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+ QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
+
+ if (err)
+ dev_warn(priv->dev,
+ "failed to set STP state %d for port %d: err %d\n",
+ stp_state, port->index, err);
+}
+
+static void ipqess_port_set_state_now(struct ipqess_port *port,
+ u8 state, bool do_fast_age)
+{
+ ipqess_port_stp_state_set(port, state);
+
+ if ((port->stp_state == BR_STATE_LEARNING ||
+ port->stp_state == BR_STATE_FORWARDING) &&
+ (state == BR_STATE_DISABLED || state == BR_STATE_BLOCKING ||
+ state == BR_STATE_LISTENING))
+ ipqess_port_fast_age(port);
+
+ port->stp_state = state;
+}
+
+static int ipqess_port_enable_rt(struct ipqess_port *port,
+ struct phy_device *phy)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+
+ qca8k_port_set_status(priv, port->index, 1);
+ priv->port_enabled_map |= BIT(port->index);
+
+ phy_support_asym_pause(phy);
+
+ ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
+
+ if (port->pl)
+ phylink_start(port->pl);
+
+ return 0;
+}
+
+static void ipqess_port_disable_rt(struct ipqess_port *port)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+
+ if (port->pl)
+ phylink_stop(port->pl);
+
+ ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
+
+ qca8k_port_set_status(priv, port->index, 0);
+ priv->port_enabled_map &= ~BIT(port->index);
+}
+
+static int ipqess_port_open(struct net_device *netdev)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct phy_device *phy = netdev->phydev;
+
+ return ipqess_port_enable_rt(port, phy);
+}
+
+static int ipqess_port_close(struct net_device *netdev)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+
+ ipqess_port_disable_rt(port);
+
+ return 0;
+}
+
+static netdev_tx_t ipqess_port_xmit(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+
+ dev_sw_netstats_tx_add(netdev, 1, skb->len);
+
+ memset(skb->cb, 0, sizeof(skb->cb));
+
+ return ipqess_edma_xmit(skb, port->netdev);
+}
+
+static int ipqess_port_set_mac_address(struct net_device *netdev, void *a)
+{
+ struct sockaddr *addr = a;
+ int err;
+
+ /* If the port is down, the address isn't synced yet to hardware
+ * so there is nothing to change
+ */
+ if (!(netdev->flags & IFF_UP)) {
+ eth_hw_addr_set(netdev, addr->sa_data);
+ return 0;
+ }
+
+ if (!ether_addr_equal(addr->sa_data, netdev->dev_addr)) {
+ err = dev_uc_add(netdev, addr->sa_data);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int ipqess_port_ioctl(struct net_device *netdev, struct ifreq *ifr,
+ int cmd)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+
+ return phylink_mii_ioctl(port->pl, ifr, cmd);
+}
+
+static int ipqess_port_get_iflink(const struct net_device *dev)
+{
+ return dev->ifindex;
+}
+
+static int ipqess_port_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ struct qca8k_priv *priv = port->sw->priv;
+ int err;
+
+ /* To change the MAX_FRAME_SIZE, the cpu port must be off
+ * or the switch panics.
+ */
+ if (port->sw->port0_enabled)
+ qca8k_port_set_status(priv, 0, 0);
+
+ err = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu +
+ ETH_HLEN + ETH_FCS_LEN);
+
+ if (port->sw->port0_enabled)
+ qca8k_port_set_status(priv, 0, 1);
+
+ if (err)
+ return err;
+
+ dev->mtu = new_mtu;
+
+ return 0;
+}
+
+static int ipqess_port_do_vlan_add(struct qca8k_priv *priv, int port_index,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+ bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+ int ret;
+
+ ret = qca8k_vlan_add(priv, port_index, vlan->vid, untagged);
+ if (ret) {
+ dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port_index,
+ ret);
+ return ret;
+ }
+
+ if (pvid) {
+ ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
+ QCA8K_EGREES_VLAN_PORT_MASK(port_index),
+ QCA8K_EGREES_VLAN_PORT(port_index, vlan->vid));
+ if (ret)
+ return ret;
+
+ ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
+ QCA8K_PORT_VLAN_CVID(vlan->vid) |
+ QCA8K_PORT_VLAN_SVID(vlan->vid));
+ }
+
+ return ret;
+}
+
+static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
+ u16 vid)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ struct switchdev_obj_port_vlan vlan = {
+ .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
+ .vid = vid,
+ /* This API only allows programming tagged, non-PVID VIDs */
+ .flags = 0,
+ };
+ struct netlink_ext_ack extack = {0};
+ int ret;
+
+ /* User port... */
+ ret = ipqess_port_do_vlan_add(port->sw->priv, port->index, &vlan, &extack);
+ if (ret) {
+ if (extack._msg)
+ netdev_err(dev, "%s\n", extack._msg);
+ return ret;
+ }
+
+ /* And CPU port... */
+ ret = ipqess_port_do_vlan_add(port->sw->priv, 0, &vlan, &extack);
+ if (ret) {
+ if (extack._msg)
+ netdev_err(dev, "CPU port %d: %s\n", 0, extack._msg);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ipqess_port_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
+ u16 vid)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ int err;
+
+ err = qca8k_vlan_del(port->sw->priv, port->index, vid);
+ if (err)
+ return err;
+
+ err = qca8k_vlan_del(port->sw->priv, 0, vid);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int
+ipqess_port_fdb_do_dump(const unsigned char *addr, u16 vid,
+ bool is_static, void *data)
+{
+ struct ipqess_port_dump_ctx *dump = data;
+ u32 portid = NETLINK_CB(dump->cb->skb).portid;
+ u32 seq = dump->cb->nlh->nlmsg_seq;
+ struct nlmsghdr *nlh;
+ struct ndmsg *ndm;
+
+ if (dump->idx < dump->cb->args[2])
+ goto skip;
+
+ nlh = nlmsg_put(dump->skb, portid, seq, RTM_NEWNEIGH,
+ sizeof(*ndm), NLM_F_MULTI);
+ if (!nlh)
+ return -EMSGSIZE;
+
+ ndm = nlmsg_data(nlh);
+ ndm->ndm_family = AF_BRIDGE;
+ ndm->ndm_pad1 = 0;
+ ndm->ndm_pad2 = 0;
+ ndm->ndm_flags = NTF_SELF;
+ ndm->ndm_type = 0;
+ ndm->ndm_ifindex = dump->dev->ifindex;
+ ndm->ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE;
+
+ if (nla_put(dump->skb, NDA_LLADDR, ETH_ALEN, addr))
+ goto nla_put_failure;
+
+ if (vid && nla_put_u16(dump->skb, NDA_VLAN, vid))
+ goto nla_put_failure;
+
+ nlmsg_end(dump->skb, nlh);
+
+skip:
+ dump->idx++;
+ return 0;
+
+nla_put_failure:
+ nlmsg_cancel(dump->skb, nlh);
+ return -EMSGSIZE;
+}
+
+static int
+ipqess_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev, struct net_device *filter_dev,
+ int *idx)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ struct qca8k_priv *priv = port->sw->priv;
+ struct ipqess_port_dump_ctx dump = {
+ .dev = dev,
+ .skb = skb,
+ .cb = cb,
+ .idx = *idx,
+ };
+ int cnt = QCA8K_NUM_FDB_RECORDS;
+ struct qca8k_fdb _fdb = { 0 };
+ bool is_static;
+ int ret = 0;
+
+ mutex_lock(&priv->reg_mutex);
+ while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port->index)) {
+ if (!_fdb.aging)
+ break;
+ is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
+ ret = ipqess_port_fdb_do_dump(_fdb.mac, _fdb.vid, is_static, &dump);
+ if (ret)
+ break;
+ }
+ mutex_unlock(&priv->reg_mutex);
+
+ *idx = dump.idx;
+
+ return ret;
+}
+
+static const struct net_device_ops ipqess_port_netdev_ops = {
+ .ndo_open = ipqess_port_open,
+ .ndo_stop = ipqess_port_close,
+ .ndo_set_mac_address = ipqess_port_set_mac_address,
+ .ndo_eth_ioctl = ipqess_port_ioctl,
+ .ndo_start_xmit = ipqess_port_xmit,
+ .ndo_get_iflink = ipqess_port_get_iflink,
+ .ndo_change_mtu = ipqess_port_change_mtu,
+ .ndo_vlan_rx_add_vid = ipqess_port_vlan_rx_add_vid,
+ .ndo_vlan_rx_kill_vid = ipqess_port_vlan_rx_kill_vid,
+ .ndo_fdb_dump = ipqess_port_fdb_dump,
+};
+
+/* phylink ops */
+
+static void
+ipqess_phylink_mac_config(struct phylink_config *config,
+ unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
+ struct qca8k_priv *priv = port->sw->priv;
+
+ switch (port->index) {
+ case 0:
+ /* CPU port, no configuration needed */
+ return;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ if (phy_interface_mode_is_rgmii(state->interface))
+ regmap_set_bits(priv->regmap,
+ QCA8K_IPQ4019_REG_RGMII_CTRL,
+ QCA8K_IPQ4019_RGMII_CTRL_CLK);
+ return;
+ default:
+ dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
+ port->index);
+ return;
+ }
+}
+
+static void
+ipqess_phylink_mac_link_down(struct phylink_config *config,
+ unsigned int mode,
+ phy_interface_t interface)
+{
+ struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
+ struct qca8k_priv *priv = port->sw->priv;
+
+ qca8k_port_set_status(priv, port->index, 0);
+}
+
+static void ipqess_phylink_mac_link_up(struct phylink_config *config,
+ struct phy_device *phydev,
+ unsigned int mode,
+ phy_interface_t interface,
+ int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
+ struct qca8k_priv *priv = port->sw->priv;
+ u32 reg;
+
+ if (phylink_autoneg_inband(mode)) {
+ reg = QCA8K_PORT_STATUS_LINK_AUTO;
+ } else {
+ switch (speed) {
+ case SPEED_10:
+ reg = QCA8K_PORT_STATUS_SPEED_10;
+ break;
+ case SPEED_100:
+ reg = QCA8K_PORT_STATUS_SPEED_100;
+ break;
+ case SPEED_1000:
+ reg = QCA8K_PORT_STATUS_SPEED_1000;
+ break;
+ default:
+ reg = QCA8K_PORT_STATUS_LINK_AUTO;
+ break;
+ }
+
+ if (duplex == DUPLEX_FULL)
+ reg |= QCA8K_PORT_STATUS_DUPLEX;
+
+ if (rx_pause || port->index == 0)
+ reg |= QCA8K_PORT_STATUS_RXFLOW;
+
+ if (tx_pause || port->index == 0)
+ reg |= QCA8K_PORT_STATUS_TXFLOW;
+ }
+
+ reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
+
+ qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), reg);
+}
+
+static const struct phylink_mac_ops ipqess_phylink_mac_ops = {
+ .mac_config = ipqess_phylink_mac_config,
+ .mac_link_down = ipqess_phylink_mac_link_down,
+ .mac_link_up = ipqess_phylink_mac_link_up,
+};
+
+static int ipqess_phylink_create(struct net_device *netdev)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct phylink_config *pl_config = &port->pl_config;
+ phy_interface_t mode;
+ struct phylink *pl;
+ int err;
+
+ err = of_get_phy_mode(port->dn, &mode);
+ if (err)
+ mode = PHY_INTERFACE_MODE_NA;
+
+ switch (port->index) {
+ case 1:
+ case 2:
+ case 3:
+ __set_bit(PHY_INTERFACE_MODE_PSGMII,
+ pl_config->supported_interfaces);
+ break;
+ case 4:
+ case 5:
+ phy_interface_set_rgmii(pl_config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_PSGMII,
+ pl_config->supported_interfaces);
+ break;
+ case 0: /* CPU port, this shouldn't happen */
+ default:
+ return -EINVAL;
+ }
+ /* phylink caps */
+ pl_config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000FD;
+
+ pl = phylink_create(pl_config, of_fwnode_handle(port->dn),
+ mode, &ipqess_phylink_mac_ops);
+ if (IS_ERR(pl))
+ return PTR_ERR(pl);
+
+ port->pl = pl;
+ return 0;
+}
+
+static int ipqess_port_phy_connect(struct net_device *netdev, int addr,
+ u32 flags)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+
+ netdev->phydev = mdiobus_get_phy(port->mii_bus, addr);
+ if (!netdev->phydev) {
+ netdev_err(netdev, "no phy at %d\n", addr);
+ return -ENODEV;
+ }
+
+ netdev->phydev->dev_flags |= flags;
+
+ return phylink_connect_phy(port->pl, netdev->phydev);
+}
+
+static int ipqess_port_phy_setup(struct net_device *netdev)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct device_node *port_dn = port->dn;
+ u32 phy_flags = 0;
+ int ret;
+
+ port->pl_config.dev = &netdev->dev;
+ port->pl_config.type = PHYLINK_NETDEV;
+
+ ret = ipqess_phylink_create(netdev);
+ if (ret)
+ return ret;
+
+ ret = phylink_of_phy_connect(port->pl, port_dn, phy_flags);
+ if (ret == -ENODEV && port->mii_bus) {
+ /* We could not connect to a designated PHY or SFP, so try to
+ * use the switch internal MDIO bus instead
+ */
+ ret = ipqess_port_phy_connect(netdev, port->index, phy_flags);
+ }
+
+ if (ret) {
+ netdev_err(netdev, "failed to connect to PHY: %pe\n",
+ ERR_PTR(ret));
+ phylink_destroy(port->pl);
+ port->pl = NULL;
+ }
+
+ dev_info(&netdev->dev, "enabled port's phy: %s",
+ phydev_name(netdev->phydev));
+ return ret;
+}
+
+/* netlink */
+
+#define IFLA_IPQESS_UNSPEC 0
+#define IFLA_IPQESS_MAX 0
+
+static const struct nla_policy ipqess_port_policy[IFLA_IPQESS_MAX + 1] = {
+ [IFLA_IPQESS_MAX] = { .type = NLA_U32 },
+};
+
+static size_t ipqess_port_get_size(const struct net_device *dev)
+{
+ return nla_total_size(sizeof(u32));
+}
+
+static int ipqess_port_fill_info(struct sk_buff *skb,
+ const struct net_device *dev)
+{
+ if (nla_put_u32(skb, IFLA_IPQESS_UNSPEC, dev->ifindex))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static struct rtnl_link_ops ipqess_port_link_ops __read_mostly = {
+ .kind = "switch",
+ .priv_size = sizeof(struct ipqess_port),
+ .maxtype = 1,
+ .policy = ipqess_port_policy,
+ .get_size = ipqess_port_get_size,
+ .fill_info = ipqess_port_fill_info,
+ .netns_refund = true,
+};
+
+/* devlink */
+
+static int ipqess_port_devlink_setup(struct ipqess_port *port)
+{
+ struct devlink_port *dlp = &port->devlink_port;
+ struct devlink *dl = port->sw->devlink;
+ struct devlink_port_attrs attrs = {};
+ const unsigned char *id;
+ unsigned int index = 0;
+ unsigned char len;
+ int err;
+
+ id = (const unsigned char *)&index;
+ len = sizeof(index);
+ memset(dlp, 0, sizeof(*dlp));
+
+ attrs.phys.port_number = port->index;
+ memcpy(attrs.switch_id.id, id, len);
+ attrs.switch_id.id_len = len;
+ attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL;
+ devlink_port_attrs_set(dlp, &attrs);
+
+ err = devlink_port_register(dl, dlp, port->index);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+/* register */
+
+int ipqess_port_register(struct ipqess_switch *sw,
+ struct device_node *port_node)
+{
+ struct qca8k_priv *priv = sw->priv;
+ struct net_device *netdev;
+ struct ipqess_port *port;
+ const char *name;
+ int assign_type;
+ int num_queues;
+ u32 index;
+ int err;
+
+ err = of_property_read_u32(port_node, "reg", &index);
+ if (err) {
+ pr_err("Node without reg property!");
+ return err;
+ }
+
+ name = of_get_property(port_node, "label", NULL);
+ if (!name) {
+ name = "eth%d";
+ assign_type = NET_NAME_ENUM;
+ } else {
+ assign_type = NET_NAME_PREDICTABLE;
+ }
+
+ /* For the NAPI leader, we allocate one queue per MAC queue */
+ if (!sw->napi_leader)
+ num_queues = IPQESS_EDMA_NETDEV_QUEUES;
+ else
+ num_queues = 1;
+
+ netdev = alloc_netdev_mqs(sizeof(struct ipqess_port), name, assign_type,
+ ether_setup, num_queues, num_queues);
+ if (!netdev)
+ return -ENOMEM;
+
+ if (!sw->napi_leader)
+ sw->napi_leader = netdev;
+
+ port = netdev_priv(netdev);
+ port->index = (int)index;
+ port->dn = port_node;
+ port->netdev = netdev;
+ port->edma = NULL; /* Assigned during edma initialization */
+ port->qid = port->index - 1;
+ port->sw = sw;
+
+ of_get_mac_address(port_node, port->mac);
+ if (!is_zero_ether_addr(port->mac))
+ eth_hw_addr_set(netdev, port->mac);
+ else
+ eth_hw_addr_random(netdev);
+
+ netdev->netdev_ops = &ipqess_port_netdev_ops;
+ netdev->max_mtu = QCA8K_MAX_MTU;
+ SET_NETDEV_DEVTYPE(netdev, &ipqess_port_type);
+ SET_NETDEV_DEV(netdev, priv->dev);
+ SET_NETDEV_DEVLINK_PORT(netdev, &port->devlink_port);
+ netdev->dev.of_node = port->dn;
+
+ netdev->rtnl_link_ops = &ipqess_port_link_ops;
+
+ netdev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+ if (!netdev->tstats) {
+ free_netdev(netdev);
+ return -ENOMEM;
+ }
+
+ err = ipqess_port_devlink_setup(port);
+ if (err)
+ goto out_free;
+
+ err = gro_cells_init(&port->gcells, netdev);
+ if (err)
+ goto out_devlink;
+
+ err = ipqess_port_phy_setup(netdev);
+ if (err) {
+ pr_err("error setting up PHY: %d\n", err);
+ goto out_gcells;
+ }
+
+ /* We use the qid and not the index because port 0 isn't registered */
+ sw->port_list[port->qid] = port;
+
+ err = register_netdev(netdev);
+ if (err) {
+ pr_err("error %d registering interface %s\n",
+ err, netdev->name);
+ rtnl_unlock();
+ goto out_phy;
+ }
+
+ return 0;
+
+out_phy:
+ rtnl_lock();
+ phylink_disconnect_phy(port->pl);
+ rtnl_unlock();
+ phylink_destroy(port->pl);
+ port->pl = NULL;
+out_gcells:
+ gro_cells_destroy(&port->gcells);
+out_devlink:
+ devlink_port_unregister(&port->devlink_port);
+out_free:
+ free_percpu(netdev->tstats);
+ free_netdev(netdev);
+ sw->port_list[port->qid] = NULL;
+ return err;
+}
+
+void ipqess_port_unregister(struct ipqess_port *port)
+{
+ struct net_device *netdev = port->netdev;
+
+ unregister_netdev(netdev);
+
+ devlink_port_unregister(&port->devlink_port);
+
+ rtnl_lock();
+ phylink_disconnect_phy(port->pl);
+ rtnl_unlock();
+ phylink_destroy(port->pl);
+ port->pl = NULL;
+
+ gro_cells_destroy(&port->gcells);
+
+ free_percpu(netdev->tstats);
+ free_netdev(netdev);
+}
+
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
new file mode 100644
index 000000000000..26bac45dd811
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 OR ISC */
+
+#ifndef IPQESS_PORT_H
+#define IPQESS_PORT_H
+
+#include <net/gro_cells.h>
+#include <net/devlink.h>
+
+#include "ipqess_edma.h"
+#include "ipqess_switch.h"
+
+struct ipqess_port {
+ u16 index;
+ u16 qid;
+
+ struct ipqess_edma *edma;
+ struct ipqess_switch *sw;
+ struct phylink *pl;
+ struct phylink_config pl_config;
+ struct device_node *dn;
+ struct mii_bus *mii_bus;
+ struct net_device *netdev;
+ struct devlink_port devlink_port;
+
+ u8 stp_state;
+
+ u8 mac[ETH_ALEN];
+
+ /* Warning: the following bit field is not atomic, and updating it
+ * can only be done from code paths where concurrency is not possible
+ * (probe time or under rtnl_lock).
+ */
+ u8 vlan_filtering:1;
+
+ unsigned int ageing_time;
+
+ struct gro_cells gcells;
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+ struct netpoll *netpoll;
+#endif
+};
+
+struct ipqess_port_dump_ctx {
+ struct net_device *dev;
+ struct sk_buff *skb;
+ struct netlink_callback *cb;
+ int idx;
+};
+
+struct ipqess_mac_addr {
+ unsigned char addr[ETH_ALEN];
+ u16 vid;
+ refcount_t refcount;
+ struct list_head list;
+};
+
+int ipqess_port_register(struct ipqess_switch *sw,
+ struct device_node *port_node);
+void ipqess_port_unregister(struct ipqess_port *port);
+
+#endif
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
new file mode 100644
index 000000000000..927f834a62bc
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
@@ -0,0 +1,546 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
+ * Based on net/dsa
+ */
+
+#include <linux/dsa/qca8k.h>
+#include <linux/of_platform.h>
+#include <linux/of_mdio.h>
+#include <linux/reset.h>
+
+#include "ipqess_switch.h"
+#include "ipqess_port.h"
+#include "ipqess_edma.h"
+
+static struct regmap_config qca8k_ipqess_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x16ac, /* end MIB - Port6 range */
+ .rd_table = &qca8k_readable_table,
+};
+
+static struct regmap_config qca8k_ipqess_psgmii_phy_regmap_config = {
+ .name = "psgmii-phy",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x7fc,
+};
+
+static const struct qca8k_match_data ipqess = {
+ .id = IPQESS_SWITCH_ID,
+ .mib_count = QCA8K_QCA833X_MIB_COUNT,
+};
+
+/* devlink */
+
+static const struct devlink_ops ipqess_devlink_ops = {
+ /* no ops supported by this driver */
+};
+
+static int ipqess_switch_devlink_alloc(struct ipqess_switch *sw)
+{
+ struct ipqess_switch **dl_priv;
+ struct devlink *dl;
+
+ /* Add the switch to devlink before calling setup, so that setup can
+ * add dpipe tables
+ */
+ dl = devlink_alloc(&ipqess_devlink_ops, sizeof(*dl_priv), sw->priv->dev);
+ if (!dl)
+ return -ENOMEM;
+
+ sw->devlink = dl;
+
+ dl_priv = devlink_priv(sw->devlink);
+ *dl_priv = sw;
+
+ return 0;
+}
+
+/* setup */
+
+unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
+ unsigned int ageing_time)
+{
+ struct ipqess_port *port;
+ int i;
+
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ port = sw->port_list[i];
+ if (port && port->ageing_time && port->ageing_time < ageing_time)
+ ageing_time = port->ageing_time;
+ }
+
+ return ageing_time;
+}
+
+int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs)
+{
+ struct qca8k_priv *priv = sw->priv;
+ unsigned int secs = msecs / 1000;
+ u32 val;
+
+ /* AGE_TIME reg is set in 7s step */
+ val = secs / 7;
+
+ /* Handle case with 0 as val to NOT disable
+ * learning
+ */
+ if (!val)
+ val = 1;
+
+ return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL,
+ QCA8K_ATU_AGE_TIME_MASK,
+ QCA8K_ATU_AGE_TIME(val));
+}
+
+static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
+{
+ return container_of(pcs, struct qca8k_pcs, pcs);
+}
+
+static void ipqess_switch_pcs_get_state(struct phylink_pcs *pcs,
+ struct phylink_link_state *state)
+{
+ struct qca8k_priv *priv = pcs_to_qca8k_pcs(pcs)->priv;
+ int port = pcs_to_qca8k_pcs(pcs)->port;
+ u32 reg;
+ int ret;
+
+ ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), ®);
+ if (ret < 0) {
+ state->link = false;
+ return;
+ }
+
+ state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
+ state->an_complete = state->link;
+ state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
+ DUPLEX_HALF;
+
+ switch (reg & QCA8K_PORT_STATUS_SPEED) {
+ case QCA8K_PORT_STATUS_SPEED_10:
+ state->speed = SPEED_10;
+ break;
+ case QCA8K_PORT_STATUS_SPEED_100:
+ state->speed = SPEED_100;
+ break;
+ case QCA8K_PORT_STATUS_SPEED_1000:
+ state->speed = SPEED_1000;
+ break;
+ default:
+ state->speed = SPEED_UNKNOWN;
+ break;
+ }
+
+ if (reg & QCA8K_PORT_STATUS_RXFLOW)
+ state->pause |= MLO_PAUSE_RX;
+ if (reg & QCA8K_PORT_STATUS_TXFLOW)
+ state->pause |= MLO_PAUSE_TX;
+}
+
+static int ipqess_switch_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ return 0;
+}
+
+static void ipqess_switch_pcs_an_restart(struct phylink_pcs *pcs)
+{
+}
+
+static const struct phylink_pcs_ops qca8k_pcs_ops = {
+ .pcs_get_state = ipqess_switch_pcs_get_state,
+ .pcs_config = ipqess_switch_pcs_config,
+ .pcs_an_restart = ipqess_switch_pcs_an_restart,
+};
+
+static void ipqess_switch_setup_pcs(struct qca8k_priv *priv,
+ struct qca8k_pcs *qpcs,
+ int port_index)
+{
+ qpcs->pcs.ops = &qca8k_pcs_ops;
+
+ /* We don't have interrupts for link changes, so we need to poll */
+ qpcs->pcs.poll = true;
+ qpcs->priv = priv;
+ qpcs->port = port_index;
+}
+
+static int ipqess_switch_setup_port(struct ipqess_switch *sw, int port_index)
+{
+ struct qca8k_priv *priv = sw->priv;
+ u32 mask = 0;
+ int ret, i;
+ u32 reg;
+
+ /* CPU port gets connected to all registered ports of the switch */
+ if (port_index == IPQESS_SWITCH_CPU_PORT) {
+ for (i = 1; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ if (sw->port_list[i - 1])
+ mask |= BIT(i);
+ }
+ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_index),
+ QCA8K_PORT_LOOKUP_MEMBER, mask);
+ if (ret)
+ return ret;
+ qca8k_read(priv, QCA8K_PORT_LOOKUP_CTRL(IPQESS_SWITCH_CPU_PORT), ®);
+
+ /* Disable CPU ARP Auto-learning by default */
+ ret = regmap_clear_bits(priv->regmap,
+ QCA8K_PORT_LOOKUP_CTRL(port_index),
+ QCA8K_PORT_LOOKUP_LEARN);
+ if (ret)
+ return ret;
+ }
+
+ /* Individual user ports get connected to CPU port only */
+ if (port_index > 0 && sw->port_list[port_index - 1]) {
+ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_index),
+ QCA8K_PORT_LOOKUP_MEMBER,
+ BIT(IPQESS_SWITCH_CPU_PORT));
+ if (ret)
+ return ret;
+
+ /* Enable ARP Auto-learning by default */
+ ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port_index),
+ QCA8K_PORT_LOOKUP_LEARN);
+ if (ret)
+ return ret;
+
+ /* For port based vlans to work we need to set the
+ * default egress vid
+ */
+ ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
+ QCA8K_EGREES_VLAN_PORT_MASK(port_index),
+ QCA8K_EGREES_VLAN_PORT(port_index, QCA8K_PORT_VID_DEF));
+ if (ret)
+ return ret;
+
+ ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
+ QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
+ QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int ipqess_switch_setup(struct ipqess_switch *sw)
+{
+ struct qca8k_priv *priv = sw->priv;
+ int ret, i;
+ u32 reg;
+
+ ipqess_switch_setup_pcs(priv, &priv->pcs_port_0, 0);
+
+ /* Enable CPU Port */
+ ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
+ QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
+ if (ret) {
+ dev_err(priv->dev, "failed enabling CPU port");
+ return ret;
+ }
+
+ /* Enable MIB counters */
+ ret = qca8k_mib_init(priv);
+ if (ret)
+ dev_warn(priv->dev, "MIB init failed");
+
+ /* Disable forwarding by default on all ports */
+ for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
+ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+ QCA8K_PORT_LOOKUP_MEMBER, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Enable QCA header mode on the CPU port */
+ ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(IPQESS_SWITCH_CPU_PORT),
+ FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
+ FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
+ if (ret) {
+ dev_err(priv->dev, "failed enabling QCA header mode");
+ return ret;
+ }
+
+ /* Disable MAC by default on all ports */
+ for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
+ if (i > 0)
+ qca8k_port_set_status(priv, i, 0);
+ }
+
+ /* Forward all unknown frames to all ports */
+ ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
+ FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, 0x3f) |
+ FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, 0x3f) |
+ FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, 0x3f) |
+ FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, 0x3f));
+ if (ret) {
+ pr_err("Error while disabling MAC and forwarding unknown frames %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Setup connection between CPU port & user ports */
+ for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
+ ret = ipqess_switch_setup_port(sw, i);
+ if (ret)
+ return ret;
+ }
+
+ /* Setup our port MTUs to match power on defaults */
+ ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
+ if (ret)
+ dev_warn(priv->dev, "failed setting MTU settings");
+
+ /* Flush the FDB table */
+ qca8k_fdb_flush(priv);
+
+ if (ret < 0)
+ goto devlink_free;
+
+ /* set Port0 status */
+ reg = QCA8K_PORT_STATUS_LINK_AUTO;
+ reg |= QCA8K_PORT_STATUS_DUPLEX;
+ reg |= QCA8K_PORT_STATUS_SPEED_1000;
+ reg |= QCA8K_PORT_STATUS_RXFLOW;
+ reg |= QCA8K_PORT_STATUS_TXFLOW;
+ reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
+ qca8k_write(priv, QCA8K_REG_PORT_STATUS(0), reg);
+ sw->port0_enabled = true;
+
+ return 0;
+
+devlink_free:
+ pr_err("qca_switch_setup error: %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL(ipqess_switch_setup);
+
+/* probe */
+
+static void ipqess_switch_psgmii_rst(struct ipqess_switch *sw)
+{
+ reset_control_assert(sw->psgmii_rst);
+
+ mdelay(10);
+
+ reset_control_deassert(sw->psgmii_rst);
+
+ mdelay(10);
+}
+
+static int ipqess_switch_probe(struct platform_device *pdev)
+{
+ struct device_node *ports, *port_np;
+ struct device_node *np, *mdio_np;
+ struct device *dev = &pdev->dev;
+ struct ipqess_port *port = NULL;
+ void __iomem *base, *psgmii;
+ struct ipqess_switch *sw;
+ struct qca8k_priv *priv;
+ int ret;
+ int i;
+
+ sw = devm_kzalloc(dev, sizeof(struct ipqess_switch), GFP_KERNEL);
+ if (!sw)
+ return -ENOMEM;
+
+ priv = devm_kzalloc(dev, sizeof(struct qca8k_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ sw->priv = priv;
+ sw->port0_enabled = false;
+ priv->dev = dev;
+ priv->info = &ipqess;
+ priv->ds = NULL;
+
+ np = dev->of_node;
+ ports = of_get_child_by_name(np, "ports");
+ if (!ports) {
+ dev_err(dev, "no 'ports' attribute found\n");
+ return -EINVAL;
+ }
+
+ /* Start by setting up the register mapping */
+ base = devm_platform_ioremap_resource_byname(pdev, "base");
+ if (IS_ERR(base)) {
+ dev_err(dev, "platform ioremap fail %li\n", PTR_ERR(base));
+ return PTR_ERR(base);
+ }
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &qca8k_ipqess_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ ret = PTR_ERR(priv->regmap);
+ dev_err(dev, "base regmap initialization failed, %d\n", ret);
+ return ret;
+ }
+
+ psgmii = devm_platform_ioremap_resource_byname(pdev, "psgmii_phy");
+ if (IS_ERR(psgmii)) {
+ ret = PTR_ERR(psgmii);
+ dev_err(dev, "platform ioremap psgmii fail %d\n", ret);
+ return ret;
+ }
+
+ priv->psgmii = devm_regmap_init_mmio(dev, psgmii,
+ &qca8k_ipqess_psgmii_phy_regmap_config);
+ if (IS_ERR(priv->psgmii)) {
+ ret = PTR_ERR(priv->psgmii);
+ dev_err(dev, "PSGMII regmap initialization failed, %d\n", ret);
+ return ret;
+ }
+
+ mdio_np = of_parse_phandle(np, "mdio", 0);
+ if (!mdio_np) {
+ dev_err(dev, "unable to get MDIO bus phandle\n");
+ of_node_put(mdio_np);
+ return -EINVAL;
+ }
+
+ priv->bus = of_mdio_find_bus(mdio_np);
+ of_node_put(mdio_np);
+ if (!priv->bus) {
+ dev_err(dev, "unable to find MDIO bus\n");
+ return -EPROBE_DEFER;
+ }
+
+ /* If we don't reset the PSGMII here the switch id check will fail */
+ sw->psgmii_rst = devm_reset_control_get(&pdev->dev, "psgmii");
+ if (IS_ERR(sw->psgmii_rst)) {
+ ret = PTR_ERR(sw->psgmii_rst);
+ dev_err(dev, "Unable to get PSGMII reset line: err %d\n", ret);
+ return ret;
+ }
+
+ ipqess_switch_psgmii_rst(sw);
+
+ /* Check the detected switch id */
+ ret = qca8k_read_switch_id(sw->priv);
+ if (ret) {
+ dev_err(dev, "Failed to read switch id! error %d\n", ret);
+ return ret;
+ }
+
+ priv->ds = NULL;
+
+ mutex_init(&sw->addr_lists_lock);
+ INIT_LIST_HEAD(&sw->fdbs);
+ INIT_LIST_HEAD(&sw->mdbs);
+
+ mutex_init(&priv->reg_mutex);
+ platform_set_drvdata(pdev, sw);
+
+ ret = ipqess_switch_devlink_alloc(sw);
+ if (ret)
+ goto out_devlink;
+
+ devlink_register(sw->devlink);
+
+ /* Register switch front-facing ports */
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++)
+ sw->port_list[i] = NULL;
+
+ for_each_available_child_of_node(ports, port_np) {
+ ret = ipqess_port_register(sw, port_np);
+ if (ret) {
+ pr_err("Failed to register ipqess port! error %d\n", ret);
+ goto out_ports;
+ }
+ }
+ if (!sw->napi_leader) {
+ pr_err("No switch port registered as napi leader!\n");
+ ret = -EINVAL;
+ goto out_ports;
+ }
+
+ ret = ipqess_edma_init(pdev, np);
+ if (ret) {
+ dev_err(dev, "Failed to initialize EDMA controller! error %d\n", ret);
+ goto out_ports;
+ }
+
+ ipqess_switch_setup(sw);
+
+ return 0;
+
+out_ports:
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ port = sw->port_list[i];
+ if (port)
+ ipqess_port_unregister(port);
+ }
+out_devlink:
+ devlink_free(sw->devlink);
+ pr_err("%s failed with error %d\n", __func__, ret);
+ return ret;
+}
+
+static int
+ipqess_switch_remove(struct platform_device *pdev)
+{
+ struct ipqess_switch *sw = platform_get_drvdata(pdev);
+ struct qca8k_priv *priv = sw->priv;
+ struct ipqess_port *port = NULL;
+ int i;
+
+ if (!sw)
+ return 0;
+
+ /* Release EDMA driver */
+ ipqess_edma_uninit(sw->edma);
+
+ /* Disable all user ports */
+ for (i = 1; i < QCA8K_NUM_PORTS; i++) {
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+ QCA8K_PORT_LOOKUP_STATE_MASK,
+ QCA8K_PORT_LOOKUP_STATE_DISABLED);
+ qca8k_port_set_status(priv, i, 0);
+ priv->port_enabled_map &= ~BIT(i);
+ }
+
+ /* Unregister user ports */
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ port = sw->port_list[i];
+ if (port)
+ ipqess_port_unregister(port);
+ }
+
+ devlink_unregister(sw->devlink);
+ devlink_free(sw->devlink);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id qca8k_ipqess_of_match[] = {
+ { .compatible = "qcom,ipq4019-ess", },
+ { /* sentinel */ },
+};
+
+static struct platform_driver qca8k_ipqess_driver = {
+ .probe = ipqess_switch_probe,
+ .remove = ipqess_switch_remove,
+ .driver = {
+ .name = "ipqess",
+ .of_match_table = qca8k_ipqess_of_match,
+ },
+};
+
+module_platform_driver(qca8k_ipqess_driver);
+
+MODULE_AUTHOR("Romain Gantois <romain.gantois@bootlin.org>");
+MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
+MODULE_AUTHOR("Gabor Juhos <j4g8y7@gmail.com>, Robert Marko <robert.marko@sartura.hr>");
+MODULE_DESCRIPTION("Qualcomm IPQ4019 Ethernet Switch Subsystem driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
new file mode 100644
index 000000000000..221c628d1728
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 OR ISC */
+
+#ifndef IPQESS_SWITCH_H
+#define IPQESS_SWITCH_H
+
+#include <linux/dsa/qca8k.h>
+
+#define IPQESS_SWITCH_MAX_PORTS 5
+#define IPQESS_SWITCH_AGEING_TIME_MIN 7000
+#define IPQESS_SWITCH_AGEING_TIME_MAX 458745000
+#define IPQESS_SWITCH_CPU_PORT 0
+#define IPQESS_SWITCH_NUM_PORTS 5
+#define IPQESS_SWITCH_ID 0x14
+
+struct ipqess_switch {
+ struct net_device *napi_leader;
+ struct qca8k_priv *priv;
+ struct ipqess_edma *edma;
+ struct ipqess_port *port_list[IPQESS_SWITCH_MAX_PORTS];
+ struct devlink *devlink;
+ struct reset_control *psgmii_rst;
+ bool port0_enabled;
+
+ /* List of MAC addresses that must be forwarded on the cpu port */
+ struct mutex addr_lists_lock;
+ struct list_head fdbs;
+ struct list_head mdbs;
+};
+
+unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
+ unsigned int ageing_time);
+int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs);
+
+int ipqess_switch_setup(struct ipqess_switch *sw);
+
+#endif
diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
index 3c75c3704fa0..cafb727f4e8b 100644
--- a/include/linux/dsa/qca8k.h
+++ b/include/linux/dsa/qca8k.h
@@ -266,6 +266,7 @@
#define QCA8K_PORT_LOOKUP_STATE_LEARNING QCA8K_PORT_LOOKUP_STATE(0x3)
#define QCA8K_PORT_LOOKUP_STATE_FORWARD QCA8K_PORT_LOOKUP_STATE(0x4)
#define QCA8K_PORT_LOOKUP_LEARN BIT(20)
+#define QCA8K_PORT_LOOKUP_LOOPBACK_EN BIT(21)
#define QCA8K_PORT_LOOKUP_ING_MIRROR_EN BIT(25)
#define QCA8K_REG_GOL_TRUNK_CTRL0 0x700
@@ -342,6 +343,31 @@
#define MII_ATH_MMD_ADDR 0x0d
#define MII_ATH_MMD_DATA 0x0e
+/* IPQ4019 PSGMII PHY registers */
+#define QCA8K_IPQ4019_REG_RGMII_CTRL 0x004
+#define QCA8K_IPQ4019_RGMII_CTRL_RGMII_RXC GENMASK(1, 0)
+#define QCA8K_IPQ4019_RGMII_CTRL_RGMII_TXC GENMASK(9, 8)
+/* Some kind of CLK selection
+ * 0: gcc_ess_dly2ns
+ * 1: gcc_ess_clk
+ */
+#define QCA8K_IPQ4019_RGMII_CTRL_CLK BIT(10)
+#define QCA8K_IPQ4019_RGMII_CTRL_DELAY_RMII0 GENMASK(17, 16)
+#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII0_REF_CLK BIT(18)
+#define QCA8K_IPQ4019_RGMII_CTRL_DELAY_RMII1 GENMASK(20, 19)
+#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII1_REF_CLK BIT(21)
+#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII0_MASTER_EN BIT(24)
+#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII1_MASTER_EN BIT(25)
+
+#define PSGMIIPHY_MODE_CONTROL 0x1b4
+#define PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M BIT(0)
+#define PSGMIIPHY_TX_CONTROL 0x288
+#define PSGMIIPHY_TX_CONTROL_MAGIC_VALUE 0x8380
+#define PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_1 0x9c
+#define PSGMIIPHY_REG_PLL_VCO_CALIB_RESTART BIT(14)
+#define PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_2 0xa0
+#define PSGMIIPHY_REG_PLL_VCO_CALIB_READY BIT(0)
+
enum {
QCA8K_PORT_SPEED_10M = 0,
QCA8K_PORT_SPEED_100M = 1,
@@ -467,6 +493,9 @@ struct qca8k_priv {
struct qca8k_pcs pcs_port_6;
const struct qca8k_match_data *info;
struct qca8k_led ports_led[QCA8K_LED_COUNT];
+
+ /* IPQ4019 specific */
+ struct regmap *psgmii;
};
struct qca8k_mib_desc {
@@ -507,6 +536,8 @@ int qca8k_read_switch_id(struct qca8k_priv *priv);
int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val);
int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val);
int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
+int qca8k_set_bits(struct qca8k_priv *priv, u32 reg, u32 bits);
+int qca8k_clear_bits(struct qca8k_priv *priv, u32 reg, u32 bits);
/* Common ops function */
void qca8k_fdb_flush(struct qca8k_priv *priv);
@@ -560,6 +591,16 @@ int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
struct dsa_db db);
int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
dsa_fdb_dump_cb_t *cb, void *data);
+int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
+ u16 port_mask, u16 vid);
+int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb,
+ int port);
+int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
+ int port);
+int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
+ const u8 *mac, u16 vid, u8 aging);
+int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
+ const u8 *mac, u16 vid);
/* Common MDB function */
int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
@@ -577,8 +618,12 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
struct dsa_mall_mirror_tc_entry *mirror);
/* Common port VLAN function */
-int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
+int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
+ bool vlan_filtering,
struct netlink_ext_ack *extack);
+int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
+ bool untagged);
+int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid);
int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack);
--
2.42.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Romain Gantois
@ 2023-11-14 19:10 ` Andrew Lunn
2023-11-15 14:24 ` Romain Gantois
2023-11-15 12:55 ` Wojciech Drewek
` (4 subsequent siblings)
5 siblings, 1 reply; 37+ messages in thread
From: Andrew Lunn @ 2023-11-14 19:10 UTC (permalink / raw)
To: Romain Gantois
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
> +static void ipqess_port_stp_state_set(struct ipqess_port *port,
> + u8 state)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> + u32 stp_state;
> + int err;
> +
> + switch (state) {
> + case BR_STATE_DISABLED:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
> + break;
> + case BR_STATE_BLOCKING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
> + break;
> + case BR_STATE_LISTENING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
> + break;
> + case BR_STATE_LEARNING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
> + break;
> + case BR_STATE_FORWARDING:
> + default:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
> + break;
> + }
> +
> + err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
> + QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
When i compare this to qca8k_port_stp_state_set() it is 90% identical.
What differs is how you get to struct qca8k_priv *priv. What you need
to do is refactor the existing functions to separate the DSA parts out
and have a core function which takes qca8k_priv and in port. The DSA
core can then call it, and this function can call it, after extracting
qca8k_priv and index from port.
> +static int ipqess_port_enable_rt(struct ipqess_port *port,
> + struct phy_device *phy)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + qca8k_port_set_status(priv, port->index, 1);
> + priv->port_enabled_map |= BIT(port->index);
> +
> + phy_support_asym_pause(phy);
> +
> + ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
> +
> + if (port->pl)
> + phylink_start(port->pl);
That looks odd. You unconditionally call phy_support_asym_pause() yet
conditionally call phylink_start(). I would expect there to always be
a phylink instance.
Also, you should be telling phylink about the pause capabilities in
config->mac_capabilities. It is then phylinks problem to tell the PHY,
or the PCS driving the SFP etc about pause.
> +static int
> +ipqess_port_fdb_do_dump(const unsigned char *addr, u16 vid,
> + bool is_static, void *data)
> +{
> + struct ipqess_port_dump_ctx *dump = data;
> + u32 portid = NETLINK_CB(dump->cb->skb).portid;
> + u32 seq = dump->cb->nlh->nlmsg_seq;
> + struct nlmsghdr *nlh;
> + struct ndmsg *ndm;
> +
> + if (dump->idx < dump->cb->args[2])
> + goto skip;
> +
> + nlh = nlmsg_put(dump->skb, portid, seq, RTM_NEWNEIGH,
> + sizeof(*ndm), NLM_F_MULTI);
> + if (!nlh)
> + return -EMSGSIZE;
> +
> + ndm = nlmsg_data(nlh);
> + ndm->ndm_family = AF_BRIDGE;
> + ndm->ndm_pad1 = 0;
> + ndm->ndm_pad2 = 0;
> + ndm->ndm_flags = NTF_SELF;
> + ndm->ndm_type = 0;
> + ndm->ndm_ifindex = dump->dev->ifindex;
> + ndm->ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE;
> +
> + if (nla_put(dump->skb, NDA_LLADDR, ETH_ALEN, addr))
> + goto nla_put_failure;
> +
> + if (vid && nla_put_u16(dump->skb, NDA_VLAN, vid))
> + goto nla_put_failure;
> +
> + nlmsg_end(dump->skb, nlh);
> +
> +skip:
> + dump->idx++;
> + return 0;
> +
> +nla_put_failure:
> + nlmsg_cancel(dump->skb, nlh);
> + return -EMSGSIZE;
> +}
This looks identical to dsa_slave_port_fdb_do_dump(). Please export
and reuse it.
> +
> +static int
> +ipqess_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
> + struct net_device *dev, struct net_device *filter_dev,
> + int *idx)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + struct qca8k_priv *priv = port->sw->priv;
> + struct ipqess_port_dump_ctx dump = {
> + .dev = dev,
> + .skb = skb,
> + .cb = cb,
> + .idx = *idx,
> + };
> + int cnt = QCA8K_NUM_FDB_RECORDS;
> + struct qca8k_fdb _fdb = { 0 };
> + bool is_static;
> + int ret = 0;
> +
> + mutex_lock(&priv->reg_mutex);
> + while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port->index)) {
> + if (!_fdb.aging)
> + break;
> + is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
> + ret = ipqess_port_fdb_do_dump(_fdb.mac, _fdb.vid, is_static, &dump);
> + if (ret)
> + break;
> + }
> + mutex_unlock(&priv->reg_mutex);
> +
> + *idx = dump.idx;
> +
> + return ret;
> +}
And with a little bit of refactoring you can reuse the core of
qca8k_port_fdb_dump.
> +static void ipqess_phylink_mac_link_up(struct phylink_config *config,
> + struct phy_device *phydev,
> + unsigned int mode,
> + phy_interface_t interface,
> + int speed, int duplex,
> + bool tx_pause, bool rx_pause)
> +{
> + struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> + struct qca8k_priv *priv = port->sw->priv;
> + u32 reg;
> +
> + if (phylink_autoneg_inband(mode)) {
> + reg = QCA8K_PORT_STATUS_LINK_AUTO;
> + } else {
> + switch (speed) {
> + case SPEED_10:
> + reg = QCA8K_PORT_STATUS_SPEED_10;
> + break;
> + case SPEED_100:
> + reg = QCA8K_PORT_STATUS_SPEED_100;
> + break;
> + case SPEED_1000:
> + reg = QCA8K_PORT_STATUS_SPEED_1000;
> + break;
> + default:
> + reg = QCA8K_PORT_STATUS_LINK_AUTO;
> + break;
> + }
> +
> + if (duplex == DUPLEX_FULL)
> + reg |= QCA8K_PORT_STATUS_DUPLEX;
> +
> + if (rx_pause || port->index == 0)
> + reg |= QCA8K_PORT_STATUS_RXFLOW;
> +
> + if (tx_pause || port->index == 0)
> + reg |= QCA8K_PORT_STATUS_TXFLOW;
> + }
> +
> + reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
> +
> + qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), reg);
> +}
qca8k_phylink_mac_link_up() with some refactoring can be
reused. Please look through the driver and find other instances like
this where you can reuse more code.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-14 19:10 ` Andrew Lunn
@ 2023-11-15 14:24 ` Romain Gantois
0 siblings, 0 replies; 37+ messages in thread
From: Romain Gantois @ 2023-11-15 14:24 UTC (permalink / raw)
To: Andrew Lunn
Cc: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski,
Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
devicetree, thomas.petazzoni, Florian Fainelli, Heiner Kallweit,
Russell King, linux-arm-kernel, Vladimir Oltean, Luka Perkov,
Robert Marko, Andy Gross, Bjorn Andersson, Konrad Dybcio
On Tue, 14 Nov 2023, Andrew Lunn wrote:
...
> > + phy_support_asym_pause(phy);
> > +
> > + ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
> > +
> > + if (port->pl)
> > + phylink_start(port->pl);
>
> That looks odd. You unconditionally call phy_support_asym_pause() yet
> conditionally call phylink_start(). I would expect there to always be
> a phylink instance.
>
> Also, you should be telling phylink about the pause capabilities in
> config->mac_capabilities. It is then phylinks problem to tell the PHY,
> or the PCS driving the SFP etc about pause.
You are correct. I probably fumbled this when splitting the calibration code.
> > + if (tx_pause || port->index == 0)
> > + reg |= QCA8K_PORT_STATUS_TXFLOW;
> > + }
> > +
> > + reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
> > +
> > + qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), reg);
> > +}
>
> qca8k_phylink_mac_link_up() with some refactoring can be
> reused. Please look through the driver and find other instances like
> this where you can reuse more code.
I tried to be conservative with modifying qca8k-common.c when it required
modifying qca8k-8xxx.c. But I'll factor this code more aggressively since you
think it is preferable.
Best,
--
Romain Gantois, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Romain Gantois
2023-11-14 19:10 ` Andrew Lunn
@ 2023-11-15 12:55 ` Wojciech Drewek
2023-11-15 15:07 ` Romain Gantois
2023-11-16 14:39 ` Vladimir Oltean
2023-11-15 18:11 ` Simon Horman
` (3 subsequent siblings)
5 siblings, 2 replies; 37+ messages in thread
From: Wojciech Drewek @ 2023-11-15 12:55 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
devicetree, thomas.petazzoni, Andrew Lunn, Florian Fainelli,
Heiner Kallweit, Russell King, linux-arm-kernel, Vladimir Oltean,
Luka Perkov, Robert Marko, Andy Gross, Bjorn Andersson,
Konrad Dybcio
On 14.11.2023 11:55, Romain Gantois wrote:
> The Qualcomm IPQ4019 Ethernet Switch Subsystem for the IPQ4019 chip
> includes an internal Ethernet switch based on the QCA8K IP.
>
> The CPU-to-switch port data plane depends on the IPQESS EDMA Controller,
> a simple 1G Ethernet controller. It is connected to the switch through an
> internal link, and doesn't expose directly any external interface.
>
> The EDMA controller has 16 RX and TX queues, with a very basic RSS fanout
> configured at init time.
>
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
Hi Romain,
I see that you splitted the patch, thanks for that.
However it's still too big IMHO, edma ipqess_edma could be introduced in seperate file e.g.
> ---
> MAINTAINERS | 7 +
> drivers/net/dsa/qca/qca8k-common.c | 47 +-
> drivers/net/ethernet/qualcomm/Kconfig | 14 +
> drivers/net/ethernet/qualcomm/Makefile | 2 +
> drivers/net/ethernet/qualcomm/ipqess/Makefile | 8 +
> .../ethernet/qualcomm/ipqess/ipqess_edma.c | 1188 +++++++++++++++++
> .../ethernet/qualcomm/ipqess/ipqess_edma.h | 488 +++++++
> .../qualcomm/ipqess/ipqess_notifiers.h | 29 +
> .../ethernet/qualcomm/ipqess/ipqess_port.c | 757 +++++++++++
> .../ethernet/qualcomm/ipqess/ipqess_port.h | 62 +
> .../ethernet/qualcomm/ipqess/ipqess_switch.c | 546 ++++++++
> .../ethernet/qualcomm/ipqess/ipqess_switch.h | 36 +
> include/linux/dsa/qca8k.h | 47 +-
> 13 files changed, 3217 insertions(+), 14 deletions(-)
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/Makefile
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
>
<...>
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h
> new file mode 100644
> index 000000000000..172193d7126b
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h
> @@ -0,0 +1,488 @@
> +/* SPDX-License-Identifier: (GPL-2.0 OR ISC) */
> +/* Copyright (c) 2014 - 2016, The Linux Foundation. All rights reserved.
> + * Copyright (c) 2017 - 2018, John Crispin <john@phrozen.org>
> + * Copyright (c) 2018 - 2019, Christian Lamparter <chunkeey@gmail.com>
> + * Copyright (c) 2020 - 2021, Gabor Juhos <j4g8y7@gmail.com>
> + * Copyright (c) 2021 - 2022, Maxime Chevallier <maxime.chevallier@bootlin.com>
> + * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
> + *
> + */
> +
<...>
> +
> +/* TX descriptor fields */
> +#define IPQESS_EDMA_TPD_HDR_SHIFT 0
> +#define IPQESS_EDMA_TPD_PPPOE_EN 0x00000100
> +#define IPQESS_EDMA_TPD_IP_CSUM_EN 0x00000200
> +#define IPQESS_EDMA_TPD_TCP_CSUM_EN 0x0000400
> +#define IPQESS_EDMA_TPD_UDP_CSUM_EN 0x00000800
> +#define IPQESS_EDMA_TPD_CUSTOM_CSUM_EN 0x00000C00
> +#define IPQESS_EDMA_TPD_LSO_EN 0x00001000
> +#define IPQESS_EDMA_TPD_LSO_V2_EN 0x00002000
> +/* The VLAN_TAGGED bit is not used in the publicly available
> + * drivers. The definition has been stolen from the Atheros
> + * 'alx' driver (drivers/net/ethernet/atheros/alx/hw.h). It
> + * seems that it has the same meaning in regard to the EDMA
> + * hardware.
> + */
> +#define IPQESS_EDMA_TPD_VLAN_TAGGED 0x00004000
> +#define IPQESS_EDMA_TPD_IPV4_EN 0x00010000
> +#define IPQESS_EDMA_TPD_MSS_MASK 0x1FFF
> +#define IPQESS_EDMA_TPD_MSS_SHIFT 18
> +#define IPQESS_EDMA_TPD_CUSTOM_CSUM_SHIFT 18
> +
> +/* RRD descriptor fields */
> +#define IPQESS_EDMA_RRD_NUM_RFD_MASK 0x000F
> +#define IPQESS_EDMA_RRD_PKT_SIZE_MASK 0x3FFF
> +#define IPQESS_EDMA_RRD_SRC_PORT_NUM_MASK 0x4000
> +#define IPQESS_EDMA_RRD_SVLAN 0x8000
> +#define IPQESS_EDMA_RRD_FLOW_COOKIE_MASK 0x07FF
> +
> +#define IPQESS_EDMA_RRD_PKT_SIZE_MASK 0x3FFF
> +#define IPQESS_EDMA_RRD_CSUM_FAIL_MASK 0xC000
> +#define IPQESS_EDMA_RRD_CVLAN 0x0001
> +#define IPQESS_EDMA_RRD_DESC_VALID 0x8000
> +
> +#define IPQESS_EDMA_RRD_PRIORITY_SHIFT 4
> +#define IPQESS_EDMA_RRD_PRIORITY_MASK 0x7
> +#define IPQESS_EDMA_RRD_PORT_TYPE_SHIFT 7
> +#define IPQESS_EDMA_RRD_PORT_TYPE_MASK 0x1F
> +
> +#define IPQESS_EDMA_RRD_PORT_ID_MASK 0x7000
> +
> +#define IPQESS_EDMA_MAX_MTU 9000
I see that some of those defines are unused in this patch.
They should be introduced together with their references and if
they have no usecaes then those defines should be dropped.
> +
> +#endif
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h
> new file mode 100644
> index 000000000000..47f06a757cf7
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR ISC */
> +
> +#ifndef IPQESS_NOTIFIERS_H
> +#define IPQESS_NOTIFIERS_H
> +
> +#include <linux/if_ether.h>
> +
> +struct ipqess_switchdev_event_work {
> + struct net_device *netdev;
> + struct net_device *orig_netdev;
> + struct work_struct work;
> + unsigned long event;
> + /* Specific for SWITCHDEV_FDB_ADD_TO_DEVICE and
> + * SWITCHDEV_FDB_DEL_TO_DEVICE
> + */
> + unsigned char addr[ETH_ALEN];
> + u16 vid;
> + bool host_addr;
> +};
> +
> +extern struct notifier_block ipqess_switchdev_notifier;
> +extern struct notifier_block ipqess_switchdev_blocking_notifier;
> +
> +int ipqess_notifiers_register(void);
> +void ipqess_notifiers_unregister(void);
> +
> +void ipqess_flush_workqueue(void);
This is unused in this patch, I think this file should be moved to the 5th one.
> +
> +#endif
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> new file mode 100644
> index 000000000000..f0f5fe3a7c24
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> @@ -0,0 +1,757 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Handling of a single switch port
> + *
> + * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
> + * Based on net/dsa
> + */
> +
> +#include <linux/if_bridge.h>
> +#include <linux/etherdevice.h>
> +#include <linux/if_vlan.h>
> +#include <linux/of_net.h>
> +
> +#include "ipqess_port.h"
> +#include "ipqess_edma.h"
> +#include "ipqess_switch.h"
> +#include "ipqess_notifiers.h"
> +
> +#define ipqess_port_from_pl_state(config, pl_config)\
> +container_of(config, struct ipqess_port, pl_config)
> +
> +static struct device_type ipqess_port_type = {
> + .name = "switch",
> +};
> +
> +/* netdev ops */
> +
> +static void ipqess_port_fast_age(const struct ipqess_port *port)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + mutex_lock(&priv->reg_mutex);
> + qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port->index);
> + mutex_unlock(&priv->reg_mutex);
> +}
> +
> +static void ipqess_port_stp_state_set(struct ipqess_port *port,
> + u8 state)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> + u32 stp_state;
> + int err;
> +
> + switch (state) {
> + case BR_STATE_DISABLED:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
> + break;
> + case BR_STATE_BLOCKING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
> + break;
> + case BR_STATE_LISTENING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
> + break;
> + case BR_STATE_LEARNING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
> + break;
> + case BR_STATE_FORWARDING:
> + default:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
> + break;
> + }
> +
> + err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
> + QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
> +
> + if (err)
> + dev_warn(priv->dev,
> + "failed to set STP state %d for port %d: err %d\n",
> + stp_state, port->index, err);
> +}
> +
> +static void ipqess_port_set_state_now(struct ipqess_port *port,
> + u8 state, bool do_fast_age)
> +{
> + ipqess_port_stp_state_set(port, state);
> +
> + if ((port->stp_state == BR_STATE_LEARNING ||
> + port->stp_state == BR_STATE_FORWARDING) &&
> + (state == BR_STATE_DISABLED || state == BR_STATE_BLOCKING ||
> + state == BR_STATE_LISTENING))
> + ipqess_port_fast_age(port);
> +
> + port->stp_state = state;
> +}
> +
> +static int ipqess_port_enable_rt(struct ipqess_port *port,
> + struct phy_device *phy)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + qca8k_port_set_status(priv, port->index, 1);
> + priv->port_enabled_map |= BIT(port->index);
> +
> + phy_support_asym_pause(phy);
> +
> + ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
> +
> + if (port->pl)
> + phylink_start(port->pl);
> +
> + return 0;
> +}
> +
> +static void ipqess_port_disable_rt(struct ipqess_port *port)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + if (port->pl)
> + phylink_stop(port->pl);
> +
> + ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
> +
> + qca8k_port_set_status(priv, port->index, 0);
> + priv->port_enabled_map &= ~BIT(port->index);
> +}
> +
> +static int ipqess_port_open(struct net_device *netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct phy_device *phy = netdev->phydev;
> +
> + return ipqess_port_enable_rt(port, phy);
> +}
> +
> +static int ipqess_port_close(struct net_device *netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> +
> + ipqess_port_disable_rt(port);
> +
> + return 0;
> +}
> +
> +static netdev_tx_t ipqess_port_xmit(struct sk_buff *skb,
> + struct net_device *netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> +
> + dev_sw_netstats_tx_add(netdev, 1, skb->len);
> +
> + memset(skb->cb, 0, sizeof(skb->cb));
> +
> + return ipqess_edma_xmit(skb, port->netdev);
> +}
> +
> +static int ipqess_port_set_mac_address(struct net_device *netdev, void *a)
> +{
> + struct sockaddr *addr = a;
> + int err;> +
> + /* If the port is down, the address isn't synced yet to hardware
> + * so there is nothing to change
> + */
> + if (!(netdev->flags & IFF_UP)) {
> + eth_hw_addr_set(netdev, addr->sa_data);
> + return 0;
> + }
> +
> + if (!ether_addr_equal(addr->sa_data, netdev->dev_addr)) {
> + err = dev_uc_add(netdev, addr->sa_data);
> + if (err < 0)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ipqess_port_ioctl(struct net_device *netdev, struct ifreq *ifr,
> + int cmd)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> +
> + return phylink_mii_ioctl(port->pl, ifr, cmd);
> +}
> +
> +static int ipqess_port_get_iflink(const struct net_device *dev)
> +{
> + return dev->ifindex;
> +}
> +
> +static int ipqess_port_change_mtu(struct net_device *dev, int new_mtu)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + struct qca8k_priv *priv = port->sw->priv;
> + int err;
> +
> + /* To change the MAX_FRAME_SIZE, the cpu port must be off
> + * or the switch panics.
> + */
> + if (port->sw->port0_enabled)
> + qca8k_port_set_status(priv, 0, 0);
> +
> + err = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu +
> + ETH_HLEN + ETH_FCS_LEN);
> +
> + if (port->sw->port0_enabled)
> + qca8k_port_set_status(priv, 0, 1);
> +
> + if (err)
> + return err;
> +
> + dev->mtu = new_mtu;
> +
> + return 0;
> +}
> +
> +static int ipqess_port_do_vlan_add(struct qca8k_priv *priv, int port_index,
> + const struct switchdev_obj_port_vlan *vlan,
> + struct netlink_ext_ack *extack)
> +{
> + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
> + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
> + int ret;
> +
> + ret = qca8k_vlan_add(priv, port_index, vlan->vid, untagged);
> + if (ret) {
> + dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port_index,
> + ret);
> + return ret;
> + }
> +
> + if (pvid) {
> + ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
> + QCA8K_EGREES_VLAN_PORT_MASK(port_index),
> + QCA8K_EGREES_VLAN_PORT(port_index, vlan->vid));
> + if (ret)
> + return ret;
> +
> + ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
> + QCA8K_PORT_VLAN_CVID(vlan->vid) |
> + QCA8K_PORT_VLAN_SVID(vlan->vid));
> + }
> +
> + return ret;
> +}
> +
> +static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
> + u16 vid)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + struct switchdev_obj_port_vlan vlan = {
> + .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
> + .vid = vid,
> + /* This API only allows programming tagged, non-PVID VIDs */
> + .flags = 0,
> + };
> + struct netlink_ext_ack extack = {0};
> + int ret;
> +
> + /* User port... */
> + ret = ipqess_port_do_vlan_add(port->sw->priv, port->index, &vlan, &extack);
> + if (ret) {
> + if (extack._msg)
> + netdev_err(dev, "%s\n", extack._msg);
> + return ret;
> + }
> +
> + /* And CPU port... */
> + ret = ipqess_port_do_vlan_add(port->sw->priv, 0, &vlan, &extack);
> + if (ret) {
Should we delete vlan from user port if this fails?
> + if (extack._msg)
> + netdev_err(dev, "CPU port %d: %s\n", 0, extack._msg);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int ipqess_port_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
> + u16 vid)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + int err;
> +
> + err = qca8k_vlan_del(port->sw->priv, port->index, vid);
> + if (err)
> + return err;
> +
> + err = qca8k_vlan_del(port->sw->priv, 0, vid);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +static int
> +ipqess_port_fdb_do_dump(const unsigned char *addr, u16 vid,
> + bool is_static, void *data)
> +{
> + struct ipqess_port_dump_ctx *dump = data;
> + u32 portid = NETLINK_CB(dump->cb->skb).portid;
> + u32 seq = dump->cb->nlh->nlmsg_seq;
> + struct nlmsghdr *nlh;
> + struct ndmsg *ndm;
> +
> + if (dump->idx < dump->cb->args[2])
> + goto skip;
> +
> + nlh = nlmsg_put(dump->skb, portid, seq, RTM_NEWNEIGH,
> + sizeof(*ndm), NLM_F_MULTI);
> + if (!nlh)
> + return -EMSGSIZE;
> +
> + ndm = nlmsg_data(nlh);
> + ndm->ndm_family = AF_BRIDGE;
> + ndm->ndm_pad1 = 0;
> + ndm->ndm_pad2 = 0;
> + ndm->ndm_flags = NTF_SELF;
> + ndm->ndm_type = 0;
> + ndm->ndm_ifindex = dump->dev->ifindex;
> + ndm->ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE;
> +
> + if (nla_put(dump->skb, NDA_LLADDR, ETH_ALEN, addr))
> + goto nla_put_failure;
> +
> + if (vid && nla_put_u16(dump->skb, NDA_VLAN, vid))
> + goto nla_put_failure;
> +
> + nlmsg_end(dump->skb, nlh);
> +
> +skip:
> + dump->idx++;
> + return 0;
> +
> +nla_put_failure:
> + nlmsg_cancel(dump->skb, nlh);
> + return -EMSGSIZE;
> +}
> +
> +static int
> +ipqess_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
> + struct net_device *dev, struct net_device *filter_dev,
> + int *idx)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + struct qca8k_priv *priv = port->sw->priv;
> + struct ipqess_port_dump_ctx dump = {
> + .dev = dev,
> + .skb = skb,
> + .cb = cb,
> + .idx = *idx,
> + };
> + int cnt = QCA8K_NUM_FDB_RECORDS;
> + struct qca8k_fdb _fdb = { 0 };
> + bool is_static;
> + int ret = 0;
> +
> + mutex_lock(&priv->reg_mutex);
> + while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port->index)) {
> + if (!_fdb.aging)
> + break;
> + is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
> + ret = ipqess_port_fdb_do_dump(_fdb.mac, _fdb.vid, is_static, &dump);
> + if (ret)
> + break;
> + }
> + mutex_unlock(&priv->reg_mutex);
> +
> + *idx = dump.idx;
> +
> + return ret;
> +}
> +
> +static const struct net_device_ops ipqess_port_netdev_ops = {
> + .ndo_open = ipqess_port_open,
> + .ndo_stop = ipqess_port_close,
> + .ndo_set_mac_address = ipqess_port_set_mac_address,
> + .ndo_eth_ioctl = ipqess_port_ioctl,
> + .ndo_start_xmit = ipqess_port_xmit,
> + .ndo_get_iflink = ipqess_port_get_iflink,
> + .ndo_change_mtu = ipqess_port_change_mtu,
> + .ndo_vlan_rx_add_vid = ipqess_port_vlan_rx_add_vid,
> + .ndo_vlan_rx_kill_vid = ipqess_port_vlan_rx_kill_vid,
> + .ndo_fdb_dump = ipqess_port_fdb_dump,
> +};
> +
> +/* phylink ops */
> +
> +static void
> +ipqess_phylink_mac_config(struct phylink_config *config,
> + unsigned int mode,
> + const struct phylink_link_state *state)
> +{
> + struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + switch (port->index) {
> + case 0:
> + /* CPU port, no configuration needed */
> + return;
> + case 1:
> + case 2:
> + case 3:
> + case 4:
> + case 5:
> + if (phy_interface_mode_is_rgmii(state->interface))
> + regmap_set_bits(priv->regmap,
> + QCA8K_IPQ4019_REG_RGMII_CTRL,
> + QCA8K_IPQ4019_RGMII_CTRL_CLK);
> + return;
> + default:
> + dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
> + port->index);
> + return;
> + }
> +}
> +
> +static void
> +ipqess_phylink_mac_link_down(struct phylink_config *config,
> + unsigned int mode,
> + phy_interface_t interface)
> +{
> + struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + qca8k_port_set_status(priv, port->index, 0);
> +}
> +
> +static void ipqess_phylink_mac_link_up(struct phylink_config *config,
> + struct phy_device *phydev,
> + unsigned int mode,
> + phy_interface_t interface,
> + int speed, int duplex,
> + bool tx_pause, bool rx_pause)
> +{
> + struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> + struct qca8k_priv *priv = port->sw->priv;
> + u32 reg;
> +
> + if (phylink_autoneg_inband(mode)) {
> + reg = QCA8K_PORT_STATUS_LINK_AUTO;
> + } else {
> + switch (speed) {
> + case SPEED_10:
> + reg = QCA8K_PORT_STATUS_SPEED_10;
> + break;
> + case SPEED_100:
> + reg = QCA8K_PORT_STATUS_SPEED_100;
> + break;
> + case SPEED_1000:
> + reg = QCA8K_PORT_STATUS_SPEED_1000;
> + break;
> + default:
> + reg = QCA8K_PORT_STATUS_LINK_AUTO;
> + break;
> + }
> +
> + if (duplex == DUPLEX_FULL)
> + reg |= QCA8K_PORT_STATUS_DUPLEX;
> +
> + if (rx_pause || port->index == 0)
nit: this could be !port->index instead of port->index == 0
> + reg |= QCA8K_PORT_STATUS_RXFLOW;
> +
> + if (tx_pause || port->index == 0)
> + reg |= QCA8K_PORT_STATUS_TXFLOW;
> + }
> +
> + reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
> +
> + qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), reg);
> +}
> +
> +static const struct phylink_mac_ops ipqess_phylink_mac_ops = {
> + .mac_config = ipqess_phylink_mac_config,
> + .mac_link_down = ipqess_phylink_mac_link_down,
> + .mac_link_up = ipqess_phylink_mac_link_up,
> +};
> +
> +static int ipqess_phylink_create(struct net_device *netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct phylink_config *pl_config = &port->pl_config;
> + phy_interface_t mode;
> + struct phylink *pl;
> + int err;
> +
> + err = of_get_phy_mode(port->dn, &mode);
> + if (err)
> + mode = PHY_INTERFACE_MODE_NA;
> +
> + switch (port->index) {
> + case 1:
> + case 2:
> + case 3:
> + __set_bit(PHY_INTERFACE_MODE_PSGMII,
> + pl_config->supported_interfaces);
> + break;
> + case 4:
> + case 5:
> + phy_interface_set_rgmii(pl_config->supported_interfaces);
> + __set_bit(PHY_INTERFACE_MODE_PSGMII,
> + pl_config->supported_interfaces);
> + break;
> + case 0: /* CPU port, this shouldn't happen */
> + default:
> + return -EINVAL;
> + }
> + /* phylink caps */
> + pl_config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
> + MAC_10 | MAC_100 | MAC_1000FD;
> +
> + pl = phylink_create(pl_config, of_fwnode_handle(port->dn),
> + mode, &ipqess_phylink_mac_ops);
> + if (IS_ERR(pl))
> + return PTR_ERR(pl);
> +
> + port->pl = pl;
> + return 0;
> +}
> +
> +static int ipqess_port_phy_connect(struct net_device *netdev, int addr,
> + u32 flags)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> +
> + netdev->phydev = mdiobus_get_phy(port->mii_bus, addr);
> + if (!netdev->phydev) {
> + netdev_err(netdev, "no phy at %d\n", addr);
> + return -ENODEV;
> + }
> +
> + netdev->phydev->dev_flags |= flags;
> +
> + return phylink_connect_phy(port->pl, netdev->phydev);
> +}
> +
> +static int ipqess_port_phy_setup(struct net_device *netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct device_node *port_dn = port->dn;
> + u32 phy_flags = 0;
> + int ret;
> +
> + port->pl_config.dev = &netdev->dev;
> + port->pl_config.type = PHYLINK_NETDEV;
> +
> + ret = ipqess_phylink_create(netdev);
> + if (ret)
> + return ret;
> +
> + ret = phylink_of_phy_connect(port->pl, port_dn, phy_flags);
> + if (ret == -ENODEV && port->mii_bus) {
> + /* We could not connect to a designated PHY or SFP, so try to
> + * use the switch internal MDIO bus instead
> + */
> + ret = ipqess_port_phy_connect(netdev, port->index, phy_flags);
> + }
> +
> + if (ret) {
> + netdev_err(netdev, "failed to connect to PHY: %pe\n",
> + ERR_PTR(ret));
> + phylink_destroy(port->pl);
> + port->pl = NULL;
> + }
> +
> + dev_info(&netdev->dev, "enabled port's phy: %s",
> + phydev_name(netdev->phydev));
> + return ret;
> +}
> +
> +/* netlink */
> +
> +#define IFLA_IPQESS_UNSPEC 0
> +#define IFLA_IPQESS_MAX 0
> +
> +static const struct nla_policy ipqess_port_policy[IFLA_IPQESS_MAX + 1] = {
> + [IFLA_IPQESS_MAX] = { .type = NLA_U32 },
> +};
> +
> +static size_t ipqess_port_get_size(const struct net_device *dev)
> +{
> + return nla_total_size(sizeof(u32));
> +}
> +
> +static int ipqess_port_fill_info(struct sk_buff *skb,
> + const struct net_device *dev)
> +{
> + if (nla_put_u32(skb, IFLA_IPQESS_UNSPEC, dev->ifindex))
> + return -EMSGSIZE;
> +
> + return 0;
> +}
> +
> +static struct rtnl_link_ops ipqess_port_link_ops __read_mostly = {
> + .kind = "switch",
> + .priv_size = sizeof(struct ipqess_port),
> + .maxtype = 1,
> + .policy = ipqess_port_policy,
> + .get_size = ipqess_port_get_size,
> + .fill_info = ipqess_port_fill_info,
> + .netns_refund = true,
> +};
> +
> +/* devlink */
> +
> +static int ipqess_port_devlink_setup(struct ipqess_port *port)
> +{
> + struct devlink_port *dlp = &port->devlink_port;
> + struct devlink *dl = port->sw->devlink;
> + struct devlink_port_attrs attrs = {};
> + const unsigned char *id;
> + unsigned int index = 0;
> + unsigned char len;
> + int err;
> +
> + id = (const unsigned char *)&index;
> + len = sizeof(index);
> + memset(dlp, 0, sizeof(*dlp));
> +
> + attrs.phys.port_number = port->index;
> + memcpy(attrs.switch_id.id, id, len);
> + attrs.switch_id.id_len = len;
> + attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL;
> + devlink_port_attrs_set(dlp, &attrs);
> +
> + err = devlink_port_register(dl, dlp, port->index);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +/* register */
> +
> +int ipqess_port_register(struct ipqess_switch *sw,
> + struct device_node *port_node)
> +{
> + struct qca8k_priv *priv = sw->priv;
> + struct net_device *netdev;
> + struct ipqess_port *port;
> + const char *name;
> + int assign_type;
> + int num_queues;
> + u32 index;
> + int err;
> +
> + err = of_property_read_u32(port_node, "reg", &index);
> + if (err) {
> + pr_err("Node without reg property!");
> + return err;
> + }
> +
> + name = of_get_property(port_node, "label", NULL);
> + if (!name) {
> + name = "eth%d";
> + assign_type = NET_NAME_ENUM;
> + } else {
> + assign_type = NET_NAME_PREDICTABLE;
> + }
> +
> + /* For the NAPI leader, we allocate one queue per MAC queue */
> + if (!sw->napi_leader)
> + num_queues = IPQESS_EDMA_NETDEV_QUEUES;
> + else
> + num_queues = 1;
> +
> + netdev = alloc_netdev_mqs(sizeof(struct ipqess_port), name, assign_type,
> + ether_setup, num_queues, num_queues);
> + if (!netdev)
> + return -ENOMEM;
> +
> + if (!sw->napi_leader)
> + sw->napi_leader = netdev;
> +
> + port = netdev_priv(netdev);
> + port->index = (int)index;
> + port->dn = port_node;
> + port->netdev = netdev;
> + port->edma = NULL; /* Assigned during edma initialization */
> + port->qid = port->index - 1;
> + port->sw = sw;
> +
> + of_get_mac_address(port_node, port->mac);
> + if (!is_zero_ether_addr(port->mac))
> + eth_hw_addr_set(netdev, port->mac);
> + else
> + eth_hw_addr_random(netdev);
> +
> + netdev->netdev_ops = &ipqess_port_netdev_ops;
> + netdev->max_mtu = QCA8K_MAX_MTU;
> + SET_NETDEV_DEVTYPE(netdev, &ipqess_port_type);
> + SET_NETDEV_DEV(netdev, priv->dev);
> + SET_NETDEV_DEVLINK_PORT(netdev, &port->devlink_port);
> + netdev->dev.of_node = port->dn;
> +
> + netdev->rtnl_link_ops = &ipqess_port_link_ops;
> +
> + netdev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
> + if (!netdev->tstats) {
> + free_netdev(netdev);
> + return -ENOMEM;
> + }
> +
> + err = ipqess_port_devlink_setup(port);
> + if (err)
> + goto out_free;
> +
> + err = gro_cells_init(&port->gcells, netdev);
> + if (err)
> + goto out_devlink;
> +
> + err = ipqess_port_phy_setup(netdev);
> + if (err) {
> + pr_err("error setting up PHY: %d\n", err);
> + goto out_gcells;
> + }
> +
> + /* We use the qid and not the index because port 0 isn't registered */
> + sw->port_list[port->qid] = port;
> +
> + err = register_netdev(netdev);
> + if (err) {
> + pr_err("error %d registering interface %s\n",
> + err, netdev->name);
> + rtnl_unlock();
> + goto out_phy;
> + }
> +
> + return 0;
> +
> +out_phy:
> + rtnl_lock();
> + phylink_disconnect_phy(port->pl);
> + rtnl_unlock();
> + phylink_destroy(port->pl);
> + port->pl = NULL;
> +out_gcells:
> + gro_cells_destroy(&port->gcells);
> +out_devlink:
> + devlink_port_unregister(&port->devlink_port);
> +out_free:
> + free_percpu(netdev->tstats);
> + free_netdev(netdev);
> + sw->port_list[port->qid] = NULL;
> + return err;
> +}
> +
> +void ipqess_port_unregister(struct ipqess_port *port)
> +{
> + struct net_device *netdev = port->netdev;
> +
> + unregister_netdev(netdev);
> +
> + devlink_port_unregister(&port->devlink_port);
> +
> + rtnl_lock();
> + phylink_disconnect_phy(port->pl);
> + rtnl_unlock();
> + phylink_destroy(port->pl);
> + port->pl = NULL;
> +
> + gro_cells_destroy(&port->gcells);
> +
> + free_percpu(netdev->tstats);
> + free_netdev(netdev);
> +}
> +
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> new file mode 100644
> index 000000000000..26bac45dd811
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> @@ -0,0 +1,62 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR ISC */
> +
> +#ifndef IPQESS_PORT_H
> +#define IPQESS_PORT_H
> +
> +#include <net/gro_cells.h>
> +#include <net/devlink.h>
> +
> +#include "ipqess_edma.h"
> +#include "ipqess_switch.h"
> +
> +struct ipqess_port {
> + u16 index;
> + u16 qid;
> +
> + struct ipqess_edma *edma;
> + struct ipqess_switch *sw;
> + struct phylink *pl;
> + struct phylink_config pl_config;
> + struct device_node *dn;
> + struct mii_bus *mii_bus;
> + struct net_device *netdev;
> + struct devlink_port devlink_port;
> +
> + u8 stp_state;
> +
> + u8 mac[ETH_ALEN];
> +
> + /* Warning: the following bit field is not atomic, and updating it
> + * can only be done from code paths where concurrency is not possible
> + * (probe time or under rtnl_lock).
> + */
> + u8 vlan_filtering:1;
> +
> + unsigned int ageing_time;
> +
> + struct gro_cells gcells;
> +
> +#ifdef CONFIG_NET_POLL_CONTROLLER
> + struct netpoll *netpoll;
> +#endif
> +};
> +
> +struct ipqess_port_dump_ctx {
> + struct net_device *dev;
> + struct sk_buff *skb;
> + struct netlink_callback *cb;
> + int idx;
> +};
> +
> +struct ipqess_mac_addr {
> + unsigned char addr[ETH_ALEN];
> + u16 vid;
> + refcount_t refcount;
> + struct list_head list;
> +};
> +
> +int ipqess_port_register(struct ipqess_switch *sw,
> + struct device_node *port_node);
> +void ipqess_port_unregister(struct ipqess_port *port);
> +
> +#endif
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> new file mode 100644
> index 000000000000..927f834a62bc
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> @@ -0,0 +1,546 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
> + * Based on net/dsa
> + */
> +
> +#include <linux/dsa/qca8k.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_mdio.h>
> +#include <linux/reset.h>
> +
> +#include "ipqess_switch.h"
> +#include "ipqess_port.h"
> +#include "ipqess_edma.h"
> +
> +static struct regmap_config qca8k_ipqess_regmap_config = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> + .max_register = 0x16ac, /* end MIB - Port6 range */
> + .rd_table = &qca8k_readable_table,
> +};
> +
> +static struct regmap_config qca8k_ipqess_psgmii_phy_regmap_config = {
> + .name = "psgmii-phy",
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> + .max_register = 0x7fc,
> +};
> +
> +static const struct qca8k_match_data ipqess = {
> + .id = IPQESS_SWITCH_ID,
> + .mib_count = QCA8K_QCA833X_MIB_COUNT,
> +};
> +
> +/* devlink */
> +
> +static const struct devlink_ops ipqess_devlink_ops = {
> + /* no ops supported by this driver */
> +};
> +
> +static int ipqess_switch_devlink_alloc(struct ipqess_switch *sw)
> +{
> + struct ipqess_switch **dl_priv;
> + struct devlink *dl;
> +
> + /* Add the switch to devlink before calling setup, so that setup can
> + * add dpipe tables
> + */
> + dl = devlink_alloc(&ipqess_devlink_ops, sizeof(*dl_priv), sw->priv->dev);
> + if (!dl)
> + return -ENOMEM;
Sorry, this not what I meant. You can use devlink_alloc to allocate ipqess_switch.
Which means it has to be the first thing done in ipqess_switch_probe.
struct ipqess_switch *sw
devlink = devlink_alloc(&ice_devlink_ops, sizeof(struct ipqess_switch), dev);
if (!devlink)
return NULL;
sw = devlink_priv(devlink);
> +
> + sw->devlink = dl;
> +
> + dl_priv = devlink_priv(sw->devlink);
> + *dl_priv = sw;
> +
> + return 0;
> +}
> +
> +/* setup */
> +
> +unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
> + unsigned int ageing_time)
> +{
> + struct ipqess_port *port;
> + int i;
> +
> + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> + port = sw->port_list[i];
> + if (port && port->ageing_time && port->ageing_time < ageing_time)
> + ageing_time = port->ageing_time;
> + }
> +
> + return ageing_time;
> +}
> +
> +int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs)
> +{
> + struct qca8k_priv *priv = sw->priv;
> + unsigned int secs = msecs / 1000;
> + u32 val;
> +
> + /* AGE_TIME reg is set in 7s step */
> + val = secs / 7;
> +
> + /* Handle case with 0 as val to NOT disable
> + * learning
> + */
> + if (!val)
> + val = 1;
> +
> + return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL,
> + QCA8K_ATU_AGE_TIME_MASK,
> + QCA8K_ATU_AGE_TIME(val));
> +}
> +
> +static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
> +{
> + return container_of(pcs, struct qca8k_pcs, pcs);
> +}
> +
> +static void ipqess_switch_pcs_get_state(struct phylink_pcs *pcs,
> + struct phylink_link_state *state)
> +{
> + struct qca8k_priv *priv = pcs_to_qca8k_pcs(pcs)->priv;
> + int port = pcs_to_qca8k_pcs(pcs)->port;
> + u32 reg;
> + int ret;
> +
> + ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), ®);
> + if (ret < 0) {
Could be just
if (ret)
> + state->link = false;
> + return;
> + }
> +
> + state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
> + state->an_complete = state->link;
> + state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
> + DUPLEX_HALF;
> +
> + switch (reg & QCA8K_PORT_STATUS_SPEED) {
> + case QCA8K_PORT_STATUS_SPEED_10:
> + state->speed = SPEED_10;
> + break;
> + case QCA8K_PORT_STATUS_SPEED_100:
> + state->speed = SPEED_100;
> + break;
> + case QCA8K_PORT_STATUS_SPEED_1000:
> + state->speed = SPEED_1000;
> + break;
> + default:
> + state->speed = SPEED_UNKNOWN;
> + break;
> + }
> +
> + if (reg & QCA8K_PORT_STATUS_RXFLOW)
> + state->pause |= MLO_PAUSE_RX;
> + if (reg & QCA8K_PORT_STATUS_TXFLOW)
> + state->pause |= MLO_PAUSE_TX;
> +}
> +
> +static int ipqess_switch_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
> + phy_interface_t interface,
> + const unsigned long *advertising,
> + bool permit_pause_to_mac)
> +{
> + return 0;
> +}
> +
> +static void ipqess_switch_pcs_an_restart(struct phylink_pcs *pcs)
> +{
> +}
Empty functions, I'm guessing they're going to be filled in one of the next patches.
If yes, then I'd move them to this patch.
> +
> +static const struct phylink_pcs_ops qca8k_pcs_ops = {
> + .pcs_get_state = ipqess_switch_pcs_get_state,
> + .pcs_config = ipqess_switch_pcs_config,
> + .pcs_an_restart = ipqess_switch_pcs_an_restart,
> +};
> +
> +static void ipqess_switch_setup_pcs(struct qca8k_priv *priv,
> + struct qca8k_pcs *qpcs,
> + int port_index)
> +{
> + qpcs->pcs.ops = &qca8k_pcs_ops;
> +
> + /* We don't have interrupts for link changes, so we need to poll */
> + qpcs->pcs.poll = true;
> + qpcs->priv = priv;
> + qpcs->port = port_index;
> +}
> +
> +static int ipqess_switch_setup_port(struct ipqess_switch *sw, int port_index)
> +{
> + struct qca8k_priv *priv = sw->priv;
> + u32 mask = 0;
> + int ret, i;
> + u32 reg;
> +
> + /* CPU port gets connected to all registered ports of the switch */
> + if (port_index == IPQESS_SWITCH_CPU_PORT) {
> + for (i = 1; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> + if (sw->port_list[i - 1])
> + mask |= BIT(i);
> + }
> + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_index),
> + QCA8K_PORT_LOOKUP_MEMBER, mask);
> + if (ret)
> + return ret;
> + qca8k_read(priv, QCA8K_PORT_LOOKUP_CTRL(IPQESS_SWITCH_CPU_PORT), ®);
> +
> + /* Disable CPU ARP Auto-learning by default */
> + ret = regmap_clear_bits(priv->regmap,
> + QCA8K_PORT_LOOKUP_CTRL(port_index),
> + QCA8K_PORT_LOOKUP_LEARN);
> + if (ret)
> + return ret;
> + }
> +
> + /* Individual user ports get connected to CPU port only */
> + if (port_index > 0 && sw->port_list[port_index - 1]) {
> + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_index),
> + QCA8K_PORT_LOOKUP_MEMBER,
> + BIT(IPQESS_SWITCH_CPU_PORT));
> + if (ret)
> + return ret;
> +
> + /* Enable ARP Auto-learning by default */
> + ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port_index),
> + QCA8K_PORT_LOOKUP_LEARN);
> + if (ret)
> + return ret;
> +
> + /* For port based vlans to work we need to set the
> + * default egress vid
> + */
> + ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
> + QCA8K_EGREES_VLAN_PORT_MASK(port_index),
> + QCA8K_EGREES_VLAN_PORT(port_index, QCA8K_PORT_VID_DEF));
> + if (ret)
> + return ret;
> +
> + ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
> + QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
> + QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +int ipqess_switch_setup(struct ipqess_switch *sw)
> +{
> + struct qca8k_priv *priv = sw->priv;
> + int ret, i;
> + u32 reg;
> +
> + ipqess_switch_setup_pcs(priv, &priv->pcs_port_0, 0);
> +
> + /* Enable CPU Port */
> + ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
> + QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
> + if (ret) {
> + dev_err(priv->dev, "failed enabling CPU port");
> + return ret;
> + }
> +
> + /* Enable MIB counters */
> + ret = qca8k_mib_init(priv);
> + if (ret)
> + dev_warn(priv->dev, "MIB init failed");
> +
> + /* Disable forwarding by default on all ports */
> + for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
> + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
> + QCA8K_PORT_LOOKUP_MEMBER, 0);
> + if (ret)
> + return ret;
> + }
> +
> + /* Enable QCA header mode on the CPU port */
> + ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(IPQESS_SWITCH_CPU_PORT),
> + FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
> + FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
> + if (ret) {
> + dev_err(priv->dev, "failed enabling QCA header mode");
> + return ret;
> + }
> +
> + /* Disable MAC by default on all ports */
> + for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
> + if (i > 0)
Hmmm, we could just start from 1 here.
> + qca8k_port_set_status(priv, i, 0);
> + }
> +
> + /* Forward all unknown frames to all ports */
> + ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
> + FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, 0x3f) |
> + FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, 0x3f) |
> + FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, 0x3f) |
> + FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, 0x3f));
> + if (ret) {
> + pr_err("Error while disabling MAC and forwarding unknown frames %d\n",
> + ret);
> + return ret;
> + }
> +
> + /* Setup connection between CPU port & user ports */
> + for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
This happens a lot, ipqess_for_each_port would be nice.
> + ret = ipqess_switch_setup_port(sw, i);
> + if (ret)
> + return ret;
> + }
> +
> + /* Setup our port MTUs to match power on defaults */
> + ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
> + if (ret)
> + dev_warn(priv->dev, "failed setting MTU settings");
> +
> + /* Flush the FDB table */
> + qca8k_fdb_flush(priv);
> +
> + if (ret < 0)
> + goto devlink_free;
> +
> + /* set Port0 status */
> + reg = QCA8K_PORT_STATUS_LINK_AUTO;
> + reg |= QCA8K_PORT_STATUS_DUPLEX;
> + reg |= QCA8K_PORT_STATUS_SPEED_1000;
> + reg |= QCA8K_PORT_STATUS_RXFLOW;
> + reg |= QCA8K_PORT_STATUS_TXFLOW;
> + reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
> + qca8k_write(priv, QCA8K_REG_PORT_STATUS(0), reg);
> + sw->port0_enabled = true;
> +
> + return 0;
> +
> +devlink_free:
Why is it called devlink_free, I don't see any connection to devlink.
> + pr_err("qca_switch_setup error: %d\n", ret);
> + return ret;
> +}
> +EXPORT_SYMBOL(ipqess_switch_setup);
> +
> +/* probe */
> +
> +static void ipqess_switch_psgmii_rst(struct ipqess_switch *sw)
> +{
> + reset_control_assert(sw->psgmii_rst);
> +
> + mdelay(10);
> +
> + reset_control_deassert(sw->psgmii_rst);
> +
> + mdelay(10);
> +}
> +
> +static int ipqess_switch_probe(struct platform_device *pdev)
> +{
> + struct device_node *ports, *port_np;
> + struct device_node *np, *mdio_np;
> + struct device *dev = &pdev->dev;
> + struct ipqess_port *port = NULL;
> + void __iomem *base, *psgmii;
> + struct ipqess_switch *sw;
> + struct qca8k_priv *priv;
> + int ret;
> + int i;
> +
> + sw = devm_kzalloc(dev, sizeof(struct ipqess_switch), GFP_KERNEL);
> + if (!sw)
> + return -ENOMEM;
> +
> + priv = devm_kzalloc(dev, sizeof(struct qca8k_priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + sw->priv = priv;
> + sw->port0_enabled = false;
> + priv->dev = dev;
> + priv->info = &ipqess;
> + priv->ds = NULL;
> +
> + np = dev->of_node;
> + ports = of_get_child_by_name(np, "ports");
> + if (!ports) {
> + dev_err(dev, "no 'ports' attribute found\n");
> + return -EINVAL;
> + }
> +
> + /* Start by setting up the register mapping */
> + base = devm_platform_ioremap_resource_byname(pdev, "base");
> + if (IS_ERR(base)) {
> + dev_err(dev, "platform ioremap fail %li\n", PTR_ERR(base));
> + return PTR_ERR(base);
> + }
> +
> + priv->regmap = devm_regmap_init_mmio(dev, base,
> + &qca8k_ipqess_regmap_config);
> + if (IS_ERR(priv->regmap)) {
> + ret = PTR_ERR(priv->regmap);
> + dev_err(dev, "base regmap initialization failed, %d\n", ret);
> + return ret;
> + }
> +
> + psgmii = devm_platform_ioremap_resource_byname(pdev, "psgmii_phy");
> + if (IS_ERR(psgmii)) {
> + ret = PTR_ERR(psgmii);
> + dev_err(dev, "platform ioremap psgmii fail %d\n", ret);
> + return ret;
> + }
> +
> + priv->psgmii = devm_regmap_init_mmio(dev, psgmii,
> + &qca8k_ipqess_psgmii_phy_regmap_config);
> + if (IS_ERR(priv->psgmii)) {
> + ret = PTR_ERR(priv->psgmii);
> + dev_err(dev, "PSGMII regmap initialization failed, %d\n", ret);
> + return ret;
> + }
> +
> + mdio_np = of_parse_phandle(np, "mdio", 0);
> + if (!mdio_np) {
> + dev_err(dev, "unable to get MDIO bus phandle\n");
> + of_node_put(mdio_np);
> + return -EINVAL;
> + }
> +
> + priv->bus = of_mdio_find_bus(mdio_np);
> + of_node_put(mdio_np);
> + if (!priv->bus) {
> + dev_err(dev, "unable to find MDIO bus\n");
> + return -EPROBE_DEFER;
> + }
> +
> + /* If we don't reset the PSGMII here the switch id check will fail */
> + sw->psgmii_rst = devm_reset_control_get(&pdev->dev, "psgmii");
> + if (IS_ERR(sw->psgmii_rst)) {
> + ret = PTR_ERR(sw->psgmii_rst);
> + dev_err(dev, "Unable to get PSGMII reset line: err %d\n", ret);
> + return ret;
> + }
> +
> + ipqess_switch_psgmii_rst(sw);
> +
> + /* Check the detected switch id */
> + ret = qca8k_read_switch_id(sw->priv);
> + if (ret) {
> + dev_err(dev, "Failed to read switch id! error %d\n", ret);
> + return ret;
> + }
> +
> + priv->ds = NULL;
> +
> + mutex_init(&sw->addr_lists_lock);
> + INIT_LIST_HEAD(&sw->fdbs);
> + INIT_LIST_HEAD(&sw->mdbs);
> +
> + mutex_init(&priv->reg_mutex);
> + platform_set_drvdata(pdev, sw);
> +
> + ret = ipqess_switch_devlink_alloc(sw);
> + if (ret)
> + goto out_devlink;
> +
> + devlink_register(sw->devlink);
> +
> + /* Register switch front-facing ports */
> + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++)
> + sw->port_list[i] = NULL;
> +
> + for_each_available_child_of_node(ports, port_np) {
> + ret = ipqess_port_register(sw, port_np);
> + if (ret) {
> + pr_err("Failed to register ipqess port! error %d\n", ret);
> + goto out_ports;
> + }
> + }
> + if (!sw->napi_leader) {
> + pr_err("No switch port registered as napi leader!\n");
> + ret = -EINVAL;
> + goto out_ports;
> + }
> +
> + ret = ipqess_edma_init(pdev, np);
> + if (ret) {
> + dev_err(dev, "Failed to initialize EDMA controller! error %d\n", ret);
> + goto out_ports;
> + }
> +
> + ipqess_switch_setup(sw);
> +
> + return 0;
> +
> +out_ports:
> + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> + port = sw->port_list[i];
> + if (port)
> + ipqess_port_unregister(port);
You can add something like ipqess_destroy_ports,
could be used both here and in ipqess_switch_remove
> + }
> +out_devlink:
> + devlink_free(sw->devlink);
> + pr_err("%s failed with error %d\n", __func__, ret);
> + return ret;
> +}
> +
> +static int
> +ipqess_switch_remove(struct platform_device *pdev)
> +{
> + struct ipqess_switch *sw = platform_get_drvdata(pdev);
> + struct qca8k_priv *priv = sw->priv;
> + struct ipqess_port *port = NULL;
> + int i;
> +
> + if (!sw)
> + return 0;
> +
> + /* Release EDMA driver */
> + ipqess_edma_uninit(sw->edma);
> +
> + /* Disable all user ports */
> + for (i = 1; i < QCA8K_NUM_PORTS; i++) {
> + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
> + QCA8K_PORT_LOOKUP_STATE_MASK,
> + QCA8K_PORT_LOOKUP_STATE_DISABLED);
> + qca8k_port_set_status(priv, i, 0);
> + priv->port_enabled_map &= ~BIT(i);
> + }
> +
> + /* Unregister user ports */
> + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> + port = sw->port_list[i];
> + if (port)
> + ipqess_port_unregister(port);
> + }
> +
> + devlink_unregister(sw->devlink);
> + devlink_free(sw->devlink);
> +
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id qca8k_ipqess_of_match[] = {
> + { .compatible = "qcom,ipq4019-ess", },
> + { /* sentinel */ },
> +};
> +
> +static struct platform_driver qca8k_ipqess_driver = {
> + .probe = ipqess_switch_probe,
> + .remove = ipqess_switch_remove,
> + .driver = {
> + .name = "ipqess",
> + .of_match_table = qca8k_ipqess_of_match,
> + },
> +};
> +
> +module_platform_driver(qca8k_ipqess_driver);
> +
> +MODULE_AUTHOR("Romain Gantois <romain.gantois@bootlin.org>");
> +MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
> +MODULE_AUTHOR("Gabor Juhos <j4g8y7@gmail.com>, Robert Marko <robert.marko@sartura.hr>");
> +MODULE_DESCRIPTION("Qualcomm IPQ4019 Ethernet Switch Subsystem driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
> new file mode 100644
> index 000000000000..221c628d1728
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR ISC */
> +
> +#ifndef IPQESS_SWITCH_H
> +#define IPQESS_SWITCH_H
> +
> +#include <linux/dsa/qca8k.h>
> +
> +#define IPQESS_SWITCH_MAX_PORTS 5
> +#define IPQESS_SWITCH_AGEING_TIME_MIN 7000
> +#define IPQESS_SWITCH_AGEING_TIME_MAX 458745000
> +#define IPQESS_SWITCH_CPU_PORT 0
> +#define IPQESS_SWITCH_NUM_PORTS 5
> +#define IPQESS_SWITCH_ID 0x14
> +
> +struct ipqess_switch {
> + struct net_device *napi_leader;
> + struct qca8k_priv *priv;
> + struct ipqess_edma *edma;
> + struct ipqess_port *port_list[IPQESS_SWITCH_MAX_PORTS];
> + struct devlink *devlink;
> + struct reset_control *psgmii_rst;
> + bool port0_enabled;
> +
> + /* List of MAC addresses that must be forwarded on the cpu port */
> + struct mutex addr_lists_lock;
> + struct list_head fdbs;
> + struct list_head mdbs;
> +};
> +
> +unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
> + unsigned int ageing_time);
> +int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs);
> +
> +int ipqess_switch_setup(struct ipqess_switch *sw);
> +
> +#endif
> diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
> index 3c75c3704fa0..cafb727f4e8b 100644
> --- a/include/linux/dsa/qca8k.h
> +++ b/include/linux/dsa/qca8k.h
> @@ -266,6 +266,7 @@
> #define QCA8K_PORT_LOOKUP_STATE_LEARNING QCA8K_PORT_LOOKUP_STATE(0x3)
> #define QCA8K_PORT_LOOKUP_STATE_FORWARD QCA8K_PORT_LOOKUP_STATE(0x4)
> #define QCA8K_PORT_LOOKUP_LEARN BIT(20)
> +#define QCA8K_PORT_LOOKUP_LOOPBACK_EN BIT(21)
> #define QCA8K_PORT_LOOKUP_ING_MIRROR_EN BIT(25)
>
> #define QCA8K_REG_GOL_TRUNK_CTRL0 0x700
> @@ -342,6 +343,31 @@
> #define MII_ATH_MMD_ADDR 0x0d
> #define MII_ATH_MMD_DATA 0x0e
>
> +/* IPQ4019 PSGMII PHY registers */
> +#define QCA8K_IPQ4019_REG_RGMII_CTRL 0x004
> +#define QCA8K_IPQ4019_RGMII_CTRL_RGMII_RXC GENMASK(1, 0)
> +#define QCA8K_IPQ4019_RGMII_CTRL_RGMII_TXC GENMASK(9, 8)
> +/* Some kind of CLK selection
> + * 0: gcc_ess_dly2ns
> + * 1: gcc_ess_clk
> + */
> +#define QCA8K_IPQ4019_RGMII_CTRL_CLK BIT(10)
> +#define QCA8K_IPQ4019_RGMII_CTRL_DELAY_RMII0 GENMASK(17, 16)
> +#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII0_REF_CLK BIT(18)
> +#define QCA8K_IPQ4019_RGMII_CTRL_DELAY_RMII1 GENMASK(20, 19)
> +#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII1_REF_CLK BIT(21)
> +#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII0_MASTER_EN BIT(24)
> +#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII1_MASTER_EN BIT(25)
> +
> +#define PSGMIIPHY_MODE_CONTROL 0x1b4
> +#define PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M BIT(0)
> +#define PSGMIIPHY_TX_CONTROL 0x288
> +#define PSGMIIPHY_TX_CONTROL_MAGIC_VALUE 0x8380
> +#define PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_1 0x9c
> +#define PSGMIIPHY_REG_PLL_VCO_CALIB_RESTART BIT(14)
> +#define PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_2 0xa0
> +#define PSGMIIPHY_REG_PLL_VCO_CALIB_READY BIT(0)
> +
> enum {
> QCA8K_PORT_SPEED_10M = 0,
> QCA8K_PORT_SPEED_100M = 1,
> @@ -467,6 +493,9 @@ struct qca8k_priv {
> struct qca8k_pcs pcs_port_6;
> const struct qca8k_match_data *info;
> struct qca8k_led ports_led[QCA8K_LED_COUNT];
> +
> + /* IPQ4019 specific */
> + struct regmap *psgmii;
> };
>
> struct qca8k_mib_desc {
> @@ -507,6 +536,8 @@ int qca8k_read_switch_id(struct qca8k_priv *priv);
> int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val);
> int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val);
> int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
> +int qca8k_set_bits(struct qca8k_priv *priv, u32 reg, u32 bits);
> +int qca8k_clear_bits(struct qca8k_priv *priv, u32 reg, u32 bits);
>
> /* Common ops function */
> void qca8k_fdb_flush(struct qca8k_priv *priv);
> @@ -560,6 +591,16 @@ int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
> struct dsa_db db);
> int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
> dsa_fdb_dump_cb_t *cb, void *data);
> +int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
> + u16 port_mask, u16 vid);
> +int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb,
> + int port);
> +int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
> + int port);
> +int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
> + const u8 *mac, u16 vid, u8 aging);
> +int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
> + const u8 *mac, u16 vid);
>
> /* Common MDB function */
> int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
> @@ -577,8 +618,12 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
> struct dsa_mall_mirror_tc_entry *mirror);
>
> /* Common port VLAN function */
> -int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
> +int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
> + bool vlan_filtering,
> struct netlink_ext_ack *extack);
> +int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
> + bool untagged);
> +int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid);
> int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
> const struct switchdev_obj_port_vlan *vlan,
> struct netlink_ext_ack *extack);
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-15 12:55 ` Wojciech Drewek
@ 2023-11-15 15:07 ` Romain Gantois
2023-11-16 14:39 ` Vladimir Oltean
1 sibling, 0 replies; 37+ messages in thread
From: Romain Gantois @ 2023-11-15 15:07 UTC (permalink / raw)
To: Wojciech Drewek
Cc: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski,
Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
devicetree, thomas.petazzoni, Andrew Lunn, Florian Fainelli,
Heiner Kallweit, Russell King, linux-arm-kernel, Vladimir Oltean,
Luka Perkov, Robert Marko, Andy Gross, Bjorn Andersson,
Konrad Dybcio
On Wed, 15 Nov 2023, Wojciech Drewek wrote:
...
> > +static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
> > + u16 vid)
> > +{
> > + struct ipqess_port *port = netdev_priv(dev);
> > + struct switchdev_obj_port_vlan vlan = {
> > + .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
> > + .vid = vid,
> > + /* This API only allows programming tagged, non-PVID VIDs */
> > + .flags = 0,
> > + };
> > + struct netlink_ext_ack extack = {0};
> > + int ret;
> > +
> > + /* User port... */
> > + ret = ipqess_port_do_vlan_add(port->sw->priv, port->index, &vlan, &extack);
> > + if (ret) {
> > + if (extack._msg)
> > + netdev_err(dev, "%s\n", extack._msg);
> > + return ret;
> > + }
> > +
> > + /* And CPU port... */
> > + ret = ipqess_port_do_vlan_add(port->sw->priv, 0, &vlan, &extack);
> > + if (ret) {
>
> Should we delete vlan from user port if this fails?
I'll have to look into how and when this API is called in more detail but I
think this would indeed make sense.
> > +
> > + /* Flush the FDB table */
> > + qca8k_fdb_flush(priv);
> > +
> > + if (ret < 0)
> > + goto devlink_free;
> > +
> > + /* set Port0 status */
> > + reg = QCA8K_PORT_STATUS_LINK_AUTO;
> > + reg |= QCA8K_PORT_STATUS_DUPLEX;
> > + reg |= QCA8K_PORT_STATUS_SPEED_1000;
> > + reg |= QCA8K_PORT_STATUS_RXFLOW;
> > + reg |= QCA8K_PORT_STATUS_TXFLOW;
> > + reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
> > + qca8k_write(priv, QCA8K_REG_PORT_STATUS(0), reg);
> > + sw->port0_enabled = true;
> > +
> > + return 0;
> > +
> > +devlink_free:
>
> Why is it called devlink_free, I don't see any connection to devlink.
I think this is leftover from a previous version of this function, where it
interacted with devlink. I'll rename it to error.
Best,
--
Romain Gantois, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-15 12:55 ` Wojciech Drewek
2023-11-15 15:07 ` Romain Gantois
@ 2023-11-16 14:39 ` Vladimir Oltean
1 sibling, 0 replies; 37+ messages in thread
From: Vladimir Oltean @ 2023-11-16 14:39 UTC (permalink / raw)
To: Wojciech Drewek
Cc: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski,
Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
devicetree, thomas.petazzoni, Andrew Lunn, Florian Fainelli,
Heiner Kallweit, Russell King, linux-arm-kernel, Luka Perkov,
Robert Marko, Andy Gross, Bjorn Andersson, Konrad Dybcio
On Wed, Nov 15, 2023 at 01:55:07PM +0100, Wojciech Drewek wrote:
> Hi Romain,
> I see that you splitted the patch, thanks for that.
> However it's still too big IMHO, edma ipqess_edma could be introduced in seperate file e.g.
I agree that it's way too much code in one go (also too much code in
general). It doesn't help that much of it is blindly copied from DSA or
duplicated from the qca8k driver from the without much thought. It looks
like the EDMA portion could be separated from the switch portion.
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Romain Gantois
2023-11-14 19:10 ` Andrew Lunn
2023-11-15 12:55 ` Wojciech Drewek
@ 2023-11-15 18:11 ` Simon Horman
2023-11-16 16:08 ` kernel test robot
` (2 subsequent siblings)
5 siblings, 0 replies; 37+ messages in thread
From: Simon Horman @ 2023-11-15 18:11 UTC (permalink / raw)
To: Romain Gantois
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Andrew Lunn, Florian Fainelli, Heiner Kallweit,
Russell King, linux-arm-kernel, Vladimir Oltean, Luka Perkov,
Robert Marko, Andy Gross, Bjorn Andersson, Konrad Dybcio
On Tue, Nov 14, 2023 at 11:55:53AM +0100, Romain Gantois wrote:
> The Qualcomm IPQ4019 Ethernet Switch Subsystem for the IPQ4019 chip
> includes an internal Ethernet switch based on the QCA8K IP.
>
> The CPU-to-switch port data plane depends on the IPQESS EDMA Controller,
> a simple 1G Ethernet controller. It is connected to the switch through an
> internal link, and doesn't expose directly any external interface.
>
> The EDMA controller has 16 RX and TX queues, with a very basic RSS fanout
> configured at init time.
>
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
Hi Romain,
some minor feedback from my side.
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
...
> +static int ipqess_switch_probe(struct platform_device *pdev)
> +{
...
> + for_each_available_child_of_node(ports, port_np) {
> + ret = ipqess_port_register(sw, port_np);
> + if (ret) {
> + pr_err("Failed to register ipqess port! error %d\n", ret);
Coccinelle warns that a call to of_node_put() is needed here.
> + goto out_ports;
> + }
> + }
...
> +static int
> +ipqess_switch_remove(struct platform_device *pdev)
> +{
> + struct ipqess_switch *sw = platform_get_drvdata(pdev);
> + struct qca8k_priv *priv = sw->priv;
Here sw is dereferenced...
> + struct ipqess_port *port = NULL;
> + int i;
> +
> + if (!sw)
> + return 0;
... but here the code guards against sw being NULL.
This seems inconsistent.
As flagged by Smatch.
...
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Romain Gantois
` (2 preceding siblings ...)
2023-11-15 18:11 ` Simon Horman
@ 2023-11-16 16:08 ` kernel test robot
2023-11-16 21:56 ` Vladimir Oltean
2023-11-17 0:28 ` kernel test robot
5 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-11-16 16:08 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all,
Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-4-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
config: i386-kismet-CONFIG_NET_DSA-CONFIG_QCOM_IPQ4019_ESS-0-0 (https://download.01.org/0day-ci/archive/20231116/202311162336.2BYtVL3Q-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20231116/202311162336.2BYtVL3Q-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311162336.2BYtVL3Q-lkp@intel.com/
kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for NET_DSA when selected by QCOM_IPQ4019_ESS
WARNING: unmet direct dependencies detected for NET_DSA
Depends on [n]: NET [=y] && (BRIDGE [=n] || BRIDGE [=n]=n) && (HSR [=n] || HSR [=n]=n) && INET [=n] && NETDEVICES [=y]
Selected by [y]:
- QCOM_IPQ4019_ESS [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_QUALCOMM [=y] && (OF [=n] && ARCH_QCOM || COMPILE_TEST [=y])
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Romain Gantois
` (3 preceding siblings ...)
2023-11-16 16:08 ` kernel test robot
@ 2023-11-16 21:56 ` Vladimir Oltean
2023-11-17 0:28 ` kernel test robot
5 siblings, 0 replies; 37+ messages in thread
From: Vladimir Oltean @ 2023-11-16 21:56 UTC (permalink / raw)
To: Romain Gantois, Andrew Lunn, Florian Fainelli
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Heiner Kallweit, Russell King, linux-arm-kernel,
Luka Perkov, Robert Marko, Andy Gross, Bjorn Andersson,
Konrad Dybcio
On Tue, Nov 14, 2023 at 11:55:53AM +0100, Romain Gantois wrote:
> diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
> index 56663770382e..a66a821ce4d6 100644
> --- a/drivers/net/dsa/qca/qca8k-common.c
> +++ b/drivers/net/dsa/qca/qca8k-common.c
> @@ -79,6 +79,19 @@ int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
> {
> return regmap_update_bits(priv->regmap, reg, mask, write_val);
> }
> +EXPORT_SYMBOL_GPL(qca8k_rmw);
> +
> +int qca8k_set_bits(struct qca8k_priv *priv, u32 reg, u32 bits)
> +{
> + return regmap_set_bits(priv->regmap, reg, bits);
> +}
> +EXPORT_SYMBOL_GPL(qca8k_set_bits);
> +
> +int qca8k_clear_bits(struct qca8k_priv *priv, u32 reg, u32 bits)
> +{
> + return regmap_clear_bits(priv->regmap, reg, bits);
> +}
> +EXPORT_SYMBOL_GPL(qca8k_clear_bits);
No motivation given for these. Also, no one uses them in this change.
> diff --git a/drivers/net/ethernet/qualcomm/Kconfig b/drivers/net/ethernet/qualcomm/Kconfig
> index 9210ff360fdc..008d20ec9eae 100644
> --- a/drivers/net/ethernet/qualcomm/Kconfig
> +++ b/drivers/net/ethernet/qualcomm/Kconfig
> @@ -61,6 +61,20 @@ config QCOM_EMAC
> low power, Receive-Side Scaling (RSS), and IEEE 1588-2008
> Precision Clock Synchronization Protocol.
>
> +config QCOM_IPQ4019_ESS
> + tristate "Qualcomm Atheros IPQ4019 Ethernet Switch Subsystem support"
> + depends on (OF && ARCH_QCOM) || COMPILE_TEST
> + select PHYLINK
> + select NET_DSA
NET_DSA has dependencies, you cannot select something with dependencies
unless you also depend on all of NET_DSA's dependencies too (which you don't).
You shouldn't depend on the NET_DSA framework as far as I can tell,
since you use nothing from it. You just need NET_DSA_QCA8K_LIB, which in
my mind should have been a DSA-unaware hardware library, with its own
data structures and functions shareable by both driver front-ends. To
get to that, drivers/net/Makefile currently has obj-$(CONFIG_NET_DSA) +=
dsa/, but maybe it should have had obj-y += dsa/, to make
NET_DSA_QCA8K_LIB available without NET_DSA.
> + select NET_DSA_QCA8K_LIB
> + select PAGE_POOL
> + help
> + This driver supports the Qualcomm Atheros IPQ40xx built-in
> + Ethernet Switch Subsystem.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called ipqess.
> +
> source "drivers/net/ethernet/qualcomm/rmnet/Kconfig"
>
> endif # NET_VENDOR_QUALCOMM
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> new file mode 100644
> index 000000000000..f0f5fe3a7c24
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> @@ -0,0 +1,757 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Handling of a single switch port
> + *
> + * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
> + * Based on net/dsa
> + */
> +
> +#include <linux/if_bridge.h>
> +#include <linux/etherdevice.h>
> +#include <linux/if_vlan.h>
> +#include <linux/of_net.h>
> +
> +#include "ipqess_port.h"
> +#include "ipqess_edma.h"
> +#include "ipqess_switch.h"
> +#include "ipqess_notifiers.h"
> +
> +#define ipqess_port_from_pl_state(config, pl_config)\
> +container_of(config, struct ipqess_port, pl_config)
> +
> +static struct device_type ipqess_port_type = {
> + .name = "switch",
> +};
(...)
> + SET_NETDEV_DEVTYPE(netdev, &ipqess_port_type);
Why do you do this?
> +static void ipqess_port_stp_state_set(struct ipqess_port *port,
> + u8 state)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> + u32 stp_state;
> + int err;
> +
> + switch (state) {
> + case BR_STATE_DISABLED:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
> + break;
> + case BR_STATE_BLOCKING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
> + break;
> + case BR_STATE_LISTENING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
> + break;
> + case BR_STATE_LEARNING:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
> + break;
> + case BR_STATE_FORWARDING:
> + default:
> + stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
> + break;
> + }
> +
> + err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
> + QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
> +
> + if (err)
> + dev_warn(priv->dev,
> + "failed to set STP state %d for port %d: err %d\n",
> + stp_state, port->index, err);
> +}
Does this have to reimplement qca8k_port_stp_state_set()?
> +static int ipqess_port_enable_rt(struct ipqess_port *port,
> + struct phy_device *phy)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + qca8k_port_set_status(priv, port->index, 1);
> + priv->port_enabled_map |= BIT(port->index);
> +
> + phy_support_asym_pause(phy);
Question mark here, phylink_bringup_phy() should do this to. I see it
comes from qca8k_port_enable(), so I don't think we need this there
either.
> +
> + ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
> +
> + if (port->pl)
> + phylink_start(port->pl);
> +
> + return 0;
> +}
> +static netdev_tx_t ipqess_port_xmit(struct sk_buff *skb,
> + struct net_device *netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> +
> + dev_sw_netstats_tx_add(netdev, 1, skb->len);
> +
> + memset(skb->cb, 0, sizeof(skb->cb));
What use do you make of skb->cb?
> +
> + return ipqess_edma_xmit(skb, port->netdev);
> +}
> +
> +static int ipqess_port_set_mac_address(struct net_device *netdev, void *a)
> +{
> + struct sockaddr *addr = a;
> + int err;
> +
> + /* If the port is down, the address isn't synced yet to hardware
> + * so there is nothing to change
> + */
> + if (!(netdev->flags & IFF_UP)) {
> + eth_hw_addr_set(netdev, addr->sa_data);
> + return 0;
In DSA, the MAC address is programmed to hardware in dsa_user_open().
Please help me see where the same thing happens in the ipqess switch
driver, as the copied comment suggests.
> + }
> +
> + if (!ether_addr_equal(addr->sa_data, netdev->dev_addr)) {
> + err = dev_uc_add(netdev, addr->sa_data);
> + if (err < 0)
> + return err;
> + }
I guess this should have programmed something to hardware. It looks like
an inept adaptation of dsa_user_set_mac_address(). The major difference
is that with DSA, the dev_uc_add() is done on the conduit interface.
Here it's done on itself.
> +
> + return 0;
> +}
> +
> +static int ipqess_port_get_iflink(const struct net_device *dev)
> +{
> + return dev->ifindex;
> +}
What do you need an iflink for?
> +static int ipqess_port_do_vlan_add(struct qca8k_priv *priv, int port_index,
> + const struct switchdev_obj_port_vlan *vlan,
> + struct netlink_ext_ack *extack)
> +{
> + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
> + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
> + int ret;
> +
> + ret = qca8k_vlan_add(priv, port_index, vlan->vid, untagged);
> + if (ret) {
> + dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port_index,
> + ret);
> + return ret;
> + }
> +
> + if (pvid) {
> + ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
> + QCA8K_EGREES_VLAN_PORT_MASK(port_index),
A preliminary patch renaming EGREES to EGRESS would be nice.
> + QCA8K_EGREES_VLAN_PORT(port_index, vlan->vid));
> + if (ret)
> + return ret;
> +
> + ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
> + QCA8K_PORT_VLAN_CVID(vlan->vid) |
> + QCA8K_PORT_VLAN_SVID(vlan->vid));
> + }
> +
> + return ret;
> +}
I'm not clear why this reimplements qca8k_port_vlan_add().
> +/* phylink ops */
> +
> +static void
> +ipqess_phylink_mac_config(struct phylink_config *config,
> + unsigned int mode,
> + const struct phylink_link_state *state)
> +{
> + struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + switch (port->index) {
> + case 0:
> + /* CPU port, no configuration needed */
> + return;
> + case 1:
> + case 2:
> + case 3:
> + case 4:
> + case 5:
> + if (phy_interface_mode_is_rgmii(state->interface))
> + regmap_set_bits(priv->regmap,
> + QCA8K_IPQ4019_REG_RGMII_CTRL,
> + QCA8K_IPQ4019_RGMII_CTRL_CLK);
> + return;
> + default:
> + dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
> + port->index);
> + return;
> + }
> +}
> +
> +static void
> +ipqess_phylink_mac_link_down(struct phylink_config *config,
> + unsigned int mode,
> + phy_interface_t interface)
> +{
> + struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + qca8k_port_set_status(priv, port->index, 0);
> +}
> +
> +static void ipqess_phylink_mac_link_up(struct phylink_config *config,
> + struct phy_device *phydev,
> + unsigned int mode,
> + phy_interface_t interface,
> + int speed, int duplex,
> + bool tx_pause, bool rx_pause)
> +{
> + struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> + struct qca8k_priv *priv = port->sw->priv;
> + u32 reg;
> +
> + if (phylink_autoneg_inband(mode)) {
> + reg = QCA8K_PORT_STATUS_LINK_AUTO;
> + } else {
> + switch (speed) {
> + case SPEED_10:
> + reg = QCA8K_PORT_STATUS_SPEED_10;
> + break;
> + case SPEED_100:
> + reg = QCA8K_PORT_STATUS_SPEED_100;
> + break;
> + case SPEED_1000:
> + reg = QCA8K_PORT_STATUS_SPEED_1000;
> + break;
> + default:
> + reg = QCA8K_PORT_STATUS_LINK_AUTO;
> + break;
> + }
> +
> + if (duplex == DUPLEX_FULL)
> + reg |= QCA8K_PORT_STATUS_DUPLEX;
> +
> + if (rx_pause || port->index == 0)
> + reg |= QCA8K_PORT_STATUS_RXFLOW;
> +
> + if (tx_pause || port->index == 0)
> + reg |= QCA8K_PORT_STATUS_TXFLOW;
> + }
> +
> + reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
> +
> + qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), reg);
> +}
> +
> +static const struct phylink_mac_ops ipqess_phylink_mac_ops = {
> + .mac_config = ipqess_phylink_mac_config,
> + .mac_link_down = ipqess_phylink_mac_link_down,
> + .mac_link_up = ipqess_phylink_mac_link_up,
> +};
phylink mac ops are a reimplementation of the qca8k ops. So are the pcs
ops, for that matter. For the pcs ops, there is the option of moving the
code to drivers/net/pcs/ for easier sharing.
> +static int ipqess_phylink_create(struct net_device *netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct phylink_config *pl_config = &port->pl_config;
> + phy_interface_t mode;
> + struct phylink *pl;
> + int err;
> +
> + err = of_get_phy_mode(port->dn, &mode);
> + if (err)
> + mode = PHY_INTERFACE_MODE_NA;
> +
> + switch (port->index) {
> + case 1:
> + case 2:
> + case 3:
> + __set_bit(PHY_INTERFACE_MODE_PSGMII,
> + pl_config->supported_interfaces);
> + break;
> + case 4:
> + case 5:
> + phy_interface_set_rgmii(pl_config->supported_interfaces);
> + __set_bit(PHY_INTERFACE_MODE_PSGMII,
> + pl_config->supported_interfaces);
> + break;
> + case 0: /* CPU port, this shouldn't happen */
> + default:
> + return -EINVAL;
> + }
> + /* phylink caps */
> + pl_config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
> + MAC_10 | MAC_100 | MAC_1000FD;
> +
> + pl = phylink_create(pl_config, of_fwnode_handle(port->dn),
> + mode, &ipqess_phylink_mac_ops);
> + if (IS_ERR(pl))
> + return PTR_ERR(pl);
> +
> + port->pl = pl;
> + return 0;
> +}
> +
> +static int ipqess_port_phy_connect(struct net_device *netdev, int addr,
> + u32 flags)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> +
> + netdev->phydev = mdiobus_get_phy(port->mii_bus, addr);
> + if (!netdev->phydev) {
> + netdev_err(netdev, "no phy at %d\n", addr);
> + return -ENODEV;
> + }
> +
> + netdev->phydev->dev_flags |= flags;
> +
> + return phylink_connect_phy(port->pl, netdev->phydev);
> +}
> +
> +static int ipqess_port_phy_setup(struct net_device *netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct device_node *port_dn = port->dn;
> + u32 phy_flags = 0;
> + int ret;
> +
> + port->pl_config.dev = &netdev->dev;
> + port->pl_config.type = PHYLINK_NETDEV;
> +
> + ret = ipqess_phylink_create(netdev);
> + if (ret)
> + return ret;
> +
> + ret = phylink_of_phy_connect(port->pl, port_dn, phy_flags);
> + if (ret == -ENODEV && port->mii_bus) {
> + /* We could not connect to a designated PHY or SFP, so try to
> + * use the switch internal MDIO bus instead
> + */
> + ret = ipqess_port_phy_connect(netdev, port->index, phy_flags);
> + }
Do you need a non-OF based phy_connect()? Will you not always have a
phy-handle or a fixed-link?
> +
> + if (ret) {
> + netdev_err(netdev, "failed to connect to PHY: %pe\n",
> + ERR_PTR(ret));
> + phylink_destroy(port->pl);
> + port->pl = NULL;
> + }
> +
> + dev_info(&netdev->dev, "enabled port's phy: %s",
> + phydev_name(netdev->phydev));
> + return ret;
> +}
> +
> +/* netlink */
> +
> +#define IFLA_IPQESS_UNSPEC 0
> +#define IFLA_IPQESS_MAX 0
> +
> +static const struct nla_policy ipqess_port_policy[IFLA_IPQESS_MAX + 1] = {
> + [IFLA_IPQESS_MAX] = { .type = NLA_U32 },
> +};
> +
> +static size_t ipqess_port_get_size(const struct net_device *dev)
> +{
> + return nla_total_size(sizeof(u32));
> +}
> +
> +static int ipqess_port_fill_info(struct sk_buff *skb,
> + const struct net_device *dev)
> +{
> + if (nla_put_u32(skb, IFLA_IPQESS_UNSPEC, dev->ifindex))
> + return -EMSGSIZE;
> +
> + return 0;
> +}
> +
> +static struct rtnl_link_ops ipqess_port_link_ops __read_mostly = {
> + .kind = "switch",
> + .priv_size = sizeof(struct ipqess_port),
> + .maxtype = 1,
> + .policy = ipqess_port_policy,
> + .get_size = ipqess_port_get_size,
> + .fill_info = ipqess_port_fill_info,
> + .netns_refund = true,
> +};
What the hell do you use netlink for?
> +/* devlink */
> +
> +static int ipqess_port_devlink_setup(struct ipqess_port *port)
> +{
> + struct devlink_port *dlp = &port->devlink_port;
> + struct devlink *dl = port->sw->devlink;
> + struct devlink_port_attrs attrs = {};
> + const unsigned char *id;
> + unsigned int index = 0;
> + unsigned char len;
> + int err;
> +
> + id = (const unsigned char *)&index;
> + len = sizeof(index);
> + memset(dlp, 0, sizeof(*dlp));
> +
> + attrs.phys.port_number = port->index;
> + memcpy(attrs.switch_id.id, id, len);
> + attrs.switch_id.id_len = len;
> + attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL;
> + devlink_port_attrs_set(dlp, &attrs);
> +
> + err = devlink_port_register(dl, dlp, port->index);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +/* register */
> +
> +int ipqess_port_register(struct ipqess_switch *sw,
> + struct device_node *port_node)
> +{
> + struct qca8k_priv *priv = sw->priv;
> + struct net_device *netdev;
> + struct ipqess_port *port;
> + const char *name;
> + int assign_type;
> + int num_queues;
> + u32 index;
> + int err;
> +
> + err = of_property_read_u32(port_node, "reg", &index);
> + if (err) {
> + pr_err("Node without reg property!");
> + return err;
> + }
> +
> + name = of_get_property(port_node, "label", NULL);
The "label" device tree property is deprecated in favor of using udev
for predictable netdev names.
> + if (!name) {
> + name = "eth%d";
> + assign_type = NET_NAME_ENUM;
> + } else {
> + assign_type = NET_NAME_PREDICTABLE;
> + }
> +
> + /* For the NAPI leader, we allocate one queue per MAC queue */
> + if (!sw->napi_leader)
> + num_queues = IPQESS_EDMA_NETDEV_QUEUES;
> + else
> + num_queues = 1;
> +
> + netdev = alloc_netdev_mqs(sizeof(struct ipqess_port), name, assign_type,
> + ether_setup, num_queues, num_queues);
> + if (!netdev)
> + return -ENOMEM;
> +
> + if (!sw->napi_leader)
> + sw->napi_leader = netdev;
> +
> + port = netdev_priv(netdev);
> + port->index = (int)index;
> + port->dn = port_node;
> + port->netdev = netdev;
> + port->edma = NULL; /* Assigned during edma initialization */
> + port->qid = port->index - 1;
> + port->sw = sw;
> +
> + of_get_mac_address(port_node, port->mac);
> + if (!is_zero_ether_addr(port->mac))
> + eth_hw_addr_set(netdev, port->mac);
> + else
> + eth_hw_addr_random(netdev);
> +
> + netdev->netdev_ops = &ipqess_port_netdev_ops;
> + netdev->max_mtu = QCA8K_MAX_MTU;
> + SET_NETDEV_DEVTYPE(netdev, &ipqess_port_type);
> + SET_NETDEV_DEV(netdev, priv->dev);
> + SET_NETDEV_DEVLINK_PORT(netdev, &port->devlink_port);
> + netdev->dev.of_node = port->dn;
> +
> + netdev->rtnl_link_ops = &ipqess_port_link_ops;
> +
> + netdev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
> + if (!netdev->tstats) {
> + free_netdev(netdev);
> + return -ENOMEM;
> + }
> +
> + err = ipqess_port_devlink_setup(port);
> + if (err)
> + goto out_free;
> +
> + err = gro_cells_init(&port->gcells, netdev);
> + if (err)
> + goto out_devlink;
If the switch doesn't insert DSA tags, you don't need GRO cells.
Normal GRO should work just fine on the receive side of the DMA engine.
> +
> + err = ipqess_port_phy_setup(netdev);
> + if (err) {
> + pr_err("error setting up PHY: %d\n", err);
> + goto out_gcells;
> + }
> +
> + /* We use the qid and not the index because port 0 isn't registered */
> + sw->port_list[port->qid] = port;
port_list is an array? :)
> +
> + err = register_netdev(netdev);
> + if (err) {
> + pr_err("error %d registering interface %s\n",
> + err, netdev->name);
> + rtnl_unlock();
Where did you rtnl_lock()?
> + goto out_phy;
> + }
> +
> + return 0;
> +
> +out_phy:
> + rtnl_lock();
> + phylink_disconnect_phy(port->pl);
> + rtnl_unlock();
> + phylink_destroy(port->pl);
> + port->pl = NULL;
> +out_gcells:
> + gro_cells_destroy(&port->gcells);
> +out_devlink:
> + devlink_port_unregister(&port->devlink_port);
> +out_free:
> + free_percpu(netdev->tstats);
> + free_netdev(netdev);
> + sw->port_list[port->qid] = NULL;
> + return err;
> +}
> +
> +void ipqess_port_unregister(struct ipqess_port *port)
> +{
> + struct net_device *netdev = port->netdev;
> +
> + unregister_netdev(netdev);
> +
> + devlink_port_unregister(&port->devlink_port);
> +
> + rtnl_lock();
> + phylink_disconnect_phy(port->pl);
> + rtnl_unlock();
> + phylink_destroy(port->pl);
> + port->pl = NULL;
> +
> + gro_cells_destroy(&port->gcells);
> +
> + free_percpu(netdev->tstats);
> + free_netdev(netdev);
> +}
> +
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> new file mode 100644
> index 000000000000..26bac45dd811
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> @@ -0,0 +1,62 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR ISC */
> +
> +#ifndef IPQESS_PORT_H
> +#define IPQESS_PORT_H
> +
> +#include <net/gro_cells.h>
> +#include <net/devlink.h>
> +
> +#include "ipqess_edma.h"
> +#include "ipqess_switch.h"
> +
> +struct ipqess_port {
> + u16 index;
> + u16 qid;
> +
> + struct ipqess_edma *edma;
> + struct ipqess_switch *sw;
> + struct phylink *pl;
> + struct phylink_config pl_config;
> + struct device_node *dn;
> + struct mii_bus *mii_bus;
> + struct net_device *netdev;
> + struct devlink_port devlink_port;
> +
> + u8 stp_state;
> +
> + u8 mac[ETH_ALEN];
Inconsistent mix of single space, many spaces and tabs for alignment.
> +
> + /* Warning: the following bit field is not atomic, and updating it
> + * can only be done from code paths where concurrency is not possible
> + * (probe time or under rtnl_lock).
> + */
> + u8 vlan_filtering:1;
In dsa_switch, where you copied this comment from, there are actually
multiple bitfields which share the same u32. Here, there's a single one.
So the comment actually does not apply.
Not to mention that this can be simply "bool".
> +
> + unsigned int ageing_time;
> +
> + struct gro_cells gcells;
> +
> +#ifdef CONFIG_NET_POLL_CONTROLLER
> + struct netpoll *netpoll;
> +#endif
Do you actually use netpoll?
> +};
I think that commenting on the rest is slightly pointless.
I'm 99% sure that I know the answer to the questions I formulated thus
far: you didn't do the most basic step of assessing what you need from
DSA, and only taking it as a loose implementation example of a switchdev
driver.
What we see here is a snapshot of DSA from October 2023 which was copied
and pasted with some renaming.
Snap out of the mentality where you can copy DSA and call it a day, and
think about it as a new driver - your driver. Present it feature by
feature, and even more importantly, test it feature by feature. After it
is merged, you'll have to maintain it. The DSA bug fixes won't magically
fix the ipqess driver copy as well. Every line that you add is a line
that you're responsible for. I'm not sure how someone who was this
negligent copying code word for word can be less negligent maintaining
it.
I'm very serious about this, and I'm sorry that Wojciech spent so much
energy reviewing essentially a clumsy duplication of DSA. Let's stop and
think how we can proceed forward. I'm not saying that the "two frontends,
one common hardware library" idea cannot be successfully implemented
(after all, ocelot/felix is just that), but maybe I understated the fact
that it is a complex task that should be taken seriously. As things
stand, the variant with the "in-band DSA tags" was more palatable than
this.
@Florian, in Documentation/networking/dsa/dsa.rst we say this:
| TODO
| ====
|
| Making SWITCHDEV and DSA converge towards an unified codebase
| -------------------------------------------------------------
|
| SWITCHDEV properly takes care of abstracting the networking stack with offload
| capable hardware, but does not enforce a strict switch device driver model. On
| the other DSA enforces a fairly strict device driver model, and deals with most
| of the switch specific. At some point we should envision a merger between these
| two subsystems and get the best of both worlds.
Now we have Bootlin's ipqess project as a clear use case for this switch
handling "library code" that we could extract out of DSA. So it is a
good moment to talk about whether there is any value to this
long-standing TODO. I'm not saying yes or no, but at least, if it can't
help here, I would like to put the idea to rest and delete the TODO.
If we do see a path forward for that TODO through this discussion, the
way I see it is that one of the DSA maintainers will have to sit down a
while and prototype something. It can also be me who spends that time,
but I don't personally have an idea how to get started - how to extract
that code to a library without it getting extremely clunky and limiting
the flexibility of other drivers that may use it.
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Romain Gantois
` (4 preceding siblings ...)
2023-11-16 21:56 ` Vladimir Oltean
@ 2023-11-17 0:28 ` kernel test robot
5 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-11-17 0:28 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all,
Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-4-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
config: i386-kismet-CONFIG_NET_DSA-CONFIG_QCOM_IPQ4019_ESS-0-0 (https://download.01.org/0day-ci/archive/20231117/202311170830.ltsTVtIh-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20231117/202311170830.ltsTVtIh-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311170830.ltsTVtIh-lkp@intel.com/
kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for NET_DSA when selected by QCOM_IPQ4019_ESS
WARNING: unmet direct dependencies detected for NET_DSA
Depends on [n]: NET [=y] && (BRIDGE [=n] || BRIDGE [=n]=n) && (HSR [=n] || HSR [=n]=n) && INET [=n] && NETDEVICES [=y]
Selected by [y]:
- QCOM_IPQ4019_ESS [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_QUALCOMM [=y] && (OF [=n] && ARCH_QCOM || COMPILE_TEST [=y])
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
` (2 preceding siblings ...)
2023-11-14 10:55 ` [PATCH net-next v3 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Romain Gantois
@ 2023-11-14 10:55 ` Romain Gantois
2023-11-15 13:07 ` Wojciech Drewek
` (2 more replies)
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
` (4 subsequent siblings)
8 siblings, 3 replies; 37+ messages in thread
From: Romain Gantois @ 2023-11-14 10:55 UTC (permalink / raw)
To: davem, Rob Herring, Krzysztof Kozlowski
Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
The IPQESS driver registers one netdevice for each front-facing switch
port. Add support for several ethtool operations to these netdevices.
Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
drivers/net/ethernet/qualcomm/ipqess/Makefile | 2 +-
.../ethernet/qualcomm/ipqess/ipqess_ethtool.c | 245 ++++++++++++++++++
.../ethernet/qualcomm/ipqess/ipqess_port.c | 1 +
.../ethernet/qualcomm/ipqess/ipqess_port.h | 3 +
4 files changed, 250 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
index f389080cc5aa..6253f1b0ffd2 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/Makefile
+++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
@@ -5,4 +5,4 @@
obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
-ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o
+ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
new file mode 100644
index 000000000000..06a8f2cccc4e
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ethtool operations for a single switch port
+ *
+ * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
+ * Based on net/dsa
+ */
+
+#include <net/selftests.h>
+
+#include "ipqess_port.h"
+
+static void ipqess_port_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *drvinfo)
+{
+ strscpy(drvinfo->driver, "qca8k-ipqess", sizeof(drvinfo->driver));
+ strscpy(drvinfo->bus_info, "platform", sizeof(drvinfo->bus_info));
+}
+
+static int ipqess_port_nway_reset(struct net_device *dev)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+
+ return phylink_ethtool_nway_reset(port->pl);
+}
+
+static const char ipqess_gstrings_base_stats[][ETH_GSTRING_LEN] = {
+ "tx_packets",
+ "tx_bytes",
+ "rx_packets",
+ "rx_bytes",
+};
+
+static void ipqess_port_get_strings(struct net_device *dev,
+ u32 stringset, u8 *data)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ struct qca8k_priv *priv = port->sw->priv;
+ int i;
+
+ if (stringset == ETH_SS_STATS) {
+ memcpy(data, &ipqess_gstrings_base_stats,
+ sizeof(ipqess_gstrings_base_stats));
+
+ if (stringset != ETH_SS_STATS)
+ return;
+
+ for (i = 0; i < priv->info->mib_count; i++)
+ memcpy(data + (4 + i) * ETH_GSTRING_LEN,
+ ar8327_mib[i].name,
+ ETH_GSTRING_LEN);
+
+ } else if (stringset == ETH_SS_TEST) {
+ net_selftest_get_strings(data);
+ }
+}
+
+static void ipqess_port_get_ethtool_stats(struct net_device *dev,
+ struct ethtool_stats *stats,
+ uint64_t *data)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ struct qca8k_priv *priv = port->sw->priv;
+ const struct qca8k_mib_desc *mib;
+ struct pcpu_sw_netstats *s;
+ unsigned int start;
+ u32 reg, c, val;
+ u32 hi = 0;
+ int ret;
+ int i;
+
+ for_each_possible_cpu(i) {
+ u64 tx_packets, tx_bytes, rx_packets, rx_bytes;
+
+ s = per_cpu_ptr(dev->tstats, i);
+ do {
+ start = u64_stats_fetch_begin(&s->syncp);
+ tx_packets = u64_stats_read(&s->tx_packets);
+ tx_bytes = u64_stats_read(&s->tx_bytes);
+ rx_packets = u64_stats_read(&s->rx_packets);
+ rx_bytes = u64_stats_read(&s->rx_bytes);
+ } while (u64_stats_fetch_retry(&s->syncp, start));
+ data[0] += tx_packets;
+ data[1] += tx_bytes;
+ data[2] += rx_packets;
+ data[3] += rx_bytes;
+ }
+
+ for (c = 0; c < priv->info->mib_count; c++) {
+ mib = &ar8327_mib[c];
+ reg = QCA8K_PORT_MIB_COUNTER(port->index) + mib->offset;
+
+ ret = qca8k_read(priv, reg, &val);
+ if (ret < 0)
+ continue;
+
+ if (mib->size == 2) {
+ ret = qca8k_read(priv, reg + 4, &hi);
+ if (ret < 0)
+ continue;
+ }
+
+ data[4 + c] = val;
+ if (mib->size == 2)
+ data[4 + c] |= (u64)hi << 32;
+ }
+}
+
+static int ipqess_port_get_sset_count(struct net_device *dev, int sset)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ struct qca8k_priv *priv = port->sw->priv;
+
+ if (sset == ETH_SS_STATS) {
+ int count = 0;
+
+ if (sset != ETH_SS_STATS)
+ count = 0;
+ else
+ count = priv->info->mib_count;
+
+ if (count < 0)
+ return count;
+
+ return count + 4;
+ } else if (sset == ETH_SS_TEST) {
+ return net_selftest_get_count();
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int ipqess_port_set_wol(struct net_device *dev,
+ struct ethtool_wolinfo *w)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+
+ return phylink_ethtool_set_wol(port->pl, w);
+}
+
+static void ipqess_port_get_wol(struct net_device *dev,
+ struct ethtool_wolinfo *w)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+
+ phylink_ethtool_get_wol(port->pl, w);
+}
+
+static int ipqess_port_set_eee(struct net_device *dev, struct ethtool_eee *eee)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ int ret;
+ u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port->index);
+ struct qca8k_priv *priv = port->sw->priv;
+ u32 lpi_ctl1;
+
+ /* Port's PHY and MAC both need to be EEE capable */
+ if (!dev->phydev || !port->pl)
+ return -ENODEV;
+
+ mutex_lock(&priv->reg_mutex);
+ lpi_ctl1 = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &lpi_ctl1);
+ if (lpi_ctl1 < 0) {
+ mutex_unlock(&priv->reg_mutex);
+ return ret;
+ }
+
+ if (eee->tx_lpi_enabled && eee->eee_enabled)
+ lpi_ctl1 |= lpi_en;
+ else
+ lpi_ctl1 &= ~lpi_en;
+ ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, lpi_ctl1);
+ mutex_unlock(&priv->reg_mutex);
+
+ if (ret)
+ return ret;
+
+ return phylink_ethtool_set_eee(port->pl, eee);
+}
+
+static int ipqess_port_get_eee(struct net_device *dev, struct ethtool_eee *e)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+
+ /* Port's PHY and MAC both need to be EEE capable */
+ if (!dev->phydev || !port->pl)
+ return -ENODEV;
+
+ return phylink_ethtool_get_eee(port->pl, e);
+}
+
+static int ipqess_port_get_link_ksettings(struct net_device *dev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+
+ return phylink_ethtool_ksettings_get(port->pl, cmd);
+}
+
+static int ipqess_port_set_link_ksettings(struct net_device *dev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+
+ return phylink_ethtool_ksettings_set(port->pl, cmd);
+}
+
+static void ipqess_port_get_pauseparam(struct net_device *dev,
+ struct ethtool_pauseparam *pause)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+
+ phylink_ethtool_get_pauseparam(port->pl, pause);
+}
+
+static int ipqess_port_set_pauseparam(struct net_device *dev,
+ struct ethtool_pauseparam *pause)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+
+ return phylink_ethtool_set_pauseparam(port->pl, pause);
+}
+
+static const struct ethtool_ops ipqess_port_ethtool_ops = {
+ .get_drvinfo = ipqess_port_get_drvinfo,
+ .nway_reset = ipqess_port_nway_reset,
+ .get_link = ethtool_op_get_link,
+ .get_strings = ipqess_port_get_strings,
+ .get_ethtool_stats = ipqess_port_get_ethtool_stats,
+ .get_sset_count = ipqess_port_get_sset_count,
+ .self_test = net_selftest,
+ .set_wol = ipqess_port_set_wol,
+ .get_wol = ipqess_port_get_wol,
+ .set_eee = ipqess_port_set_eee,
+ .get_eee = ipqess_port_get_eee,
+ .get_link_ksettings = ipqess_port_get_link_ksettings,
+ .set_link_ksettings = ipqess_port_set_link_ksettings,
+ .get_pauseparam = ipqess_port_get_pauseparam,
+ .set_pauseparam = ipqess_port_set_pauseparam,
+};
+
+void ipqess_port_set_ethtool_ops(struct net_device *netdev)
+{
+ netdev->ethtool_ops = &ipqess_port_ethtool_ops;
+}
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
index f0f5fe3a7c24..52d7baa7cae0 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
@@ -684,6 +684,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
netdev->dev.of_node = port->dn;
netdev->rtnl_link_ops = &ipqess_port_link_ops;
+ ipqess_port_set_ethtool_ops(netdev);
netdev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
if (!netdev->tstats) {
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
index 26bac45dd811..19d4b5d73625 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
@@ -59,4 +59,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
struct device_node *port_node);
void ipqess_port_unregister(struct ipqess_port *port);
+/* Defined in ipqess_ethtool.c */
+void ipqess_port_set_ethtool_ops(struct net_device *netdev);
+
#endif
--
2.42.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices
2023-11-14 10:55 ` [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Romain Gantois
@ 2023-11-15 13:07 ` Wojciech Drewek
2023-11-15 18:18 ` Simon Horman
2023-11-21 12:18 ` kernel test robot
2 siblings, 0 replies; 37+ messages in thread
From: Wojciech Drewek @ 2023-11-15 13:07 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
devicetree, thomas.petazzoni, Andrew Lunn, Florian Fainelli,
Heiner Kallweit, Russell King, linux-arm-kernel, Vladimir Oltean,
Luka Perkov, Robert Marko, Andy Gross, Bjorn Andersson,
Konrad Dybcio
On 14.11.2023 11:55, Romain Gantois wrote:
> The IPQESS driver registers one netdevice for each front-facing switch
> port. Add support for several ethtool operations to these netdevices.
>
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
> ---
> drivers/net/ethernet/qualcomm/ipqess/Makefile | 2 +-
> .../ethernet/qualcomm/ipqess/ipqess_ethtool.c | 245 ++++++++++++++++++
> .../ethernet/qualcomm/ipqess/ipqess_port.c | 1 +
> .../ethernet/qualcomm/ipqess/ipqess_port.h | 3 +
> 4 files changed, 250 insertions(+), 1 deletion(-)
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
>
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
> index f389080cc5aa..6253f1b0ffd2 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/Makefile
> +++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
> @@ -5,4 +5,4 @@
>
> obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
>
> -ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o
> +ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
> new file mode 100644
> index 000000000000..06a8f2cccc4e
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
> @@ -0,0 +1,245 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Ethtool operations for a single switch port
> + *
> + * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
> + * Based on net/dsa
> + */
> +
> +#include <net/selftests.h>
> +
> +#include "ipqess_port.h"
> +
> +static void ipqess_port_get_drvinfo(struct net_device *dev,
> + struct ethtool_drvinfo *drvinfo)
> +{
> + strscpy(drvinfo->driver, "qca8k-ipqess", sizeof(drvinfo->driver));
> + strscpy(drvinfo->bus_info, "platform", sizeof(drvinfo->bus_info));
> +}
> +
> +static int ipqess_port_nway_reset(struct net_device *dev)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> +
> + return phylink_ethtool_nway_reset(port->pl);
> +}
> +
> +static const char ipqess_gstrings_base_stats[][ETH_GSTRING_LEN] = {
> + "tx_packets",
> + "tx_bytes",
> + "rx_packets",
> + "rx_bytes",
> +};
> +
> +static void ipqess_port_get_strings(struct net_device *dev,
> + u32 stringset, u8 *data)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + struct qca8k_priv *priv = port->sw->priv;
> + int i;
> +
> + if (stringset == ETH_SS_STATS) {
> + memcpy(data, &ipqess_gstrings_base_stats,
> + sizeof(ipqess_gstrings_base_stats));
> +
> + if (stringset != ETH_SS_STATS)
> + return;
> +
> + for (i = 0; i < priv->info->mib_count; i++)
> + memcpy(data + (4 + i) * ETH_GSTRING_LEN,
> + ar8327_mib[i].name,
> + ETH_GSTRING_LEN);
> +
> + } else if (stringset == ETH_SS_TEST) {
> + net_selftest_get_strings(data);
> + }
> +}
> +
> +static void ipqess_port_get_ethtool_stats(struct net_device *dev,
> + struct ethtool_stats *stats,
> + uint64_t *data)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + struct qca8k_priv *priv = port->sw->priv;
> + const struct qca8k_mib_desc *mib;
> + struct pcpu_sw_netstats *s;
> + unsigned int start;
> + u32 reg, c, val;
> + u32 hi = 0;
> + int ret;
> + int i;
> +
> + for_each_possible_cpu(i) {
> + u64 tx_packets, tx_bytes, rx_packets, rx_bytes;
> +
> + s = per_cpu_ptr(dev->tstats, i);
> + do {
> + start = u64_stats_fetch_begin(&s->syncp);
> + tx_packets = u64_stats_read(&s->tx_packets);
> + tx_bytes = u64_stats_read(&s->tx_bytes);
> + rx_packets = u64_stats_read(&s->rx_packets);
> + rx_bytes = u64_stats_read(&s->rx_bytes);
> + } while (u64_stats_fetch_retry(&s->syncp, start));
> + data[0] += tx_packets;
> + data[1] += tx_bytes;
> + data[2] += rx_packets;
> + data[3] += rx_bytes;
> + }
> +
> + for (c = 0; c < priv->info->mib_count; c++) {
> + mib = &ar8327_mib[c];
> + reg = QCA8K_PORT_MIB_COUNTER(port->index) + mib->offset;
> +
> + ret = qca8k_read(priv, reg, &val);
> + if (ret < 0)
if (ret)
> + continue;
> +
> + if (mib->size == 2) {
> + ret = qca8k_read(priv, reg + 4, &hi);
> + if (ret < 0)
same
> + continue;
> + }
> +
> + data[4 + c] = val;
> + if (mib->size == 2)
> + data[4 + c] |= (u64)hi << 32;
> + }
> +}
> +
> +static int ipqess_port_get_sset_count(struct net_device *dev, int sset)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + struct qca8k_priv *priv = port->sw->priv;
> +
> + if (sset == ETH_SS_STATS) {
> + int count = 0;
> +
> + if (sset != ETH_SS_STATS)
> + count = 0;
> + else
> + count = priv->info->mib_count;
> +
> + if (count < 0)
> + return count;
> +
> + return count + 4;
> + } else if (sset == ETH_SS_TEST) {
> + return net_selftest_get_count();
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static int ipqess_port_set_wol(struct net_device *dev,
> + struct ethtool_wolinfo *w)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> +
> + return phylink_ethtool_set_wol(port->pl, w);
> +}
> +
> +static void ipqess_port_get_wol(struct net_device *dev,
> + struct ethtool_wolinfo *w)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> +
> + phylink_ethtool_get_wol(port->pl, w);
> +}
> +
> +static int ipqess_port_set_eee(struct net_device *dev, struct ethtool_eee *eee)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + int ret;
> + u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port->index);
> + struct qca8k_priv *priv = port->sw->priv;
> + u32 lpi_ctl1;
RCT
> +
> + /* Port's PHY and MAC both need to be EEE capable */
> + if (!dev->phydev || !port->pl)
> + return -ENODEV;
> +
> + mutex_lock(&priv->reg_mutex);
> + lpi_ctl1 = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &lpi_ctl1);
> + if (lpi_ctl1 < 0) {
if (lpi_ctl1)
> + mutex_unlock(&priv->reg_mutex);
> + return ret;
ret is not initialized at this point
> + }
> +
> + if (eee->tx_lpi_enabled && eee->eee_enabled)
> + lpi_ctl1 |= lpi_en;
> + else
> + lpi_ctl1 &= ~lpi_en;
> + ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, lpi_ctl1);
> + mutex_unlock(&priv->reg_mutex);
> +
> + if (ret)
> + return ret;
> +
> + return phylink_ethtool_set_eee(port->pl, eee);
> +}
> +
> +static int ipqess_port_get_eee(struct net_device *dev, struct ethtool_eee *e)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> +
> + /* Port's PHY and MAC both need to be EEE capable */
> + if (!dev->phydev || !port->pl)
> + return -ENODEV;
> +
> + return phylink_ethtool_get_eee(port->pl, e);
> +}
> +
> +static int ipqess_port_get_link_ksettings(struct net_device *dev,
> + struct ethtool_link_ksettings *cmd)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> +
> + return phylink_ethtool_ksettings_get(port->pl, cmd);
> +}
> +
> +static int ipqess_port_set_link_ksettings(struct net_device *dev,
> + const struct ethtool_link_ksettings *cmd)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> +
> + return phylink_ethtool_ksettings_set(port->pl, cmd);
> +}
> +
> +static void ipqess_port_get_pauseparam(struct net_device *dev,
> + struct ethtool_pauseparam *pause)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> +
> + phylink_ethtool_get_pauseparam(port->pl, pause);
> +}
> +
> +static int ipqess_port_set_pauseparam(struct net_device *dev,
> + struct ethtool_pauseparam *pause)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> +
> + return phylink_ethtool_set_pauseparam(port->pl, pause);
> +}
> +
> +static const struct ethtool_ops ipqess_port_ethtool_ops = {
> + .get_drvinfo = ipqess_port_get_drvinfo,
> + .nway_reset = ipqess_port_nway_reset,
> + .get_link = ethtool_op_get_link,
> + .get_strings = ipqess_port_get_strings,
> + .get_ethtool_stats = ipqess_port_get_ethtool_stats,
> + .get_sset_count = ipqess_port_get_sset_count,
> + .self_test = net_selftest,
> + .set_wol = ipqess_port_set_wol,
> + .get_wol = ipqess_port_get_wol,
> + .set_eee = ipqess_port_set_eee,
> + .get_eee = ipqess_port_get_eee,
> + .get_link_ksettings = ipqess_port_get_link_ksettings,
> + .set_link_ksettings = ipqess_port_set_link_ksettings,
> + .get_pauseparam = ipqess_port_get_pauseparam,
> + .set_pauseparam = ipqess_port_set_pauseparam,
> +};
> +
> +void ipqess_port_set_ethtool_ops(struct net_device *netdev)
> +{
> + netdev->ethtool_ops = &ipqess_port_ethtool_ops;
> +}
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> index f0f5fe3a7c24..52d7baa7cae0 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> @@ -684,6 +684,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
> netdev->dev.of_node = port->dn;
>
> netdev->rtnl_link_ops = &ipqess_port_link_ops;
> + ipqess_port_set_ethtool_ops(netdev);
>
> netdev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
> if (!netdev->tstats) {
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> index 26bac45dd811..19d4b5d73625 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> @@ -59,4 +59,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
> struct device_node *port_node);
> void ipqess_port_unregister(struct ipqess_port *port);
>
> +/* Defined in ipqess_ethtool.c */
> +void ipqess_port_set_ethtool_ops(struct net_device *netdev);
> +
> #endif
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices
2023-11-14 10:55 ` [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Romain Gantois
2023-11-15 13:07 ` Wojciech Drewek
@ 2023-11-15 18:18 ` Simon Horman
2023-11-21 12:18 ` kernel test robot
2 siblings, 0 replies; 37+ messages in thread
From: Simon Horman @ 2023-11-15 18:18 UTC (permalink / raw)
To: Romain Gantois
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Andrew Lunn, Florian Fainelli, Heiner Kallweit,
Russell King, linux-arm-kernel, Vladimir Oltean, Luka Perkov,
Robert Marko, Andy Gross, Bjorn Andersson, Konrad Dybcio
On Tue, Nov 14, 2023 at 11:55:54AM +0100, Romain Gantois wrote:
> The IPQESS driver registers one netdevice for each front-facing switch
> port. Add support for several ethtool operations to these netdevices.
>
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
Hi Romain,
some more minor feedback from my side.
> ---
> drivers/net/ethernet/qualcomm/ipqess/Makefile | 2 +-
> .../ethernet/qualcomm/ipqess/ipqess_ethtool.c | 245 ++++++++++++++++++
> .../ethernet/qualcomm/ipqess/ipqess_port.c | 1 +
> .../ethernet/qualcomm/ipqess/ipqess_port.h | 3 +
> 4 files changed, 250 insertions(+), 1 deletion(-)
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
...
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
...
> +static int ipqess_port_set_eee(struct net_device *dev, struct ethtool_eee *eee)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + int ret;
> + u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port->index);
> + struct qca8k_priv *priv = port->sw->priv;
> + u32 lpi_ctl1;
nit: Please consider using reverse xmas tree - longest line to shortest -
for local variable declarations in networking code.
> +
> + /* Port's PHY and MAC both need to be EEE capable */
> + if (!dev->phydev || !port->pl)
> + return -ENODEV;
> +
> + mutex_lock(&priv->reg_mutex);
> + lpi_ctl1 = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &lpi_ctl1);
> + if (lpi_ctl1 < 0) {
lpi_ctl1 is unsigned, it can never be less than zero.
As flagged by Smatch and Coccinelle.
I think this should probably be (completely untested!):
ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &lpi_ctl1);
if (ret < 0) {
Which would also resolve the issue immediately below too.
> + mutex_unlock(&priv->reg_mutex);
> + return ret;
It seems that ret is used uninitialised here.
Flagged by clang-16 W=1 builds.
> + }
...
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices
2023-11-14 10:55 ` [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Romain Gantois
2023-11-15 13:07 ` Wojciech Drewek
2023-11-15 18:18 ` Simon Horman
@ 2023-11-21 12:18 ` kernel test robot
2 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-11-21 12:18 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: llvm, oe-kbuild-all, Romain Gantois, Jakub Kicinski, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, devicetree, thomas.petazzoni,
Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-5-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices
config: arm64-allyesconfig (https://download.01.org/0day-ci/archive/20231121/202311211930.FOMUSlbU-lkp@intel.com/config)
compiler: clang version 17.0.0 (https://github.com/llvm/llvm-project.git 4a5ac14ee968ff0ad5d2cc1ffa0299048db4c88a)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231121/202311211930.FOMUSlbU-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311211930.FOMUSlbU-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c:165:10: warning: variable 'ret' is uninitialized when used here [-Wuninitialized]
165 | return ret;
| ^~~
drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c:152:9: note: initialize the variable 'ret' to silence this warning
152 | int ret;
| ^
| = 0
1 warning generated.
vim +/ret +165 drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
148
149 static int ipqess_port_set_eee(struct net_device *dev, struct ethtool_eee *eee)
150 {
151 struct ipqess_port *port = netdev_priv(dev);
152 int ret;
153 u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port->index);
154 struct qca8k_priv *priv = port->sw->priv;
155 u32 lpi_ctl1;
156
157 /* Port's PHY and MAC both need to be EEE capable */
158 if (!dev->phydev || !port->pl)
159 return -ENODEV;
160
161 mutex_lock(&priv->reg_mutex);
162 lpi_ctl1 = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &lpi_ctl1);
163 if (lpi_ctl1 < 0) {
164 mutex_unlock(&priv->reg_mutex);
> 165 return ret;
166 }
167
168 if (eee->tx_lpi_enabled && eee->eee_enabled)
169 lpi_ctl1 |= lpi_en;
170 else
171 lpi_ctl1 &= ~lpi_en;
172 ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, lpi_ctl1);
173 mutex_unlock(&priv->reg_mutex);
174
175 if (ret)
176 return ret;
177
178 return phylink_ethtool_set_eee(port->pl, eee);
179 }
180
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
` (3 preceding siblings ...)
2023-11-14 10:55 ` [PATCH net-next v3 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Romain Gantois
@ 2023-11-14 10:55 ` Romain Gantois
2023-11-15 11:56 ` kernel test robot
` (8 more replies)
2023-11-14 10:55 ` [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver Romain Gantois
` (3 subsequent siblings)
8 siblings, 9 replies; 37+ messages in thread
From: Romain Gantois @ 2023-11-14 10:55 UTC (permalink / raw)
To: davem, Rob Herring, Krzysztof Kozlowski
Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
The IPQ4019 ESS switch is capable of offloading various bridge features.
Add netdev and switchdev notifiers to offload bridge uppers, link state
changes, FDB and MDB accesses and VLANs.
Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
drivers/net/dsa/qca/qca8k-8xxx.c | 49 +-
drivers/net/dsa/qca/qca8k-common.c | 42 +-
drivers/net/ethernet/qualcomm/Kconfig | 1 +
drivers/net/ethernet/qualcomm/ipqess/Makefile | 2 +-
.../ethernet/qualcomm/ipqess/ipqess_edma.c | 7 +
.../qualcomm/ipqess/ipqess_notifiers.c | 306 +++++
.../ethernet/qualcomm/ipqess/ipqess_port.c | 1050 +++++++++++++++--
.../ethernet/qualcomm/ipqess/ipqess_port.h | 33 +
.../ethernet/qualcomm/ipqess/ipqess_switch.c | 15 +-
include/linux/dsa/qca8k.h | 16 +-
10 files changed, 1391 insertions(+), 130 deletions(-)
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index 210667755b00..7f2bde8ed311 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -1979,34 +1979,71 @@ qca8k_setup(struct dsa_switch *ds)
return 0;
}
+int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ return qca8k_port_fdb_dump(ds->priv, port, cb, data);
+}
+
+void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
+ u8 state)
+{
+ qca8k_port_stp_state_set(ds->priv, port, state,
+ dsa_to_port(ds, port)->learning, true);
+}
+
+void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
+{
+ qca8k_port_fast_age(ds->priv, port);
+}
+
+int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+ return qca8k_set_ageing_time(ds->priv, msecs);
+}
+
+int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
+ bool vlan_filtering,
+ struct netlink_ext_ack *extack)
+{
+ return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
+}
+
+int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
+}
+
static const struct dsa_switch_ops qca8k_switch_ops = {
.get_tag_protocol = qca8k_get_tag_protocol,
.setup = qca8k_setup,
.get_strings = qca8k_get_strings,
.get_ethtool_stats = qca8k_get_ethtool_stats,
.get_sset_count = qca8k_get_sset_count,
- .set_ageing_time = qca8k_set_ageing_time,
+ .set_ageing_time = qca8k_dsa_set_ageing_time,
.get_mac_eee = qca8k_get_mac_eee,
.set_mac_eee = qca8k_set_mac_eee,
.port_enable = qca8k_port_enable,
.port_disable = qca8k_port_disable,
.port_change_mtu = qca8k_port_change_mtu,
.port_max_mtu = qca8k_port_max_mtu,
- .port_stp_state_set = qca8k_port_stp_state_set,
+ .port_stp_state_set = qca8k_dsa_port_stp_state_set,
.port_pre_bridge_flags = qca8k_port_pre_bridge_flags,
.port_bridge_flags = qca8k_port_bridge_flags,
.port_bridge_join = qca8k_port_bridge_join,
.port_bridge_leave = qca8k_port_bridge_leave,
- .port_fast_age = qca8k_port_fast_age,
+ .port_fast_age = qca8k_dsa_port_fast_age,
.port_fdb_add = qca8k_port_fdb_add,
.port_fdb_del = qca8k_port_fdb_del,
- .port_fdb_dump = qca8k_port_fdb_dump,
+ .port_fdb_dump = qca8k_dsa_port_fdb_dump,
.port_mdb_add = qca8k_port_mdb_add,
.port_mdb_del = qca8k_port_mdb_del,
.port_mirror_add = qca8k_port_mirror_add,
.port_mirror_del = qca8k_port_mirror_del,
- .port_vlan_filtering = qca8k_port_vlan_filtering,
- .port_vlan_add = qca8k_port_vlan_add,
+ .port_vlan_filtering = qca8k_dsa_port_vlan_filtering,
+ .port_vlan_add = qca8k_dsa_vlan_add,
.port_vlan_del = qca8k_port_vlan_del,
.phylink_get_caps = qca8k_phylink_get_caps,
.phylink_mac_select_pcs = qca8k_phylink_mac_select_pcs,
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index a66a821ce4d6..3d6ff6f24288 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -595,11 +595,9 @@ int qca8k_get_mac_eee(struct dsa_switch *ds, int port,
}
EXPORT_SYMBOL_GPL(qca8k_get_mac_eee);
-static int qca8k_port_configure_learning(struct dsa_switch *ds, int port,
+static int qca8k_port_configure_learning(struct qca8k_priv *priv, int port,
bool learning)
{
- struct qca8k_priv *priv = ds->priv;
-
if (learning)
return regmap_set_bits(priv->regmap,
QCA8K_PORT_LOOKUP_CTRL(port),
@@ -610,10 +608,10 @@ static int qca8k_port_configure_learning(struct dsa_switch *ds, int port,
QCA8K_PORT_LOOKUP_LEARN);
}
-void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+void qca8k_port_stp_state_set(struct qca8k_priv *priv,
+ int port, u8 state,
+ bool port_learning, int set_learning)
{
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct qca8k_priv *priv = ds->priv;
bool learning = false;
u32 stp_state;
@@ -629,10 +627,10 @@ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
break;
case BR_STATE_LEARNING:
stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
- learning = dp->learning;
+ learning = port_learning;
break;
case BR_STATE_FORWARDING:
- learning = dp->learning;
+ learning = port_learning;
fallthrough;
default:
stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
@@ -642,7 +640,8 @@ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
- qca8k_port_configure_learning(ds, port, learning);
+ if (set_learning)
+ qca8k_port_configure_learning(priv, port, learning);
}
EXPORT_SYMBOL_GPL(qca8k_port_stp_state_set);
@@ -664,7 +663,7 @@ int qca8k_port_bridge_flags(struct dsa_switch *ds, int port,
int ret;
if (flags.mask & BR_LEARNING) {
- ret = qca8k_port_configure_learning(ds, port,
+ ret = qca8k_port_configure_learning(ds->priv, port,
flags.val & BR_LEARNING);
if (ret)
return ret;
@@ -740,19 +739,16 @@ void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
}
EXPORT_SYMBOL_GPL(qca8k_port_bridge_leave);
-void qca8k_port_fast_age(struct dsa_switch *ds, int port)
+void qca8k_port_fast_age(struct qca8k_priv *priv, int port)
{
- struct qca8k_priv *priv = ds->priv;
-
mutex_lock(&priv->reg_mutex);
qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
mutex_unlock(&priv->reg_mutex);
}
EXPORT_SYMBOL_GPL(qca8k_port_fast_age);
-int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+int qca8k_set_ageing_time(struct qca8k_priv *priv, unsigned int msecs)
{
- struct qca8k_priv *priv = ds->priv;
unsigned int secs = msecs / 1000;
u32 val;
@@ -877,10 +873,9 @@ int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
}
EXPORT_SYMBOL_GPL(qca8k_port_fdb_del);
-int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
+int qca8k_port_fdb_dump(struct qca8k_priv *priv, int port,
dsa_fdb_dump_cb_t *cb, void *data)
{
- struct qca8k_priv *priv = ds->priv;
struct qca8k_fdb _fdb = { 0 };
int cnt = QCA8K_NUM_FDB_RECORDS;
bool is_static;
@@ -933,8 +928,8 @@ int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
EXPORT_SYMBOL_GPL(qca8k_port_mdb_del);
int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
- struct dsa_mall_mirror_tc_entry *mirror,
- bool ingress, struct netlink_ext_ack *extack)
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress, struct netlink_ext_ack *extack)
{
struct qca8k_priv *priv = ds->priv;
int monitor_port, ret;
@@ -1025,11 +1020,9 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
}
EXPORT_SYMBOL_GPL(qca8k_port_mirror_del);
-int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
- bool vlan_filtering,
- struct netlink_ext_ack *extack)
+int qca8k_port_vlan_filtering(struct qca8k_priv *priv, int port,
+ bool vlan_filtering)
{
- struct qca8k_priv *priv = ds->priv;
int ret;
if (vlan_filtering) {
@@ -1046,13 +1039,12 @@ int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
}
EXPORT_SYMBOL_GPL(qca8k_port_vlan_filtering);
-int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
+int qca8k_port_vlan_add(struct qca8k_priv *priv, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack)
{
bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
- struct qca8k_priv *priv = ds->priv;
int ret;
ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
diff --git a/drivers/net/ethernet/qualcomm/Kconfig b/drivers/net/ethernet/qualcomm/Kconfig
index 008d20ec9eae..15d120d9e64a 100644
--- a/drivers/net/ethernet/qualcomm/Kconfig
+++ b/drivers/net/ethernet/qualcomm/Kconfig
@@ -66,6 +66,7 @@ config QCOM_IPQ4019_ESS
depends on (OF && ARCH_QCOM) || COMPILE_TEST
select PHYLINK
select NET_DSA
+ select NET_SWITCHDEV
select NET_DSA_QCA8K_LIB
select PAGE_POOL
help
diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
index 6253f1b0ffd2..b12142bbc7e5 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/Makefile
+++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
@@ -5,4 +5,4 @@
obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
-ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o
+ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o ipqess_notifiers.o
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
index 008e92de9eb7..13be40c70750 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
@@ -22,6 +22,7 @@
#include "ipqess_edma.h"
#include "ipqess_port.h"
#include "ipqess_switch.h"
+#include "ipqess_notifiers.h"
static void ipqess_edma_w32(struct ipqess_edma *edma, u32 reg, u32 val)
{
@@ -1152,6 +1153,10 @@ int ipqess_edma_init(struct platform_device *pdev, struct device_node *np)
port->edma = edma;
}
+ err = ipqess_notifiers_register();
+ if (err)
+ goto err_hw_stop;
+
return 0;
err_hw_stop:
@@ -1172,6 +1177,8 @@ void ipqess_edma_uninit(struct ipqess_edma *edma)
struct qca8k_priv *priv = edma->sw->priv;
u32 val;
+ ipqess_notifiers_unregister();
+
ipqess_edma_irq_disable(edma);
ipqess_edma_hw_stop(edma);
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
new file mode 100644
index 000000000000..77f6d79c2ff6
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0 OR ISC
+/*
+ * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
+ * Based on net/dsa/slave.c
+ */
+
+#include <net/switchdev.h>
+
+#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
+#include <linux/if_hsr.h>
+
+#include "ipqess_notifiers.h"
+#include "ipqess_port.h"
+
+static struct workqueue_struct *ipqess_owq;
+
+static bool ipqess_schedule_work(struct work_struct *work)
+{
+ return queue_work(ipqess_owq, work);
+}
+
+void ipqess_flush_workqueue(void)
+{
+ flush_workqueue(ipqess_owq);
+}
+
+/* switchdev */
+
+static int ipqess_port_fdb_event(struct net_device *netdev,
+ struct net_device *orig_netdev,
+ unsigned long event, const void *ctx,
+ const struct switchdev_notifier_fdb_info *fdb_info)
+{
+ struct ipqess_switchdev_event_work *switchdev_work;
+ struct ipqess_port *port = netdev_priv(netdev);
+ bool host_addr = fdb_info->is_local;
+
+ if (ctx && ctx != port)
+ return 0;
+
+ if (!port->bridge)
+ return 0;
+
+ if (switchdev_fdb_is_dynamically_learned(fdb_info) &&
+ ipqess_port_offloads_bridge_port(port, orig_netdev))
+ return 0;
+
+ /* Also treat FDB entries on foreign interfaces bridged with us as host
+ * addresses.
+ */
+ if (ipqess_port_dev_is_foreign(netdev, orig_netdev))
+ host_addr = true;
+
+ switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+ if (!switchdev_work)
+ return -ENOMEM;
+
+ netdev_dbg(netdev, "%s FDB entry towards %s, addr %pM vid %d%s\n",
+ event == SWITCHDEV_FDB_ADD_TO_DEVICE ? "Adding" : "Deleting",
+ orig_netdev->name, fdb_info->addr, fdb_info->vid,
+ host_addr ? " as host address" : "");
+
+ INIT_WORK(&switchdev_work->work, ipqess_port_switchdev_event_work);
+ switchdev_work->event = event;
+ switchdev_work->netdev = netdev;
+ switchdev_work->orig_netdev = orig_netdev;
+
+ ether_addr_copy(switchdev_work->addr, fdb_info->addr);
+ switchdev_work->vid = fdb_info->vid;
+ switchdev_work->host_addr = host_addr;
+
+ ipqess_schedule_work(&switchdev_work->work);
+
+ return 0;
+}
+
+/* Called under rcu_read_lock() */
+static int ipqess_switchdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *netdev = switchdev_notifier_info_to_dev(ptr);
+ int err;
+
+ switch (event) {
+ case SWITCHDEV_PORT_ATTR_SET:
+ err = switchdev_handle_port_attr_set(netdev, ptr,
+ ipqess_port_recognize_netdev,
+ ipqess_port_attr_set);
+ return notifier_from_errno(err);
+ case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ case SWITCHDEV_FDB_DEL_TO_DEVICE:
+ err = switchdev_handle_fdb_event_to_device(netdev, event, ptr,
+ ipqess_port_recognize_netdev,
+ ipqess_port_dev_is_foreign,
+ ipqess_port_fdb_event);
+ return notifier_from_errno(err);
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int ipqess_switchdev_blocking_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *netdev = switchdev_notifier_info_to_dev(ptr);
+ int err;
+
+ switch (event) {
+ case SWITCHDEV_PORT_OBJ_ADD:
+ err = switchdev_handle_port_obj_add_foreign(netdev, ptr,
+ ipqess_port_recognize_netdev,
+ ipqess_port_dev_is_foreign,
+ ipqess_port_obj_add);
+ return notifier_from_errno(err);
+ case SWITCHDEV_PORT_OBJ_DEL:
+ err = switchdev_handle_port_obj_del_foreign(netdev, ptr,
+ ipqess_port_recognize_netdev,
+ ipqess_port_dev_is_foreign,
+ ipqess_port_obj_del);
+ return notifier_from_errno(err);
+ case SWITCHDEV_PORT_ATTR_SET:
+ err = switchdev_handle_port_attr_set(netdev, ptr,
+ ipqess_port_recognize_netdev,
+ ipqess_port_attr_set);
+ return notifier_from_errno(err);
+ }
+
+ return NOTIFY_DONE;
+}
+
+/* netdevice */
+
+static int ipqess_port_changeupper(struct net_device *netdev,
+ struct netdev_notifier_changeupper_info *info)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct netlink_ext_ack *extack;
+ int err = NOTIFY_DONE;
+
+ if (!ipqess_port_recognize_netdev(netdev))
+ return err;
+
+ extack = netdev_notifier_info_to_extack(&info->info);
+
+ if (netif_is_bridge_master(info->upper_dev)) {
+ if (info->linking) {
+ err = ipqess_port_bridge_join(port, info->upper_dev, extack);
+ if (err == -EOPNOTSUPP) {
+ NL_SET_ERR_MSG_WEAK_MOD(extack,
+ "Offloading not supported");
+ err = NOTIFY_DONE;
+ }
+ err = notifier_from_errno(err);
+ } else {
+ ipqess_port_bridge_leave(port, info->upper_dev);
+ err = NOTIFY_OK;
+ }
+ } else if (netif_is_lag_master(info->upper_dev)) {
+ /* LAG offloading is not supported by this driver */
+ NL_SET_ERR_MSG_WEAK_MOD(extack,
+ "Offloading not supported");
+ err = NOTIFY_DONE;
+ } else if (is_hsr_master(info->upper_dev)) {
+ if (info->linking) {
+ NL_SET_ERR_MSG_WEAK_MOD(extack,
+ "Offloading not supported");
+ err = NOTIFY_DONE;
+ } else {
+ err = NOTIFY_OK;
+ }
+ }
+
+ return err;
+}
+
+static int ipqess_port_prechangeupper(struct net_device *netdev,
+ struct netdev_notifier_changeupper_info *info)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct net_device *brport_dev;
+ int err;
+
+ /* sanity check */
+ if (is_vlan_dev(info->upper_dev)) {
+ err = ipqess_port_check_8021q_upper(netdev, info);
+ if (notifier_to_errno(err))
+ return err;
+ }
+
+ /* prechangeupper */
+ if (netif_is_bridge_master(info->upper_dev) && !info->linking)
+ brport_dev = ipqess_port_get_bridged_netdev(port);
+ else
+ return NOTIFY_DONE;
+
+ if (!brport_dev)
+ return NOTIFY_DONE;
+
+ switchdev_bridge_port_unoffload(brport_dev, port,
+ &ipqess_switchdev_notifier,
+ &ipqess_switchdev_blocking_notifier);
+
+ ipqess_flush_workqueue();
+
+ return NOTIFY_DONE;
+}
+
+static int ipqess_netdevice_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
+ int err;
+
+ if (!ipqess_port_recognize_netdev(netdev))
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_PRECHANGEUPPER: {
+ err = ipqess_port_prechangeupper(netdev, ptr);
+ if (notifier_to_errno(err))
+ return err;
+
+ break;
+ }
+
+ case NETDEV_CHANGEUPPER: {
+ err = ipqess_port_changeupper(netdev, ptr);
+ if (notifier_to_errno(err))
+ return err;
+
+ break;
+ }
+
+ /* Handling this is only useful for LAG offloading, which this driver
+ * doesn't support
+ */
+ case NETDEV_CHANGELOWERSTATE:
+ return NOTIFY_DONE;
+ case NETDEV_CHANGE:
+ case NETDEV_UP:
+ case NETDEV_GOING_DOWN:
+ default:
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+struct notifier_block ipqess_switchdev_notifier = {
+ .notifier_call = ipqess_switchdev_event,
+};
+
+struct notifier_block ipqess_switchdev_blocking_notifier = {
+ .notifier_call = ipqess_switchdev_blocking_event,
+};
+
+static struct notifier_block ipqess_nb __read_mostly = {
+ .notifier_call = ipqess_netdevice_event,
+};
+
+int ipqess_notifiers_register(void)
+{
+ int err;
+
+ ipqess_owq = alloc_ordered_workqueue("ipqess_ordered",
+ WQ_MEM_RECLAIM);
+ if (!ipqess_owq)
+ return -ENOMEM;
+
+ err = register_netdevice_notifier(&ipqess_nb);
+ if (err)
+ goto err_netdev_nb;
+
+ err = register_switchdev_notifier(&ipqess_switchdev_notifier);
+ if (err)
+ goto err_switchdev_nb;
+
+ err = register_switchdev_blocking_notifier(&ipqess_switchdev_blocking_notifier);
+ if (err)
+ goto err_switchdev_blocking_nb;
+
+ return 0;
+
+err_switchdev_blocking_nb:
+ unregister_switchdev_notifier(&ipqess_switchdev_notifier);
+err_switchdev_nb:
+ unregister_netdevice_notifier(&ipqess_nb);
+err_netdev_nb:
+ destroy_workqueue(ipqess_owq);
+
+ return err;
+}
+EXPORT_SYMBOL(ipqess_notifiers_register);
+
+void ipqess_notifiers_unregister(void)
+{
+ unregister_switchdev_blocking_notifier(&ipqess_switchdev_blocking_notifier);
+ unregister_switchdev_notifier(&ipqess_switchdev_notifier);
+ unregister_netdevice_notifier(&ipqess_nb);
+
+ destroy_workqueue(ipqess_owq);
+}
+EXPORT_SYMBOL(ipqess_notifiers_unregister);
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
index 52d7baa7cae0..29420820c3d8 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
@@ -23,50 +23,50 @@ static struct device_type ipqess_port_type = {
.name = "switch",
};
+struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port)
+{
+ if (!port->bridge)
+ return NULL;
+
+ return port->netdev;
+}
+
/* netdev ops */
+static void ipqess_port_notify_bridge_fdb_flush(const struct ipqess_port *port,
+ u16 vid)
+{
+ struct net_device *brport_dev = ipqess_port_get_bridged_netdev(port);
+ struct switchdev_notifier_fdb_info info = {
+ .vid = vid,
+ };
+
+ /* When the port becomes standalone it has already left the bridge.
+ * Don't notify the bridge in that case.
+ */
+ if (!brport_dev)
+ return;
+
+ call_switchdev_notifiers(SWITCHDEV_FDB_FLUSH_TO_BRIDGE,
+ brport_dev, &info.info, NULL);
+}
+
static void ipqess_port_fast_age(const struct ipqess_port *port)
{
struct qca8k_priv *priv = port->sw->priv;
- mutex_lock(&priv->reg_mutex);
- qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port->index);
- mutex_unlock(&priv->reg_mutex);
+ qca8k_port_fast_age(priv, port->index);
+
+ /* Flush all VLANs */
+ ipqess_port_notify_bridge_fdb_flush(port, 0);
}
static void ipqess_port_stp_state_set(struct ipqess_port *port,
u8 state)
{
struct qca8k_priv *priv = port->sw->priv;
- u32 stp_state;
- int err;
- switch (state) {
- case BR_STATE_DISABLED:
- stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
- break;
- case BR_STATE_BLOCKING:
- stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
- break;
- case BR_STATE_LISTENING:
- stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
- break;
- case BR_STATE_LEARNING:
- stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
- break;
- case BR_STATE_FORWARDING:
- default:
- stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
- break;
- }
-
- err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
- QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
-
- if (err)
- dev_warn(priv->dev,
- "failed to set STP state %d for port %d: err %d\n",
- stp_state, port->index, err);
+ qca8k_port_stp_state_set(priv, port->index, state, false, false);
}
static void ipqess_port_set_state_now(struct ipqess_port *port,
@@ -93,7 +93,8 @@ static int ipqess_port_enable_rt(struct ipqess_port *port,
phy_support_asym_pause(phy);
- ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
+ if (!port->bridge)
+ ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
if (port->pl)
phylink_start(port->pl);
@@ -108,7 +109,8 @@ static void ipqess_port_disable_rt(struct ipqess_port *port)
if (port->pl)
phylink_stop(port->pl);
- ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
+ if (!port->bridge)
+ ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
qca8k_port_set_status(priv, port->index, 0);
priv->port_enabled_map &= ~BIT(port->index);
@@ -204,34 +206,9 @@ static int ipqess_port_change_mtu(struct net_device *dev, int new_mtu)
return 0;
}
-static int ipqess_port_do_vlan_add(struct qca8k_priv *priv, int port_index,
- const struct switchdev_obj_port_vlan *vlan,
- struct netlink_ext_ack *extack)
+static inline struct net_device *ipqess_port_bridge_dev_get(struct ipqess_port *port)
{
- bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
- bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
- int ret;
-
- ret = qca8k_vlan_add(priv, port_index, vlan->vid, untagged);
- if (ret) {
- dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port_index,
- ret);
- return ret;
- }
-
- if (pvid) {
- ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
- QCA8K_EGREES_VLAN_PORT_MASK(port_index),
- QCA8K_EGREES_VLAN_PORT(port_index, vlan->vid));
- if (ret)
- return ret;
-
- ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
- QCA8K_PORT_VLAN_CVID(vlan->vid) |
- QCA8K_PORT_VLAN_SVID(vlan->vid));
- }
-
- return ret;
+ return port->bridge ? port->bridge->netdev : NULL;
}
static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
@@ -248,7 +225,7 @@ static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
int ret;
/* User port... */
- ret = ipqess_port_do_vlan_add(port->sw->priv, port->index, &vlan, &extack);
+ ret = qca8k_port_vlan_add(port->sw->priv, port->index, &vlan, &extack);
if (ret) {
if (extack._msg)
netdev_err(dev, "%s\n", extack._msg);
@@ -256,7 +233,7 @@ static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
}
/* And CPU port... */
- ret = ipqess_port_do_vlan_add(port->sw->priv, 0, &vlan, &extack);
+ ret = qca8k_port_vlan_add(port->sw->priv, 0, &vlan, &extack);
if (ret) {
if (extack._msg)
netdev_err(dev, "CPU port %d: %s\n", 0, extack._msg);
@@ -340,24 +317,13 @@ ipqess_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
.cb = cb,
.idx = *idx,
};
- int cnt = QCA8K_NUM_FDB_RECORDS;
- struct qca8k_fdb _fdb = { 0 };
- bool is_static;
int ret = 0;
- mutex_lock(&priv->reg_mutex);
- while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port->index)) {
- if (!_fdb.aging)
- break;
- is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
- ret = ipqess_port_fdb_do_dump(_fdb.mac, _fdb.vid, is_static, &dump);
- if (ret)
- break;
- }
- mutex_unlock(&priv->reg_mutex);
-
*idx = dump.idx;
+ ret = qca8k_port_fdb_dump(priv, port->index, ipqess_port_fdb_do_dump,
+ &dump);
+
return ret;
}
@@ -374,6 +340,882 @@ static const struct net_device_ops ipqess_port_netdev_ops = {
.ndo_fdb_dump = ipqess_port_fdb_dump,
};
+/* Bridge ops */
+
+static int ipqess_port_bridge_alloc(struct ipqess_port *port,
+ struct net_device *br,
+ struct netlink_ext_ack *extack)
+{
+ struct ipqess_bridge *bridge;
+
+ bridge = kzalloc(sizeof(*bridge), GFP_KERNEL);
+ if (!bridge)
+ return -ENOMEM;
+
+ refcount_set(&bridge->refcount, 1);
+
+ bridge->netdev = br;
+
+ port->bridge = bridge;
+
+ return 0;
+}
+
+/* Must be called under rcu_read_lock() */
+static bool ipqess_port_can_apply_vlan_filtering(struct ipqess_port *port,
+ bool vlan_filtering,
+ struct netlink_ext_ack *extack)
+{
+ int err;
+
+ /* VLAN awareness was off, so the question is "can we turn it on".
+ * We may have had 8021q uppers, those need to go. Make sure we don't
+ * enter an inconsistent state: deny changing the VLAN awareness state
+ * as long as we have 8021q uppers.
+ */
+ if (vlan_filtering) {
+ struct net_device *br = ipqess_port_bridge_dev_get(port);
+ struct net_device *upper_dev, *netdev = port->netdev;
+ struct list_head *iter;
+
+ netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) {
+ struct bridge_vlan_info br_info;
+ u16 vid;
+
+ if (!is_vlan_dev(upper_dev))
+ continue;
+
+ vid = vlan_dev_vlan_id(upper_dev);
+
+ /* br_vlan_get_info() returns -EINVAL or -ENOENT if the
+ * device, respectively the VID is not found, returning
+ * 0 means success, which is a failure for us here.
+ */
+ err = br_vlan_get_info(br, vid, &br_info);
+ if (err == 0) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Must first remove VLAN uppers having VIDs also present in bridge");
+ return false;
+ }
+ }
+ }
+
+ /* VLAN filtering is not global so we can just return true here */
+ return true;
+}
+
+static int ipqess_port_restore_vlan(struct net_device *vdev, int vid, void *arg)
+{
+ __be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
+
+ return ipqess_port_vlan_rx_add_vid(arg, proto, vid);
+}
+
+static int ipqess_port_clear_vlan(struct net_device *vdev, int vid, void *arg)
+{
+ __be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
+
+ return ipqess_port_vlan_rx_kill_vid(arg, proto, vid);
+}
+
+/* Keep the VLAN RX filtering list in sync with the hardware only if VLAN
+ * filtering is enabled.
+ */
+static int ipqess_port_manage_vlan_filtering(struct net_device *netdev,
+ bool vlan_filtering)
+{
+ int err;
+
+ if (vlan_filtering) {
+ netdev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
+
+ err = vlan_for_each(netdev, ipqess_port_restore_vlan, netdev);
+ if (err) {
+ netdev_err(netdev,
+ "Failed to restore all VLAN's successfully, error %d\n",
+ err);
+ vlan_for_each(netdev, ipqess_port_clear_vlan, netdev);
+ netdev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
+ return err;
+ }
+ } else {
+ err = vlan_for_each(netdev, ipqess_port_clear_vlan, netdev);
+ if (err)
+ return err;
+
+ netdev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
+ }
+
+ return 0;
+}
+
+static int ipqess_port_vlan_filtering(struct ipqess_port *port,
+ bool vlan_filtering,
+ struct netlink_ext_ack *extack)
+{
+ bool old_vlan_filtering = port->vlan_filtering;
+ bool apply;
+ int err;
+
+ /* We are called from ipqess_port_switchdev_blocking_event(),
+ * which is not under rcu_read_lock(), unlike
+ * ipqess_port_switchdev_event().
+ */
+ rcu_read_lock();
+ apply = ipqess_port_can_apply_vlan_filtering(port, vlan_filtering, extack);
+ rcu_read_unlock();
+ if (!apply)
+ return -EINVAL;
+
+ if (old_vlan_filtering == vlan_filtering)
+ return 0;
+
+ err = qca8k_port_vlan_filtering(port->sw->priv, port->index,
+ vlan_filtering);
+
+ if (err)
+ return err;
+
+ port->vlan_filtering = vlan_filtering;
+
+ err = ipqess_port_manage_vlan_filtering(port->netdev,
+ vlan_filtering);
+ if (err)
+ goto restore;
+
+ return 0;
+
+restore:
+ err = qca8k_port_vlan_filtering(port->sw->priv, port->index,
+ old_vlan_filtering);
+ port->vlan_filtering = old_vlan_filtering;
+
+ return err;
+}
+
+static void ipqess_port_reset_vlan_filtering(struct ipqess_port *port,
+ struct ipqess_bridge *bridge)
+{
+ struct netlink_ext_ack extack = {0};
+ bool change_vlan_filtering = false;
+ bool vlan_filtering;
+ int err;
+
+ if (br_vlan_enabled(bridge->netdev)) {
+ change_vlan_filtering = true;
+ vlan_filtering = false;
+ }
+
+ if (!change_vlan_filtering)
+ return;
+
+ err = ipqess_port_vlan_filtering(port, vlan_filtering, &extack);
+ if (extack._msg) {
+ dev_err(&port->netdev->dev, "port %d: %s\n", port->index,
+ extack._msg);
+ }
+ if (err && err != -EOPNOTSUPP) {
+ dev_err(&port->netdev->dev,
+ "port %d failed to reset VLAN filtering to %d: %pe\n",
+ port->index, vlan_filtering, ERR_PTR(err));
+ }
+}
+
+static int ipqess_port_ageing_time(struct ipqess_port *port,
+ clock_t ageing_clock)
+{
+ unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock);
+ unsigned int ageing_time = jiffies_to_msecs(ageing_jiffies);
+
+ if (ageing_time < IPQESS_SWITCH_AGEING_TIME_MIN ||
+ ageing_time > IPQESS_SWITCH_AGEING_TIME_MAX)
+ return -ERANGE;
+
+ /* Program the fastest ageing time in case of multiple bridges */
+ ageing_time = ipqess_switch_fastest_ageing_time(port->sw, ageing_time);
+
+ port->ageing_time = ageing_time;
+ return ipqess_set_ageing_time(port->sw, ageing_time);
+}
+
+static int ipqess_port_switchdev_sync_attrs(struct ipqess_port *port,
+ struct netlink_ext_ack *extack)
+{
+ struct net_device *brport_dev = ipqess_port_get_bridged_netdev(port);
+ struct net_device *br = ipqess_port_bridge_dev_get(port);
+ int err;
+
+ ipqess_port_set_state_now(port, br_port_get_stp_state(brport_dev), false);
+
+ err = ipqess_port_vlan_filtering(port, br_vlan_enabled(br), extack);
+ if (err)
+ return err;
+
+ err = ipqess_port_ageing_time(port, br_get_ageing_time(br));
+ if (err && err != -EOPNOTSUPP)
+ return err;
+
+ return 0;
+}
+
+static void ipqess_port_switchdev_unsync_attrs(struct ipqess_port *port,
+ struct ipqess_bridge *bridge)
+{
+ /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
+ * so allow it to be in BR_STATE_FORWARDING to be kept functional
+ */
+ ipqess_port_set_state_now(port, BR_STATE_FORWARDING, true);
+
+ ipqess_port_reset_vlan_filtering(port, bridge);
+
+ /* Ageing time is global to the switch chip, so don't change it
+ * here because we have no good reason (or value) to change it to.
+ */
+}
+
+static inline bool ipqess_port_offloads_bridge(struct ipqess_port *port,
+ const struct ipqess_bridge *bridge)
+{
+ return ipqess_port_bridge_dev_get(port) == bridge->netdev;
+}
+
+bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
+ const struct net_device *netdev)
+{
+ return ipqess_port_get_bridged_netdev(port) == netdev;
+}
+
+static inline bool
+ipqess_port_offloads_bridge_dev(struct ipqess_port *port,
+ const struct net_device *bridge_dev)
+{
+ /* QCA8K ports connected to a bridge, and event was emitted
+ * for the bridge.
+ */
+ return ipqess_port_bridge_dev_get(port) == bridge_dev;
+}
+
+static void ipqess_port_bridge_destroy(struct ipqess_port *port,
+ const struct net_device *br)
+{
+ struct ipqess_bridge *bridge = port->bridge;
+
+ port->bridge = NULL;
+
+ if (!refcount_dec_and_test(&bridge->refcount))
+ return;
+
+ kfree(bridge);
+}
+
+int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
+ struct netlink_ext_ack *extack)
+{
+ struct ipqess_switch *sw = port->sw;
+ struct ipqess_bridge *bridge = NULL;
+ struct qca8k_priv *priv = sw->priv;
+ struct ipqess_port *other_port;
+ struct net_device *brport_dev;
+ int port_id = port->index;
+ int port_mask = 0;
+ int i, err;
+
+ /* QCA8K doesn't support MST */
+ if (br_mst_enabled(br)) {
+ err = -EOPNOTSUPP;
+ goto out_err;
+ }
+
+ /* Check if we already registered this bridge with
+ * another switch port
+ */
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ other_port = sw->port_list[i];
+ if (other_port && other_port->bridge &&
+ other_port->bridge->netdev == br)
+ bridge = other_port->bridge;
+ }
+
+ if (bridge) {
+ refcount_inc(&bridge->refcount);
+ port->bridge = bridge;
+ } else {
+ err = ipqess_port_bridge_alloc(port, br, extack);
+ if (err)
+ goto out_err;
+ }
+ bridge = port->bridge;
+
+ for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
+ other_port = sw->port_list[i - 1];
+ if (!other_port || !ipqess_port_offloads_bridge(other_port, bridge))
+ continue;
+ /* Add this port to the portvlan mask of the other ports
+ * in the bridge
+ */
+ err = regmap_set_bits(priv->regmap,
+ QCA8K_PORT_LOOKUP_CTRL(i),
+ BIT(port_id));
+ if (err)
+ goto out_rollback;
+ if (i != port_id)
+ port_mask |= BIT(i);
+ }
+ /* Also add the CPU port */
+ err = regmap_set_bits(priv->regmap,
+ QCA8K_PORT_LOOKUP_CTRL(0),
+ BIT(port_id));
+ port_mask |= BIT(0);
+
+ /* Add all other ports to this ports portvlan mask */
+ err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
+ QCA8K_PORT_LOOKUP_MEMBER, port_mask);
+ if (err)
+ goto out_rollback;
+
+ brport_dev = ipqess_port_get_bridged_netdev(port);
+
+ err = switchdev_bridge_port_offload(brport_dev, port->netdev, port,
+ &ipqess_switchdev_notifier,
+ &ipqess_switchdev_blocking_notifier,
+ false, extack);
+ if (err)
+ goto out_rollback_unbridge;
+
+ err = ipqess_port_switchdev_sync_attrs(port, extack);
+ if (err)
+ goto out_rollback_unoffload;
+
+ return 0;
+
+out_rollback_unoffload:
+ switchdev_bridge_port_unoffload(brport_dev, port,
+ &ipqess_switchdev_notifier,
+ &ipqess_switchdev_blocking_notifier);
+ ipqess_flush_workqueue();
+out_rollback_unbridge:
+ for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
+ other_port = sw->port_list[i - 1];
+ if (!other_port ||
+ !ipqess_port_offloads_bridge(other_port, port->bridge))
+ continue;
+ /* Remove this port from the portvlan mask of the other ports
+ * in the bridge
+ */
+ regmap_clear_bits(priv->regmap,
+ QCA8K_PORT_LOOKUP_CTRL(i),
+ BIT(port_id));
+ }
+
+ /* Set the cpu port to be the only one in the portvlan mask of
+ * this port
+ */
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
+ QCA8K_PORT_LOOKUP_MEMBER, BIT(0));
+out_rollback:
+ ipqess_port_bridge_destroy(port, br);
+out_err:
+ dev_err(&port->netdev->dev, "Failed to join bridge: errno %d\n", err);
+ return err;
+}
+
+void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br)
+{
+ struct ipqess_bridge *bridge = port->bridge;
+ struct ipqess_switch *sw = port->sw;
+ struct qca8k_priv *priv = sw->priv;
+ struct ipqess_port *other_port;
+ int port_id = port->index;
+ int i;
+
+ /* If the port could not be offloaded to begin with, then
+ * there is nothing to do.
+ */
+ if (!bridge)
+ return;
+
+ for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
+ other_port = sw->port_list[i - 1];
+ if (!other_port || !ipqess_port_offloads_bridge(other_port, bridge))
+ continue;
+ /* Remove this port from the portvlan mask of the other ports
+ * in the bridge
+ */
+ regmap_clear_bits(priv->regmap,
+ QCA8K_PORT_LOOKUP_CTRL(i),
+ BIT(port_id));
+ }
+
+ /* Set the cpu port to be the only one in the portvlan mask of
+ * this port
+ */
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
+ QCA8K_PORT_LOOKUP_MEMBER, BIT(0));
+
+ ipqess_port_switchdev_unsync_attrs(port, bridge);
+
+ /* Here the port is already unbridged. Reflect the current configuration. */
+
+ ipqess_port_bridge_destroy(port, br);
+}
+
+int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
+ const struct switchdev_attr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct ipqess_port *port = netdev_priv(dev);
+ int ret;
+
+ if (ctx && ctx != port)
+ return 0;
+
+ switch (attr->id) {
+ case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
+ if (!ipqess_port_offloads_bridge_port(port, attr->orig_dev))
+ return -EOPNOTSUPP;
+
+ ipqess_port_set_state_now(port, attr->u.stp_state, true);
+ return 0;
+ case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
+ if (!ipqess_port_offloads_bridge_dev(port, attr->orig_dev))
+ return -EOPNOTSUPP;
+
+ ret = ipqess_port_vlan_filtering(port, attr->u.vlan_filtering,
+ extack);
+ break;
+ case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
+ if (!ipqess_port_offloads_bridge_dev(port, attr->orig_dev))
+ return -EOPNOTSUPP;
+
+ ret = ipqess_port_ageing_time(port, attr->u.ageing_time);
+ break;
+ case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
+ if (!ipqess_port_offloads_bridge_port(port, attr->orig_dev))
+ return -EOPNOTSUPP;
+
+ return -EINVAL;
+ case SWITCHDEV_ATTR_ID_BRIDGE_MST:
+ case SWITCHDEV_ATTR_ID_PORT_MST_STATE:
+ case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
+ case SWITCHDEV_ATTR_ID_VLAN_MSTI:
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
+
+static int ipqess_port_vlan_check_for_8021q_uppers(struct net_device *netdev,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct net_device *upper_dev;
+ struct list_head *iter;
+
+ netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) {
+ u16 vid;
+
+ if (!is_vlan_dev(upper_dev))
+ continue;
+
+ vid = vlan_dev_vlan_id(upper_dev);
+ if (vid == vlan->vid)
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int ipqess_port_host_vlan_del(struct net_device *netdev,
+ const struct switchdev_obj *obj)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct net_device *br = ipqess_port_bridge_dev_get(port);
+ struct switchdev_obj_port_vlan *vlan;
+
+ /* Do nothing if this is a software bridge */
+ if (!port->bridge)
+ return -EOPNOTSUPP;
+
+ if (br && !br_vlan_enabled(br))
+ return 0;
+
+ vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
+
+ return qca8k_vlan_del(port->sw->priv, 0, vlan->vid);
+}
+
+static int ipqess_port_vlan_del(struct net_device *netdev,
+ const struct switchdev_obj *obj)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct net_device *br = ipqess_port_bridge_dev_get(port);
+ struct qca8k_priv *priv = port->sw->priv;
+ struct switchdev_obj_port_vlan *vlan;
+ int ret;
+
+ if (br && !br_vlan_enabled(br))
+ return 0;
+
+ vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
+
+ ret = qca8k_vlan_del(priv, port->index, vlan->vid);
+
+ if (ret)
+ dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)\n",
+ port->index, ret);
+
+ return ret;
+}
+
+static int ipqess_port_host_vlan_add(struct net_device *netdev,
+ const struct switchdev_obj *obj,
+ struct netlink_ext_ack *extack)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct switchdev_obj_port_vlan *vlan;
+ struct net_device *br;
+
+ br = ipqess_port_bridge_dev_get(port);
+ /* Do nothing is this is a software bridge */
+ if (!port->bridge)
+ return -EOPNOTSUPP;
+
+ if (br && !br_vlan_enabled(br)) {
+ NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
+ return 0;
+ }
+
+ vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
+
+ vlan->flags &= ~BRIDGE_VLAN_INFO_PVID;
+
+ /* Add vid to CPU port */
+ return qca8k_port_vlan_add(port->sw->priv, 0, vlan, extack);
+}
+
+static int ipqess_port_vlan_add(struct net_device *netdev,
+ const struct switchdev_obj *obj,
+ struct netlink_ext_ack *extack)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct net_device *br = ipqess_port_bridge_dev_get(port);
+ struct switchdev_obj_port_vlan *vlan;
+ int err;
+
+ if (br && !br_vlan_enabled(br)) {
+ NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
+ return 0;
+ }
+
+ vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
+
+ /* Deny adding a bridge VLAN when there is already an 802.1Q upper with
+ * the same VID.
+ */
+ if (br && br_vlan_enabled(br)) {
+ rcu_read_lock();
+ err = ipqess_port_vlan_check_for_8021q_uppers(netdev, vlan);
+ rcu_read_unlock();
+ if (err) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Port already has a VLAN upper with this VID");
+ return err;
+ }
+ }
+
+ err = qca8k_port_vlan_add(port->sw->priv, port->index, vlan, extack);
+ return err;
+}
+
+static int ipqess_port_host_mdb_del(struct ipqess_port *port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+ const u8 *addr = mdb->addr;
+ u16 vid = mdb->vid;
+
+ return qca8k_fdb_search_and_del(priv, BIT(0), addr, vid);
+}
+
+static int ipqess_port_host_mdb_add(struct ipqess_port *port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+ const u8 *addr = mdb->addr;
+ u16 vid = mdb->vid;
+
+ return qca8k_fdb_search_and_insert(priv, BIT(0), addr, vid,
+ QCA8K_ATU_STATUS_STATIC);
+}
+
+static int ipqess_port_mdb_del(struct ipqess_port *port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+ const u8 *addr = mdb->addr;
+ u16 vid = mdb->vid;
+
+ return qca8k_fdb_search_and_del(priv, BIT(port->index), addr, vid);
+}
+
+static int ipqess_port_mdb_add(struct ipqess_port *port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+ const u8 *addr = mdb->addr;
+ u16 vid = mdb->vid;
+
+ return qca8k_fdb_search_and_insert(priv, BIT(port->index), addr, vid,
+ QCA8K_ATU_STATUS_STATIC);
+}
+
+int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
+ const struct switchdev_obj *obj,
+ struct netlink_ext_ack *extack)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ int err;
+
+ if (ctx && ctx != port)
+ return 0;
+
+ switch (obj->id) {
+ case SWITCHDEV_OBJ_ID_PORT_MDB:
+ if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
+ return -EOPNOTSUPP;
+
+ err = ipqess_port_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
+ break;
+ case SWITCHDEV_OBJ_ID_HOST_MDB:
+ if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
+ return -EOPNOTSUPP;
+
+ err = ipqess_port_host_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
+ break;
+ case SWITCHDEV_OBJ_ID_PORT_VLAN:
+ if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
+ err = ipqess_port_vlan_add(netdev, obj, extack);
+ else
+ err = ipqess_port_host_vlan_add(netdev, obj, extack);
+ break;
+ case SWITCHDEV_OBJ_ID_MRP:
+ case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
+ const struct switchdev_obj *obj)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ int err;
+
+ if (ctx && ctx != port)
+ return 0;
+
+ switch (obj->id) {
+ case SWITCHDEV_OBJ_ID_PORT_MDB:
+ if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
+ return -EOPNOTSUPP;
+
+ err = ipqess_port_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
+ break;
+ case SWITCHDEV_OBJ_ID_HOST_MDB:
+ if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
+ return -EOPNOTSUPP;
+
+ err = ipqess_port_host_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
+ break;
+ case SWITCHDEV_OBJ_ID_PORT_VLAN:
+ if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
+ err = ipqess_port_vlan_del(netdev, obj);
+ else
+ err = ipqess_port_host_vlan_del(netdev, obj);
+ break;
+ case SWITCHDEV_OBJ_ID_MRP:
+ case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+static int ipqess_cpu_port_fdb_del(struct ipqess_port *port,
+ const unsigned char *addr, u16 vid)
+{
+ struct ipqess_mac_addr *mac_addr = NULL;
+ struct ipqess_mac_addr *other_mac_addr;
+ struct ipqess_switch *sw = port->sw;
+ int err = 0;
+
+ mutex_lock(&sw->addr_lists_lock);
+
+ list_for_each_entry(other_mac_addr, &sw->fdbs, list)
+ if (ether_addr_equal(other_mac_addr->addr, addr) && other_mac_addr->vid == vid)
+ mac_addr = other_mac_addr;
+
+ if (!mac_addr) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ if (!refcount_dec_and_test(&mac_addr->refcount))
+ goto out;
+
+ err = qca8k_fdb_del(sw->priv, addr, BIT(IPQESS_SWITCH_CPU_PORT), vid);
+ if (err) {
+ refcount_set(&mac_addr->refcount, 1);
+ goto out;
+ }
+
+ list_del(&mac_addr->list);
+ kfree(mac_addr);
+
+out:
+ mutex_unlock(&sw->addr_lists_lock);
+
+ return err;
+}
+
+static int ipqess_cpu_port_fdb_add(struct ipqess_port *port,
+ const unsigned char *addr, u16 vid)
+{
+ struct ipqess_switch *sw = port->sw;
+ struct ipqess_mac_addr *other_a = NULL;
+ struct ipqess_mac_addr *a = NULL;
+ int err = 0;
+
+ mutex_lock(&sw->addr_lists_lock);
+
+ list_for_each_entry(other_a, &sw->fdbs, list)
+ if (ether_addr_equal(other_a->addr, addr) && other_a->vid == vid)
+ a = other_a;
+
+ if (a) {
+ refcount_inc(&a->refcount);
+ goto out;
+ }
+
+ a = kzalloc(sizeof(*a), GFP_KERNEL);
+ if (!a) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = qca8k_port_fdb_insert(port->sw->priv, addr,
+ BIT(IPQESS_SWITCH_CPU_PORT), vid);
+ if (err) {
+ kfree(a);
+ goto out;
+ }
+
+ ether_addr_copy(a->addr, addr);
+ a->vid = vid;
+ refcount_set(&a->refcount, 1);
+ list_add_tail(&a->list, &sw->fdbs);
+
+out:
+ mutex_unlock(&sw->addr_lists_lock);
+
+ return err;
+}
+
+static void
+ipqess_fdb_offload_notify(struct ipqess_switchdev_event_work *switchdev_work)
+{
+ struct switchdev_notifier_fdb_info info = {};
+
+ info.addr = switchdev_work->addr;
+ info.vid = switchdev_work->vid;
+ info.offloaded = true;
+ call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
+ switchdev_work->orig_netdev, &info.info, NULL);
+}
+
+void ipqess_port_switchdev_event_work(struct work_struct *work)
+{
+ struct ipqess_switchdev_event_work *switchdev_work =
+ container_of(work, struct ipqess_switchdev_event_work, work);
+ struct net_device *netdev = switchdev_work->netdev;
+ const unsigned char *addr = switchdev_work->addr;
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct ipqess_switch *sw = port->sw;
+ struct qca8k_priv *priv = sw->priv;
+ u16 vid = switchdev_work->vid;
+ int err;
+
+ if (!vid)
+ vid = QCA8K_PORT_VID_DEF;
+
+ switch (switchdev_work->event) {
+ case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ if (switchdev_work->host_addr)
+ err = ipqess_cpu_port_fdb_add(port, addr, vid);
+ else
+ err = qca8k_port_fdb_insert(priv, addr, BIT(port->index), vid);
+ if (err) {
+ dev_err(&port->netdev->dev,
+ "port %d failed to add %pM vid %d to fdb: %d\n",
+ port->index, addr, vid, err);
+ break;
+ }
+ ipqess_fdb_offload_notify(switchdev_work);
+ break;
+
+ case SWITCHDEV_FDB_DEL_TO_DEVICE:
+ if (switchdev_work->host_addr)
+ err = ipqess_cpu_port_fdb_del(port, addr, vid);
+ else
+ err = qca8k_fdb_del(priv, addr, BIT(port->index), vid);
+ if (err) {
+ dev_err(&port->netdev->dev,
+ "port %d failed to delete %pM vid %d from fdb: %d\n",
+ port->index, addr, vid, err);
+ }
+
+ break;
+ }
+
+ kfree(switchdev_work);
+}
+
+int ipqess_port_check_8021q_upper(struct net_device *netdev,
+ struct netdev_notifier_changeupper_info *info)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct net_device *br = ipqess_port_bridge_dev_get(port);
+ struct bridge_vlan_info br_info;
+ struct netlink_ext_ack *extack;
+ int err = NOTIFY_DONE;
+ u16 vid;
+
+ if (!br || !br_vlan_enabled(br))
+ return NOTIFY_DONE;
+
+ extack = netdev_notifier_info_to_extack(&info->info);
+ vid = vlan_dev_vlan_id(info->upper_dev);
+
+ /* br_vlan_get_info() returns -EINVAL or -ENOENT if the
+ * device, respectively the VID is not found, returning
+ * 0 means success, which is a failure for us here.
+ */
+ err = br_vlan_get_info(br, vid, &br_info);
+ if (err == 0) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "This VLAN is already configured by the bridge");
+ return notifier_from_errno(-EBUSY);
+ }
+
+ return NOTIFY_DONE;
+}
+
/* phylink ops */
static void
@@ -669,6 +1511,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
port->edma = NULL; /* Assigned during edma initialization */
port->qid = port->index - 1;
port->sw = sw;
+ port->bridge = NULL;
of_get_mac_address(port_node, port->mac);
if (!is_zero_ether_addr(port->mac))
@@ -756,3 +1599,58 @@ void ipqess_port_unregister(struct ipqess_port *port)
free_netdev(netdev);
}
+/* Utilities */
+
+/* Returns true if any port of this switch offloads the given net_device */
+static bool ipqess_switch_offloads_bridge_port(struct ipqess_switch *sw,
+ const struct net_device *netdev)
+{
+ struct ipqess_port *port;
+ int i;
+
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ port = sw->port_list[i];
+ if (port && ipqess_port_offloads_bridge_port(port, netdev))
+ return true;
+ }
+
+ return false;
+}
+
+/* Returns true if any port of this switch offloads the given bridge */
+static inline bool
+ipqess_switch_offloads_bridge_dev(struct ipqess_switch *sw,
+ const struct net_device *bridge_dev)
+{
+ struct ipqess_port *port;
+ int i;
+
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ port = sw->port_list[i];
+ if (port && ipqess_port_offloads_bridge_dev(port, bridge_dev))
+ return true;
+ }
+
+ return false;
+}
+
+bool ipqess_port_recognize_netdev(const struct net_device *netdev)
+{
+ return netdev->netdev_ops == &ipqess_port_netdev_ops;
+}
+
+bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
+ const struct net_device *foreign_netdev)
+{
+ struct ipqess_port *port = netdev_priv(netdev);
+ struct ipqess_switch *sw = port->sw;
+
+ if (netif_is_bridge_master(foreign_netdev))
+ return !ipqess_switch_offloads_bridge_dev(sw, foreign_netdev);
+
+ if (netif_is_bridge_port(foreign_netdev))
+ return !ipqess_switch_offloads_bridge_port(sw, foreign_netdev);
+
+ /* Everything else is foreign */
+ return true;
+}
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
index 19d4b5d73625..00f0dff9c39d 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
@@ -9,6 +9,11 @@
#include "ipqess_edma.h"
#include "ipqess_switch.h"
+struct ipqess_bridge {
+ struct net_device *netdev;
+ refcount_t refcount;
+};
+
struct ipqess_port {
u16 index;
u16 qid;
@@ -20,6 +25,7 @@ struct ipqess_port {
struct device_node *dn;
struct mii_bus *mii_bus;
struct net_device *netdev;
+ struct ipqess_bridge *bridge;
struct devlink_port devlink_port;
u8 stp_state;
@@ -62,4 +68,31 @@ void ipqess_port_unregister(struct ipqess_port *port);
/* Defined in ipqess_ethtool.c */
void ipqess_port_set_ethtool_ops(struct net_device *netdev);
+bool ipqess_port_recognize_netdev(const struct net_device *netdev);
+bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
+ const struct net_device *foreign_netdev);
+
+int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
+ struct netlink_ext_ack *extack);
+void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br);
+
+int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
+ const struct switchdev_attr *attr,
+ struct netlink_ext_ack *extack);
+
+void ipqess_port_switchdev_event_work(struct work_struct *work);
+
+int ipqess_port_check_8021q_upper(struct net_device *netdev,
+ struct netdev_notifier_changeupper_info *info);
+
+struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port);
+
+int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
+ const struct switchdev_obj *obj,
+ struct netlink_ext_ack *extack);
+int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
+ const struct switchdev_obj *obj);
+
+bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
+ const struct net_device *netdev);
#endif
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
index 927f834a62bc..d09d0aa8314f 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
@@ -80,21 +80,8 @@ unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs)
{
struct qca8k_priv *priv = sw->priv;
- unsigned int secs = msecs / 1000;
- u32 val;
- /* AGE_TIME reg is set in 7s step */
- val = secs / 7;
-
- /* Handle case with 0 as val to NOT disable
- * learning
- */
- if (!val)
- val = 1;
-
- return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL,
- QCA8K_ATU_AGE_TIME_MASK,
- QCA8K_ATU_AGE_TIME(val));
+ return qca8k_set_ageing_time(priv, msecs);
}
static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
index cafb727f4e8b..9ad016f7201e 100644
--- a/include/linux/dsa/qca8k.h
+++ b/include/linux/dsa/qca8k.h
@@ -553,7 +553,8 @@ int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee);
int qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
/* Common bridge function */
-void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
+void qca8k_port_stp_state_set(struct qca8k_priv *priv, int port, u8 state,
+ bool port_learning, int set_learning);
int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
struct switchdev_brport_flags flags,
struct netlink_ext_ack *extack);
@@ -577,8 +578,8 @@ int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu);
int qca8k_port_max_mtu(struct dsa_switch *ds, int port);
/* Common fast age function */
-void qca8k_port_fast_age(struct dsa_switch *ds, int port);
-int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs);
+void qca8k_port_fast_age(struct qca8k_priv *priv, int port);
+int qca8k_set_ageing_time(struct qca8k_priv *priv, unsigned int msecs);
/* Common FDB function */
int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
@@ -589,7 +590,7 @@ int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db);
-int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
+int qca8k_port_fdb_dump(struct qca8k_priv *priv, int port,
dsa_fdb_dump_cb_t *cb, void *data);
int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
u16 port_mask, u16 vid);
@@ -618,13 +619,12 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
struct dsa_mall_mirror_tc_entry *mirror);
/* Common port VLAN function */
-int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
- bool vlan_filtering,
- struct netlink_ext_ack *extack);
+int qca8k_port_vlan_filtering(struct qca8k_priv *priv, int port,
+ bool vlan_filtering);
int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
bool untagged);
int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid);
-int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
+int qca8k_port_vlan_add(struct qca8k_priv *priv, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack);
int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
--
2.42.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
@ 2023-11-15 11:56 ` kernel test robot
2023-11-16 7:04 ` kernel test robot
` (7 subsequent siblings)
8 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-11-15 11:56 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: oe-kbuild-all, Romain Gantois, Jakub Kicinski, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, devicetree, thomas.petazzoni,
Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-6-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
config: x86_64-randconfig-003-20231115 (https://download.01.org/0day-ci/archive/20231115/202311151907.EpWA1jzi-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231115/202311151907.EpWA1jzi-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311151907.EpWA1jzi-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/net/dsa/qca/qca8k-8xxx.c:1982:5: warning: no previous prototype for 'qca8k_dsa_port_fdb_dump' [-Wmissing-prototypes]
1982 | int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
| ^~~~~~~~~~~~~~~~~~~~~~~
>> drivers/net/dsa/qca/qca8k-8xxx.c:1988:6: warning: no previous prototype for 'qca8k_dsa_port_stp_state_set' [-Wmissing-prototypes]
1988 | void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/net/dsa/qca/qca8k-8xxx.c:1995:6: warning: no previous prototype for 'qca8k_dsa_port_fast_age' [-Wmissing-prototypes]
1995 | void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
| ^~~~~~~~~~~~~~~~~~~~~~~
>> drivers/net/dsa/qca/qca8k-8xxx.c:2000:5: warning: no previous prototype for 'qca8k_dsa_set_ageing_time' [-Wmissing-prototypes]
2000 | int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
| ^~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/net/dsa/qca/qca8k-8xxx.c:2005:5: warning: no previous prototype for 'qca8k_dsa_port_vlan_filtering' [-Wmissing-prototypes]
2005 | int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/net/dsa/qca/qca8k-8xxx.c:2012:5: warning: no previous prototype for 'qca8k_dsa_vlan_add' [-Wmissing-prototypes]
2012 | int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
| ^~~~~~~~~~~~~~~~~~
vim +/qca8k_dsa_port_fdb_dump +1982 drivers/net/dsa/qca/qca8k-8xxx.c
1981
> 1982 int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
1983 dsa_fdb_dump_cb_t *cb, void *data)
1984 {
1985 return qca8k_port_fdb_dump(ds->priv, port, cb, data);
1986 }
1987
> 1988 void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
1989 u8 state)
1990 {
1991 qca8k_port_stp_state_set(ds->priv, port, state,
1992 dsa_to_port(ds, port)->learning, true);
1993 }
1994
> 1995 void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
1996 {
1997 qca8k_port_fast_age(ds->priv, port);
1998 }
1999
> 2000 int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
2001 {
2002 return qca8k_set_ageing_time(ds->priv, msecs);
2003 }
2004
> 2005 int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
2006 bool vlan_filtering,
2007 struct netlink_ext_ack *extack)
2008 {
2009 return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
2010 }
2011
> 2012 int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
2013 const struct switchdev_obj_port_vlan *vlan,
2014 struct netlink_ext_ack *extack)
2015 {
2016 return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
2017 }
2018
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
2023-11-15 11:56 ` kernel test robot
@ 2023-11-16 7:04 ` kernel test robot
2023-11-16 13:23 ` Wojciech Drewek
` (6 subsequent siblings)
8 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-11-16 7:04 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: oe-kbuild-all, Romain Gantois, Jakub Kicinski, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, devicetree, thomas.petazzoni,
Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-6-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
config: arc-randconfig-r112-20231116 (https://download.01.org/0day-ci/archive/20231116/202311161434.PGyQCKWs-lkp@intel.com/config)
compiler: arc-elf-gcc (GCC) 13.2.0
reproduce: (https://download.01.org/0day-ci/archive/20231116/202311161434.PGyQCKWs-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311161434.PGyQCKWs-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/net/dsa/qca/qca8k-8xxx.c:1982:5: sparse: sparse: symbol 'qca8k_dsa_port_fdb_dump' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1988:6: sparse: sparse: symbol 'qca8k_dsa_port_stp_state_set' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1995:6: sparse: sparse: symbol 'qca8k_dsa_port_fast_age' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2000:5: sparse: sparse: symbol 'qca8k_dsa_set_ageing_time' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2005:5: sparse: sparse: symbol 'qca8k_dsa_port_vlan_filtering' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2012:5: sparse: sparse: symbol 'qca8k_dsa_vlan_add' was not declared. Should it be static?
vim +/qca8k_dsa_port_fdb_dump +1982 drivers/net/dsa/qca/qca8k-8xxx.c
1981
> 1982 int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
1983 dsa_fdb_dump_cb_t *cb, void *data)
1984 {
1985 return qca8k_port_fdb_dump(ds->priv, port, cb, data);
1986 }
1987
> 1988 void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
1989 u8 state)
1990 {
1991 qca8k_port_stp_state_set(ds->priv, port, state,
1992 dsa_to_port(ds, port)->learning, true);
1993 }
1994
> 1995 void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
1996 {
1997 qca8k_port_fast_age(ds->priv, port);
1998 }
1999
> 2000 int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
2001 {
2002 return qca8k_set_ageing_time(ds->priv, msecs);
2003 }
2004
> 2005 int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
2006 bool vlan_filtering,
2007 struct netlink_ext_ack *extack)
2008 {
2009 return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
2010 }
2011
> 2012 int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
2013 const struct switchdev_obj_port_vlan *vlan,
2014 struct netlink_ext_ack *extack)
2015 {
2016 return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
2017 }
2018
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
2023-11-15 11:56 ` kernel test robot
2023-11-16 7:04 ` kernel test robot
@ 2023-11-16 13:23 ` Wojciech Drewek
2023-11-17 2:07 ` kernel test robot
` (5 subsequent siblings)
8 siblings, 0 replies; 37+ messages in thread
From: Wojciech Drewek @ 2023-11-16 13:23 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
devicetree, thomas.petazzoni, Andrew Lunn, Florian Fainelli,
Heiner Kallweit, Russell King, linux-arm-kernel, Vladimir Oltean,
Luka Perkov, Robert Marko, Andy Gross, Bjorn Andersson,
Konrad Dybcio
On 14.11.2023 11:55, Romain Gantois wrote:
> The IPQ4019 ESS switch is capable of offloading various bridge features.
> Add netdev and switchdev notifiers to offload bridge uppers, link state
> changes, FDB and MDB accesses and VLANs.
>
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
> ---
> drivers/net/dsa/qca/qca8k-8xxx.c | 49 +-
> drivers/net/dsa/qca/qca8k-common.c | 42 +-
> drivers/net/ethernet/qualcomm/Kconfig | 1 +
> drivers/net/ethernet/qualcomm/ipqess/Makefile | 2 +-
> .../ethernet/qualcomm/ipqess/ipqess_edma.c | 7 +
> .../qualcomm/ipqess/ipqess_notifiers.c | 306 +++++
> .../ethernet/qualcomm/ipqess/ipqess_port.c | 1050 +++++++++++++++--
> .../ethernet/qualcomm/ipqess/ipqess_port.h | 33 +
> .../ethernet/qualcomm/ipqess/ipqess_switch.c | 15 +-
> include/linux/dsa/qca8k.h | 16 +-
> 10 files changed, 1391 insertions(+), 130 deletions(-)
> create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
>
<...>
>
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
> new file mode 100644
> index 000000000000..77f6d79c2ff6
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
> @@ -0,0 +1,306 @@
> +// SPDX-License-Identifier: GPL-2.0 OR ISC
> +/*
> + * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
> + * Based on net/dsa/slave.c
> + */
> +
> +#include <net/switchdev.h>
> +
> +#include <linux/etherdevice.h>
> +#include <linux/if_vlan.h>
> +#include <linux/if_hsr.h>
> +
> +#include "ipqess_notifiers.h"
> +#include "ipqess_port.h"
> +
> +static struct workqueue_struct *ipqess_owq;
> +
> +static bool ipqess_schedule_work(struct work_struct *work)
> +{
> + return queue_work(ipqess_owq, work);
> +}
> +
> +void ipqess_flush_workqueue(void)
> +{
> + flush_workqueue(ipqess_owq);
> +}
Those two functions look like overkill to me
> +
> +/* switchdev */
> +
> +static int ipqess_port_fdb_event(struct net_device *netdev,
> + struct net_device *orig_netdev,
> + unsigned long event, const void *ctx,
> + const struct switchdev_notifier_fdb_info *fdb_info)
> +{
> + struct ipqess_switchdev_event_work *switchdev_work;
> + struct ipqess_port *port = netdev_priv(netdev);
> + bool host_addr = fdb_info->is_local;
> +
> + if (ctx && ctx != port)
> + return 0;
> +
> + if (!port->bridge)
> + return 0;
> +
> + if (switchdev_fdb_is_dynamically_learned(fdb_info) &&
> + ipqess_port_offloads_bridge_port(port, orig_netdev))
> + return 0;
> +
> + /* Also treat FDB entries on foreign interfaces bridged with us as host
> + * addresses.
> + */
> + if (ipqess_port_dev_is_foreign(netdev, orig_netdev))
> + host_addr = true;
> +
> + switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
> + if (!switchdev_work)
> + return -ENOMEM;
> +
> + netdev_dbg(netdev, "%s FDB entry towards %s, addr %pM vid %d%s\n",
> + event == SWITCHDEV_FDB_ADD_TO_DEVICE ? "Adding" : "Deleting",
> + orig_netdev->name, fdb_info->addr, fdb_info->vid,
> + host_addr ? " as host address" : "");
> +
> + INIT_WORK(&switchdev_work->work, ipqess_port_switchdev_event_work);
> + switchdev_work->event = event;
> + switchdev_work->netdev = netdev;
> + switchdev_work->orig_netdev = orig_netdev;
> +
> + ether_addr_copy(switchdev_work->addr, fdb_info->addr);
> + switchdev_work->vid = fdb_info->vid;
> + switchdev_work->host_addr = host_addr;
> +
> + ipqess_schedule_work(&switchdev_work->work);
> +
> + return 0;
> +}
> +
> +/* Called under rcu_read_lock() */
> +static int ipqess_switchdev_event(struct notifier_block *unused,
> + unsigned long event, void *ptr)
> +{
> + struct net_device *netdev = switchdev_notifier_info_to_dev(ptr);
> + int err;
> +
> + switch (event) {
> + case SWITCHDEV_PORT_ATTR_SET:
> + err = switchdev_handle_port_attr_set(netdev, ptr,
> + ipqess_port_recognize_netdev,
> + ipqess_port_attr_set);
> + return notifier_from_errno(err);
> + case SWITCHDEV_FDB_ADD_TO_DEVICE:
> + case SWITCHDEV_FDB_DEL_TO_DEVICE:
> + err = switchdev_handle_fdb_event_to_device(netdev, event, ptr,
> + ipqess_port_recognize_netdev,
> + ipqess_port_dev_is_foreign,
> + ipqess_port_fdb_event);
> + return notifier_from_errno(err);
> + default:
> + return NOTIFY_DONE;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static int ipqess_switchdev_blocking_event(struct notifier_block *unused,
> + unsigned long event, void *ptr)
> +{
> + struct net_device *netdev = switchdev_notifier_info_to_dev(ptr);
> + int err;
> +
> + switch (event) {
> + case SWITCHDEV_PORT_OBJ_ADD:
> + err = switchdev_handle_port_obj_add_foreign(netdev, ptr,
> + ipqess_port_recognize_netdev,
> + ipqess_port_dev_is_foreign,
> + ipqess_port_obj_add);
> + return notifier_from_errno(err);
> + case SWITCHDEV_PORT_OBJ_DEL:
> + err = switchdev_handle_port_obj_del_foreign(netdev, ptr,
> + ipqess_port_recognize_netdev,
> + ipqess_port_dev_is_foreign,
> + ipqess_port_obj_del);
> + return notifier_from_errno(err);
> + case SWITCHDEV_PORT_ATTR_SET:
> + err = switchdev_handle_port_attr_set(netdev, ptr,
> + ipqess_port_recognize_netdev,
> + ipqess_port_attr_set);
> + return notifier_from_errno(err);
> + }
> +
> + return NOTIFY_DONE;
> +}
> +
> +/* netdevice */
> +
> +static int ipqess_port_changeupper(struct net_device *netdev,
> + struct netdev_notifier_changeupper_info *info)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct netlink_ext_ack *extack;
> + int err = NOTIFY_DONE;
> +
> + if (!ipqess_port_recognize_netdev(netdev))
This already done in ipqess_netdevice_event
> + return err;
> +
> + extack = netdev_notifier_info_to_extack(&info->info);
> +
> + if (netif_is_bridge_master(info->upper_dev)) {
> + if (info->linking) {
> + err = ipqess_port_bridge_join(port, info->upper_dev, extack);
> + if (err == -EOPNOTSUPP) {
> + NL_SET_ERR_MSG_WEAK_MOD(extack,
> + "Offloading not supported");
> + err = NOTIFY_DONE;
> + }
> + err = notifier_from_errno(err);
> + } else {
> + ipqess_port_bridge_leave(port, info->upper_dev);
> + err = NOTIFY_OK;
> + }
> + } else if (netif_is_lag_master(info->upper_dev)) {
> + /* LAG offloading is not supported by this driver */
> + NL_SET_ERR_MSG_WEAK_MOD(extack,
> + "Offloading not supported");
> + err = NOTIFY_DONE;
> + } else if (is_hsr_master(info->upper_dev)) {
> + if (info->linking) {
> + NL_SET_ERR_MSG_WEAK_MOD(extack,
> + "Offloading not supported");
> + err = NOTIFY_DONE;
> + } else {
> + err = NOTIFY_OK;
> + }
> + }
> +
> + return err;
> +}
> +
> +static int ipqess_port_prechangeupper(struct net_device *netdev,
> + struct netdev_notifier_changeupper_info *info)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct net_device *brport_dev;
> + int err;
> +
> + /* sanity check */
> + if (is_vlan_dev(info->upper_dev)) {
> + err = ipqess_port_check_8021q_upper(netdev, info);
> + if (notifier_to_errno(err))
> + return err;
> + }
> +
> + /* prechangeupper */
> + if (netif_is_bridge_master(info->upper_dev) && !info->linking)
> + brport_dev = ipqess_port_get_bridged_netdev(port);
> + else
> + return NOTIFY_DONE;
> +
> + if (!brport_dev)
> + return NOTIFY_DONE;
> +
> + switchdev_bridge_port_unoffload(brport_dev, port,
> + &ipqess_switchdev_notifier,
> + &ipqess_switchdev_blocking_notifier);
> +
> + ipqess_flush_workqueue();
> +
> + return NOTIFY_DONE;
> +}
> +
> +static int ipqess_netdevice_event(struct notifier_block *nb,
> + unsigned long event, void *ptr)
> +{
> + struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
> + int err;
> +
> + if (!ipqess_port_recognize_netdev(netdev))
> + return NOTIFY_DONE;
> +
> + switch (event) {
> + case NETDEV_PRECHANGEUPPER: {
> + err = ipqess_port_prechangeupper(netdev, ptr);
> + if (notifier_to_errno(err))
> + return err;
> +
> + break;
> + }
> +
> + case NETDEV_CHANGEUPPER: {
> + err = ipqess_port_changeupper(netdev, ptr);
> + if (notifier_to_errno(err))
> + return err;
> +
> + break;
> + }
> +
> + /* Handling this is only useful for LAG offloading, which this driver
> + * doesn't support
> + */
> + case NETDEV_CHANGELOWERSTATE:
> + return NOTIFY_DONE;
> + case NETDEV_CHANGE:
> + case NETDEV_UP:
> + case NETDEV_GOING_DOWN:
> + default:
> + break;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +struct notifier_block ipqess_switchdev_notifier = {
> + .notifier_call = ipqess_switchdev_event,
> +};
> +
> +struct notifier_block ipqess_switchdev_blocking_notifier = {
> + .notifier_call = ipqess_switchdev_blocking_event,
> +};
> +
> +static struct notifier_block ipqess_nb __read_mostly = {
> + .notifier_call = ipqess_netdevice_event,
> +};
> +
> +int ipqess_notifiers_register(void)
> +{
> + int err;
> +
> + ipqess_owq = alloc_ordered_workqueue("ipqess_ordered",
> + WQ_MEM_RECLAIM);
> + if (!ipqess_owq)
> + return -ENOMEM;
> +
> + err = register_netdevice_notifier(&ipqess_nb);
> + if (err)
> + goto err_netdev_nb;
> +
> + err = register_switchdev_notifier(&ipqess_switchdev_notifier);
> + if (err)
> + goto err_switchdev_nb;
> +
> + err = register_switchdev_blocking_notifier(&ipqess_switchdev_blocking_notifier);
> + if (err)
> + goto err_switchdev_blocking_nb;
> +
> + return 0;
> +
> +err_switchdev_blocking_nb:
> + unregister_switchdev_notifier(&ipqess_switchdev_notifier);
> +err_switchdev_nb:
> + unregister_netdevice_notifier(&ipqess_nb);
> +err_netdev_nb:
> + destroy_workqueue(ipqess_owq);
> +
> + return err;
> +}
> +EXPORT_SYMBOL(ipqess_notifiers_register);
> +
> +void ipqess_notifiers_unregister(void)
> +{
> + unregister_switchdev_blocking_notifier(&ipqess_switchdev_blocking_notifier);
> + unregister_switchdev_notifier(&ipqess_switchdev_notifier);
> + unregister_netdevice_notifier(&ipqess_nb);
> +
> + destroy_workqueue(ipqess_owq);
> +}
> +EXPORT_SYMBOL(ipqess_notifiers_unregister);
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> index 52d7baa7cae0..29420820c3d8 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> @@ -23,50 +23,50 @@ static struct device_type ipqess_port_type = {
> .name = "switch",
> };
>
> +struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port)
> +{
> + if (!port->bridge)
> + return NULL;
> +
> + return port->netdev;
> +}
> +
> /* netdev ops */
>
> +static void ipqess_port_notify_bridge_fdb_flush(const struct ipqess_port *port,
> + u16 vid)
> +{
> + struct net_device *brport_dev = ipqess_port_get_bridged_netdev(port);
> + struct switchdev_notifier_fdb_info info = {
> + .vid = vid,
> + };
> +
> + /* When the port becomes standalone it has already left the bridge.
> + * Don't notify the bridge in that case.
> + */
> + if (!brport_dev)
> + return;
> +
> + call_switchdev_notifiers(SWITCHDEV_FDB_FLUSH_TO_BRIDGE,
> + brport_dev, &info.info, NULL);
> +}
> +
> static void ipqess_port_fast_age(const struct ipqess_port *port)
> {
> struct qca8k_priv *priv = port->sw->priv;
>
> - mutex_lock(&priv->reg_mutex);
> - qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port->index);
> - mutex_unlock(&priv->reg_mutex);
> + qca8k_port_fast_age(priv, port->index);
> +
> + /* Flush all VLANs */
> + ipqess_port_notify_bridge_fdb_flush(port, 0);
> }
>
> static void ipqess_port_stp_state_set(struct ipqess_port *port,
> u8 state)
> {
> struct qca8k_priv *priv = port->sw->priv;
> - u32 stp_state;
> - int err;
>
> - switch (state) {
> - case BR_STATE_DISABLED:
> - stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
> - break;
> - case BR_STATE_BLOCKING:
> - stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
> - break;
> - case BR_STATE_LISTENING:
> - stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
> - break;
> - case BR_STATE_LEARNING:
> - stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
> - break;
> - case BR_STATE_FORWARDING:
> - default:
> - stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
> - break;
> - }
> -
> - err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
> - QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
> -
> - if (err)
> - dev_warn(priv->dev,
> - "failed to set STP state %d for port %d: err %d\n",
> - stp_state, port->index, err);
> + qca8k_port_stp_state_set(priv, port->index, state, false, false);
> }
>
> static void ipqess_port_set_state_now(struct ipqess_port *port,
> @@ -93,7 +93,8 @@ static int ipqess_port_enable_rt(struct ipqess_port *port,
>
> phy_support_asym_pause(phy);
>
> - ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
> + if (!port->bridge)
> + ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
>
> if (port->pl)
> phylink_start(port->pl);
> @@ -108,7 +109,8 @@ static void ipqess_port_disable_rt(struct ipqess_port *port)
> if (port->pl)
> phylink_stop(port->pl);
>
> - ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
> + if (!port->bridge)
> + ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
>
> qca8k_port_set_status(priv, port->index, 0);
> priv->port_enabled_map &= ~BIT(port->index);
> @@ -204,34 +206,9 @@ static int ipqess_port_change_mtu(struct net_device *dev, int new_mtu)
> return 0;
> }
>
> -static int ipqess_port_do_vlan_add(struct qca8k_priv *priv, int port_index,
> - const struct switchdev_obj_port_vlan *vlan,
> - struct netlink_ext_ack *extack)
> +static inline struct net_device *ipqess_port_bridge_dev_get(struct ipqess_port *port)
> {
> - bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
> - bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
> - int ret;
> -
> - ret = qca8k_vlan_add(priv, port_index, vlan->vid, untagged);
> - if (ret) {
> - dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port_index,
> - ret);
> - return ret;
> - }
> -
> - if (pvid) {
> - ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
> - QCA8K_EGREES_VLAN_PORT_MASK(port_index),
> - QCA8K_EGREES_VLAN_PORT(port_index, vlan->vid));
> - if (ret)
> - return ret;
> -
> - ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
> - QCA8K_PORT_VLAN_CVID(vlan->vid) |
> - QCA8K_PORT_VLAN_SVID(vlan->vid));
> - }
> -
> - return ret;
> + return port->bridge ? port->bridge->netdev : NULL;
> }
>
> static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
> @@ -248,7 +225,7 @@ static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
> int ret;
>
> /* User port... */
> - ret = ipqess_port_do_vlan_add(port->sw->priv, port->index, &vlan, &extack);
> + ret = qca8k_port_vlan_add(port->sw->priv, port->index, &vlan, &extack);
> if (ret) {
> if (extack._msg)
> netdev_err(dev, "%s\n", extack._msg);
> @@ -256,7 +233,7 @@ static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
> }
>
> /* And CPU port... */
> - ret = ipqess_port_do_vlan_add(port->sw->priv, 0, &vlan, &extack);
> + ret = qca8k_port_vlan_add(port->sw->priv, 0, &vlan, &extack);
> if (ret) {
> if (extack._msg)
> netdev_err(dev, "CPU port %d: %s\n", 0, extack._msg);
> @@ -340,24 +317,13 @@ ipqess_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
> .cb = cb,
> .idx = *idx,
> };
> - int cnt = QCA8K_NUM_FDB_RECORDS;
> - struct qca8k_fdb _fdb = { 0 };
> - bool is_static;
> int ret = 0;
>
> - mutex_lock(&priv->reg_mutex);
> - while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port->index)) {
> - if (!_fdb.aging)
> - break;
> - is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
> - ret = ipqess_port_fdb_do_dump(_fdb.mac, _fdb.vid, is_static, &dump);
> - if (ret)
> - break;
> - }
> - mutex_unlock(&priv->reg_mutex);
> -
> *idx = dump.idx;
>
> + ret = qca8k_port_fdb_dump(priv, port->index, ipqess_port_fdb_do_dump,
> + &dump);
> +
> return ret;
> }
>
> @@ -374,6 +340,882 @@ static const struct net_device_ops ipqess_port_netdev_ops = {
> .ndo_fdb_dump = ipqess_port_fdb_dump,
> };
>
> +/* Bridge ops */
> +
> +static int ipqess_port_bridge_alloc(struct ipqess_port *port,
> + struct net_device *br,
> + struct netlink_ext_ack *extack)
> +{
> + struct ipqess_bridge *bridge;
> +
> + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL);
> + if (!bridge)
> + return -ENOMEM;
> +
> + refcount_set(&bridge->refcount, 1);
> +
> + bridge->netdev = br;
> +
> + port->bridge = bridge;
> +
> + return 0;
> +}
> +
> +/* Must be called under rcu_read_lock() */
> +static bool ipqess_port_can_apply_vlan_filtering(struct ipqess_port *port,
> + bool vlan_filtering,
> + struct netlink_ext_ack *extack)
> +{
> + int err;
> +
> + /* VLAN awareness was off, so the question is "can we turn it on".
> + * We may have had 8021q uppers, those need to go. Make sure we don't
> + * enter an inconsistent state: deny changing the VLAN awareness state
> + * as long as we have 8021q uppers.
> + */
> + if (vlan_filtering) {
if (!vlan_filtering)
return true;
One indentation level less
> + struct net_device *br = ipqess_port_bridge_dev_get(port);
> + struct net_device *upper_dev, *netdev = port->netdev;
> + struct list_head *iter;
> +
> + netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) {
> + struct bridge_vlan_info br_info;
> + u16 vid;
> +
> + if (!is_vlan_dev(upper_dev))
> + continue;
> +
> + vid = vlan_dev_vlan_id(upper_dev);
> +
> + /* br_vlan_get_info() returns -EINVAL or -ENOENT if the
> + * device, respectively the VID is not found, returning
> + * 0 means success, which is a failure for us here.
> + */
> + err = br_vlan_get_info(br, vid, &br_info);
> + if (err == 0) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Must first remove VLAN uppers having VIDs also present in bridge");
> + return false;
> + }
> + }
> + }
> +
> + /* VLAN filtering is not global so we can just return true here */
> + return true;
> +}
> +
> +static int ipqess_port_restore_vlan(struct net_device *vdev, int vid, void *arg)
> +{
> + __be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
> +
> + return ipqess_port_vlan_rx_add_vid(arg, proto, vid);
> +}
> +
> +static int ipqess_port_clear_vlan(struct net_device *vdev, int vid, void *arg)
> +{
> + __be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
> +
> + return ipqess_port_vlan_rx_kill_vid(arg, proto, vid);
> +}
> +
> +/* Keep the VLAN RX filtering list in sync with the hardware only if VLAN
> + * filtering is enabled.
> + */
> +static int ipqess_port_manage_vlan_filtering(struct net_device *netdev,
> + bool vlan_filtering)
> +{
> + int err;
> +
> + if (vlan_filtering) {
> + netdev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
> +
> + err = vlan_for_each(netdev, ipqess_port_restore_vlan, netdev);
> + if (err) {
> + netdev_err(netdev,
> + "Failed to restore all VLAN's successfully, error %d\n",
> + err);
> + vlan_for_each(netdev, ipqess_port_clear_vlan, netdev);
> + netdev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
> + return err;
> + }
> + } else {
> + err = vlan_for_each(netdev, ipqess_port_clear_vlan, netdev);
> + if (err)
> + return err;
> +
> + netdev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
> + }
> +
> + return 0;
> +}
> +
> +static int ipqess_port_vlan_filtering(struct ipqess_port *port,
> + bool vlan_filtering,
> + struct netlink_ext_ack *extack)
> +{
> + bool old_vlan_filtering = port->vlan_filtering;
> + bool apply;
> + int err;
> +
> + /* We are called from ipqess_port_switchdev_blocking_event(),
> + * which is not under rcu_read_lock(), unlike
> + * ipqess_port_switchdev_event().
> + */
> + rcu_read_lock();
Again, I would move locking inside ipqess_port_can_apply_vlan_filtering
> + apply = ipqess_port_can_apply_vlan_filtering(port, vlan_filtering, extack);
> + rcu_read_unlock();
> + if (!apply)
> + return -EINVAL;
> +
> + if (old_vlan_filtering == vlan_filtering)
> + return 0;
> +
> + err = qca8k_port_vlan_filtering(port->sw->priv, port->index,
> + vlan_filtering);
> +
> + if (err)
> + return err;
> +
> + port->vlan_filtering = vlan_filtering;
> +
> + err = ipqess_port_manage_vlan_filtering(port->netdev,
> + vlan_filtering);
> + if (err)
> + goto restore;
> +
> + return 0;
> +
> +restore:
> + err = qca8k_port_vlan_filtering(port->sw->priv, port->index,
> + old_vlan_filtering);
> + port->vlan_filtering = old_vlan_filtering;
> +
> + return err;
> +}
> +
> +static void ipqess_port_reset_vlan_filtering(struct ipqess_port *port,
> + struct ipqess_bridge *bridge)
> +{
> + struct netlink_ext_ack extack = {0};
> + bool change_vlan_filtering = false;
> + bool vlan_filtering;
> + int err;
> +
> + if (br_vlan_enabled(bridge->netdev)) {
> + change_vlan_filtering = true;
> + vlan_filtering = false;
> + }
> +
> + if (!change_vlan_filtering)
> + return;
> +
> + err = ipqess_port_vlan_filtering(port, vlan_filtering, &extack);
> + if (extack._msg) {
> + dev_err(&port->netdev->dev, "port %d: %s\n", port->index,
> + extack._msg);
> + }
No need for braces
> + if (err && err != -EOPNOTSUPP) {
> + dev_err(&port->netdev->dev,
> + "port %d failed to reset VLAN filtering to %d: %pe\n",
> + port->index, vlan_filtering, ERR_PTR(err));
> + }
Same
> +}
> +
> +static int ipqess_port_ageing_time(struct ipqess_port *port,
> + clock_t ageing_clock)
> +{
> + unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock);
> + unsigned int ageing_time = jiffies_to_msecs(ageing_jiffies);
> +
> + if (ageing_time < IPQESS_SWITCH_AGEING_TIME_MIN ||
> + ageing_time > IPQESS_SWITCH_AGEING_TIME_MAX)
> + return -ERANGE;
> +
> + /* Program the fastest ageing time in case of multiple bridges */
> + ageing_time = ipqess_switch_fastest_ageing_time(port->sw, ageing_time);
> +
> + port->ageing_time = ageing_time;
> + return ipqess_set_ageing_time(port->sw, ageing_time);
> +}
> +
> +static int ipqess_port_switchdev_sync_attrs(struct ipqess_port *port,
> + struct netlink_ext_ack *extack)
> +{
> + struct net_device *brport_dev = ipqess_port_get_bridged_netdev(port);
> + struct net_device *br = ipqess_port_bridge_dev_get(port);
> + int err;
> +
> + ipqess_port_set_state_now(port, br_port_get_stp_state(brport_dev), false);
> +
> + err = ipqess_port_vlan_filtering(port, br_vlan_enabled(br), extack);
> + if (err)
> + return err;
> +
> + err = ipqess_port_ageing_time(port, br_get_ageing_time(br));
> + if (err && err != -EOPNOTSUPP)
> + return err;
> +
> + return 0;
> +}
> +
> +static void ipqess_port_switchdev_unsync_attrs(struct ipqess_port *port,
> + struct ipqess_bridge *bridge)
> +{
> + /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
> + * so allow it to be in BR_STATE_FORWARDING to be kept functional
> + */
> + ipqess_port_set_state_now(port, BR_STATE_FORWARDING, true);
> +
> + ipqess_port_reset_vlan_filtering(port, bridge);
> +
> + /* Ageing time is global to the switch chip, so don't change it
> + * here because we have no good reason (or value) to change it to.
> + */
> +}
> +
> +static inline bool ipqess_port_offloads_bridge(struct ipqess_port *port,
> + const struct ipqess_bridge *bridge)
> +{
> + return ipqess_port_bridge_dev_get(port) == bridge->netdev;
> +}
> +
> +bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
> + const struct net_device *netdev)
> +{
> + return ipqess_port_get_bridged_netdev(port) == netdev;
> +}
> +
> +static inline bool
> +ipqess_port_offloads_bridge_dev(struct ipqess_port *port,
> + const struct net_device *bridge_dev)
> +{
> + /* QCA8K ports connected to a bridge, and event was emitted
> + * for the bridge.
> + */
> + return ipqess_port_bridge_dev_get(port) == bridge_dev;
> +}
> +
> +static void ipqess_port_bridge_destroy(struct ipqess_port *port,
> + const struct net_device *br)
> +{
> + struct ipqess_bridge *bridge = port->bridge;
> +
> + port->bridge = NULL;
> +
> + if (!refcount_dec_and_test(&bridge->refcount))
> + return;
> +
> + kfree(bridge);
> +}
> +
> +int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
> + struct netlink_ext_ack *extack)
> +{
> + struct ipqess_switch *sw = port->sw;
> + struct ipqess_bridge *bridge = NULL;
> + struct qca8k_priv *priv = sw->priv;
> + struct ipqess_port *other_port;
> + struct net_device *brport_dev;
> + int port_id = port->index;
> + int port_mask = 0;
> + int i, err;
> +
> + /* QCA8K doesn't support MST */
> + if (br_mst_enabled(br)) {
> + err = -EOPNOTSUPP;
> + goto out_err;
> + }
> +
> + /* Check if we already registered this bridge with
> + * another switch port
> + */
> + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> + other_port = sw->port_list[i];
> + if (other_port && other_port->bridge &&
> + other_port->bridge->netdev == br)
> + bridge = other_port->bridge;
> + }
> +
> + if (bridge) {
> + refcount_inc(&bridge->refcount);
> + port->bridge = bridge;
> + } else {
> + err = ipqess_port_bridge_alloc(port, br, extack);
> + if (err)
> + goto out_err;
You already have ipqess_port_bridge_destroy, this could be moved to the
ipqess_port_bridge_create,
> + }
> + bridge = port->bridge;
> +
> + for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
> + other_port = sw->port_list[i - 1];
> + if (!other_port || !ipqess_port_offloads_bridge(other_port, bridge))
> + continue;
> + /* Add this port to the portvlan mask of the other ports
> + * in the bridge
> + */
> + err = regmap_set_bits(priv->regmap,
> + QCA8K_PORT_LOOKUP_CTRL(i),
> + BIT(port_id));
> + if (err)
> + goto out_rollback;
> + if (i != port_id)
> + port_mask |= BIT(i);
> + }
> + /* Also add the CPU port */
> + err = regmap_set_bits(priv->regmap,
> + QCA8K_PORT_LOOKUP_CTRL(0),
> + BIT(port_id));
> + port_mask |= BIT(0);
> +
> + /* Add all other ports to this ports portvlan mask */
> + err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
> + QCA8K_PORT_LOOKUP_MEMBER, port_mask);
> + if (err)
> + goto out_rollback;
> +
> + brport_dev = ipqess_port_get_bridged_netdev(port);
> +
> + err = switchdev_bridge_port_offload(brport_dev, port->netdev, port,
> + &ipqess_switchdev_notifier,
> + &ipqess_switchdev_blocking_notifier,
> + false, extack);
> + if (err)
> + goto out_rollback_unbridge;
> +
> + err = ipqess_port_switchdev_sync_attrs(port, extack);
> + if (err)
> + goto out_rollback_unoffload;
> +
> + return 0;
> +
> +out_rollback_unoffload:
> + switchdev_bridge_port_unoffload(brport_dev, port,
> + &ipqess_switchdev_notifier,
> + &ipqess_switchdev_blocking_notifier);
> + ipqess_flush_workqueue();
> +out_rollback_unbridge:
> + for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
> + other_port = sw->port_list[i - 1];
> + if (!other_port ||
> + !ipqess_port_offloads_bridge(other_port, port->bridge))
> + continue;
> + /* Remove this port from the portvlan mask of the other ports
> + * in the bridge
> + */
> + regmap_clear_bits(priv->regmap,
> + QCA8K_PORT_LOOKUP_CTRL(i),
> + BIT(port_id));
> + }
> +
> + /* Set the cpu port to be the only one in the portvlan mask of
> + * this port
> + */
> + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
> + QCA8K_PORT_LOOKUP_MEMBER, BIT(0));
> +out_rollback:
> + ipqess_port_bridge_destroy(port, br);
> +out_err:
> + dev_err(&port->netdev->dev, "Failed to join bridge: errno %d\n", err);
> + return err;
> +}
> +
> +void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br)
> +{
> + struct ipqess_bridge *bridge = port->bridge;
> + struct ipqess_switch *sw = port->sw;
> + struct qca8k_priv *priv = sw->priv;
> + struct ipqess_port *other_port;
> + int port_id = port->index;
> + int i;
> +
> + /* If the port could not be offloaded to begin with, then
> + * there is nothing to do.
> + */
> + if (!bridge)
> + return;
> +
> + for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
> + other_port = sw->port_list[i - 1];
> + if (!other_port || !ipqess_port_offloads_bridge(other_port, bridge))
> + continue;
> + /* Remove this port from the portvlan mask of the other ports
> + * in the bridge
> + */
> + regmap_clear_bits(priv->regmap,
> + QCA8K_PORT_LOOKUP_CTRL(i),
> + BIT(port_id));
> + }
> +
> + /* Set the cpu port to be the only one in the portvlan mask of
> + * this port
> + */
> + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
> + QCA8K_PORT_LOOKUP_MEMBER, BIT(0));
> +
> + ipqess_port_switchdev_unsync_attrs(port, bridge);
> +
> + /* Here the port is already unbridged. Reflect the current configuration. */
> +
> + ipqess_port_bridge_destroy(port, br);
> +}
> +
> +int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
> + const struct switchdev_attr *attr,
> + struct netlink_ext_ack *extack)
> +{
> + struct ipqess_port *port = netdev_priv(dev);
> + int ret;
> +
> + if (ctx && ctx != port)
> + return 0;
> +
> + switch (attr->id) {
> + case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
> + if (!ipqess_port_offloads_bridge_port(port, attr->orig_dev))
> + return -EOPNOTSUPP;
> +
> + ipqess_port_set_state_now(port, attr->u.stp_state, true);
> + return 0;
> + case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
> + if (!ipqess_port_offloads_bridge_dev(port, attr->orig_dev))
> + return -EOPNOTSUPP;
> +
> + ret = ipqess_port_vlan_filtering(port, attr->u.vlan_filtering,
> + extack);
> + break;
> + case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
> + if (!ipqess_port_offloads_bridge_dev(port, attr->orig_dev))
> + return -EOPNOTSUPP;
> +
> + ret = ipqess_port_ageing_time(port, attr->u.ageing_time);
> + break;
> + case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
> + if (!ipqess_port_offloads_bridge_port(port, attr->orig_dev))
> + return -EOPNOTSUPP;
> +
> + return -EINVAL;
> + case SWITCHDEV_ATTR_ID_BRIDGE_MST:
> + case SWITCHDEV_ATTR_ID_PORT_MST_STATE:
> + case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
> + case SWITCHDEV_ATTR_ID_VLAN_MSTI:
> + default:
> + ret = -EOPNOTSUPP;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int ipqess_port_vlan_check_for_8021q_uppers(struct net_device *netdev,
> + const struct switchdev_obj_port_vlan *vlan)
> +{
> + struct net_device *upper_dev;
> + struct list_head *iter;
> +
> + netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) {
> + u16 vid;
> +
> + if (!is_vlan_dev(upper_dev))
> + continue;
> +
> + vid = vlan_dev_vlan_id(upper_dev);
> + if (vid == vlan->vid)
> + return -EBUSY;
EBUSY feels odd to me
> + }
> +
> + return 0;
> +}
> +
> +static int ipqess_port_host_vlan_del(struct net_device *netdev,
> + const struct switchdev_obj *obj)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct net_device *br = ipqess_port_bridge_dev_get(port);
> + struct switchdev_obj_port_vlan *vlan;
> +
> + /* Do nothing if this is a software bridge */
> + if (!port->bridge)
> + return -EOPNOTSUPP;
> +
> + if (br && !br_vlan_enabled(br))
> + return 0;
> +
> + vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> + return qca8k_vlan_del(port->sw->priv, 0, vlan->vid);
> +}
> +
> +static int ipqess_port_vlan_del(struct net_device *netdev,
> + const struct switchdev_obj *obj)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct net_device *br = ipqess_port_bridge_dev_get(port);
> + struct qca8k_priv *priv = port->sw->priv;
> + struct switchdev_obj_port_vlan *vlan;
> + int ret;
> +
> + if (br && !br_vlan_enabled(br))
> + return 0;
> +
> + vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> + ret = qca8k_vlan_del(priv, port->index, vlan->vid);
> +
> + if (ret)
> + dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)\n",
> + port->index, ret);
> +
> + return ret;
> +}
> +
> +static int ipqess_port_host_vlan_add(struct net_device *netdev,
> + const struct switchdev_obj *obj,
> + struct netlink_ext_ack *extack)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct switchdev_obj_port_vlan *vlan;
> + struct net_device *br;
> +
> + br = ipqess_port_bridge_dev_get(port);
> + /* Do nothing is this is a software bridge */
Do nothing IF this is a software bridge
> + if (!port->bridge)
> + return -EOPNOTSUPP;
> +
> + if (br && !br_vlan_enabled(br)) {
> + NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
> + return 0;
> + }
> +
> + vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> + vlan->flags &= ~BRIDGE_VLAN_INFO_PVID;
> +
> + /* Add vid to CPU port */
> + return qca8k_port_vlan_add(port->sw->priv, 0, vlan, extack);
> +}
> +
> +static int ipqess_port_vlan_add(struct net_device *netdev,
> + const struct switchdev_obj *obj,
> + struct netlink_ext_ack *extack)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct net_device *br = ipqess_port_bridge_dev_get(port);
> + struct switchdev_obj_port_vlan *vlan;
> + int err;
> +
> + if (br && !br_vlan_enabled(br)) {
> + NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
> + return 0;
> + }
> +
> + vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> + /* Deny adding a bridge VLAN when there is already an 802.1Q upper with
> + * the same VID.
> + */
> + if (br && br_vlan_enabled(br)) {
> + rcu_read_lock();
I'd move locking inside ipqess_port_vlan_check_for_8021q_uppers. Someone could
use in the future and forget about locks.
> + err = ipqess_port_vlan_check_for_8021q_uppers(netdev, vlan);
> + rcu_read_unlock();
> + if (err) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "Port already has a VLAN upper with this VID");
> + return err;
> + }
> + }
> +
> + err = qca8k_port_vlan_add(port->sw->priv, port->index, vlan, extack);
> + return err;
> +}
> +
> +static int ipqess_port_host_mdb_del(struct ipqess_port *port,
> + const struct switchdev_obj_port_mdb *mdb)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> + const u8 *addr = mdb->addr;
> + u16 vid = mdb->vid;
> +
> + return qca8k_fdb_search_and_del(priv, BIT(0), addr, vid);
> +}
> +
> +static int ipqess_port_host_mdb_add(struct ipqess_port *port,
> + const struct switchdev_obj_port_mdb *mdb)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> + const u8 *addr = mdb->addr;
> + u16 vid = mdb->vid;
> +
> + return qca8k_fdb_search_and_insert(priv, BIT(0), addr, vid,
> + QCA8K_ATU_STATUS_STATIC);
> +}
> +
> +static int ipqess_port_mdb_del(struct ipqess_port *port,
> + const struct switchdev_obj_port_mdb *mdb)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> + const u8 *addr = mdb->addr;
> + u16 vid = mdb->vid;
> +
> + return qca8k_fdb_search_and_del(priv, BIT(port->index), addr, vid);
> +}
> +
> +static int ipqess_port_mdb_add(struct ipqess_port *port,
> + const struct switchdev_obj_port_mdb *mdb)
> +{
> + struct qca8k_priv *priv = port->sw->priv;
> + const u8 *addr = mdb->addr;
> + u16 vid = mdb->vid;
> +
> + return qca8k_fdb_search_and_insert(priv, BIT(port->index), addr, vid,
> + QCA8K_ATU_STATUS_STATIC);
> +}
> +
> +int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
> + const struct switchdev_obj *obj,
> + struct netlink_ext_ack *extack)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + int err;
> +
> + if (ctx && ctx != port)
> + return 0;
> +
> + switch (obj->id) {
> + case SWITCHDEV_OBJ_ID_PORT_MDB:
> + if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> + return -EOPNOTSUPP;
> +
> + err = ipqess_port_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> + break;
> + case SWITCHDEV_OBJ_ID_HOST_MDB:
> + if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
> + return -EOPNOTSUPP;
> +
> + err = ipqess_port_host_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> + break;
> + case SWITCHDEV_OBJ_ID_PORT_VLAN:
> + if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> + err = ipqess_port_vlan_add(netdev, obj, extack);
> + else
> + err = ipqess_port_host_vlan_add(netdev, obj, extack);
> + break;
> + case SWITCHDEV_OBJ_ID_MRP:
> + case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
> + default:
> + err = -EOPNOTSUPP;
> + break;
> + }
> +
> + return err;
> +}
> +
> +int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
> + const struct switchdev_obj *obj)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + int err;
> +
> + if (ctx && ctx != port)
> + return 0;
> +
> + switch (obj->id) {
> + case SWITCHDEV_OBJ_ID_PORT_MDB:
> + if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> + return -EOPNOTSUPP;
> +
> + err = ipqess_port_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> + break;
> + case SWITCHDEV_OBJ_ID_HOST_MDB:
> + if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
> + return -EOPNOTSUPP;
> +
> + err = ipqess_port_host_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> + break;
> + case SWITCHDEV_OBJ_ID_PORT_VLAN:
> + if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> + err = ipqess_port_vlan_del(netdev, obj);
> + else
> + err = ipqess_port_host_vlan_del(netdev, obj);
> + break;
> + case SWITCHDEV_OBJ_ID_MRP:
> + case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
> + default:
> + err = -EOPNOTSUPP;
> + break;
> + }
> +
> + return err;
> +}
> +
> +static int ipqess_cpu_port_fdb_del(struct ipqess_port *port,
> + const unsigned char *addr, u16 vid)
> +{
> + struct ipqess_mac_addr *mac_addr = NULL;
> + struct ipqess_mac_addr *other_mac_addr;
> + struct ipqess_switch *sw = port->sw;
> + int err = 0;
> +
> + mutex_lock(&sw->addr_lists_lock);
> +
> + list_for_each_entry(other_mac_addr, &sw->fdbs, list)
> + if (ether_addr_equal(other_mac_addr->addr, addr) && other_mac_addr->vid == vid)
> + mac_addr = other_mac_addr;
Consider storing the fdbs in rhashtable, it's faster when you want to retrieve one given fdb.
> +
> + if (!mac_addr) {
> + err = -ENOENT;
> + goto out;
> + }
> +
> + if (!refcount_dec_and_test(&mac_addr->refcount))
> + goto out;
> +
> + err = qca8k_fdb_del(sw->priv, addr, BIT(IPQESS_SWITCH_CPU_PORT), vid);
> + if (err) {
> + refcount_set(&mac_addr->refcount, 1);
> + goto out;
> + }
> +
> + list_del(&mac_addr->list);
> + kfree(mac_addr);
> +
> +out:
> + mutex_unlock(&sw->addr_lists_lock);
> +
> + return err;
> +}
> +
> +static int ipqess_cpu_port_fdb_add(struct ipqess_port *port,
> + const unsigned char *addr, u16 vid)
> +{
> + struct ipqess_switch *sw = port->sw;
> + struct ipqess_mac_addr *other_a = NULL;
> + struct ipqess_mac_addr *a = NULL;
> + int err = 0;
> +
> + mutex_lock(&sw->addr_lists_lock);
> +
> + list_for_each_entry(other_a, &sw->fdbs, list)
> + if (ether_addr_equal(other_a->addr, addr) && other_a->vid == vid)
> + a = other_a;
> +
> + if (a) {
> + refcount_inc(&a->refcount);
> + goto out;
> + }
> +
> + a = kzalloc(sizeof(*a), GFP_KERNEL);
> + if (!a) {
> + err = -ENOMEM;
> + goto out;
> + }
> +
> + err = qca8k_port_fdb_insert(port->sw->priv, addr,
> + BIT(IPQESS_SWITCH_CPU_PORT), vid);
> + if (err) {
> + kfree(a);
> + goto out;
> + }
> +
> + ether_addr_copy(a->addr, addr);
> + a->vid = vid;
> + refcount_set(&a->refcount, 1);
> + list_add_tail(&a->list, &sw->fdbs);
> +
> +out:
> + mutex_unlock(&sw->addr_lists_lock);
> +
> + return err;
> +}
> +
> +static void
> +ipqess_fdb_offload_notify(struct ipqess_switchdev_event_work *switchdev_work)
> +{
> + struct switchdev_notifier_fdb_info info = {};
> +
> + info.addr = switchdev_work->addr;
> + info.vid = switchdev_work->vid;
> + info.offloaded = true;
> + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
> + switchdev_work->orig_netdev, &info.info, NULL);
> +}
> +
> +void ipqess_port_switchdev_event_work(struct work_struct *work)
> +{
> + struct ipqess_switchdev_event_work *switchdev_work =
> + container_of(work, struct ipqess_switchdev_event_work, work);
> + struct net_device *netdev = switchdev_work->netdev;
> + const unsigned char *addr = switchdev_work->addr;
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct ipqess_switch *sw = port->sw;
> + struct qca8k_priv *priv = sw->priv;
> + u16 vid = switchdev_work->vid;
> + int err;
> +
> + if (!vid)
> + vid = QCA8K_PORT_VID_DEF;
> +
> + switch (switchdev_work->event) {
> + case SWITCHDEV_FDB_ADD_TO_DEVICE:
> + if (switchdev_work->host_addr)
> + err = ipqess_cpu_port_fdb_add(port, addr, vid);
> + else
> + err = qca8k_port_fdb_insert(priv, addr, BIT(port->index), vid);
> + if (err) {
> + dev_err(&port->netdev->dev,
> + "port %d failed to add %pM vid %d to fdb: %d\n",
> + port->index, addr, vid, err);
> + break;
> + }
> + ipqess_fdb_offload_notify(switchdev_work);
> + break;
> +
> + case SWITCHDEV_FDB_DEL_TO_DEVICE:
> + if (switchdev_work->host_addr)
> + err = ipqess_cpu_port_fdb_del(port, addr, vid);
> + else
> + err = qca8k_fdb_del(priv, addr, BIT(port->index), vid);
> + if (err) {
> + dev_err(&port->netdev->dev,
> + "port %d failed to delete %pM vid %d from fdb: %d\n",
> + port->index, addr, vid, err);
> + }
> +
> + break;
> + }
> +
> + kfree(switchdev_work);
> +}
> +
> +int ipqess_port_check_8021q_upper(struct net_device *netdev,
> + struct netdev_notifier_changeupper_info *info)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct net_device *br = ipqess_port_bridge_dev_get(port);
> + struct bridge_vlan_info br_info;
> + struct netlink_ext_ack *extack;
> + int err = NOTIFY_DONE;
> + u16 vid;
RCT
> +
> + if (!br || !br_vlan_enabled(br))
> + return NOTIFY_DONE;
> +
> + extack = netdev_notifier_info_to_extack(&info->info);
> + vid = vlan_dev_vlan_id(info->upper_dev);
> +
> + /* br_vlan_get_info() returns -EINVAL or -ENOENT if the
> + * device, respectively the VID is not found, returning
> + * 0 means success, which is a failure for us here.
> + */
> + err = br_vlan_get_info(br, vid, &br_info);
> + if (err == 0) {
Could be if (!err)
> + NL_SET_ERR_MSG_MOD(extack,
> + "This VLAN is already configured by the bridge");
> + return notifier_from_errno(-EBUSY);
> + }
> +
> + return NOTIFY_DONE;
> +}
> +
> /* phylink ops */
>
> static void
> @@ -669,6 +1511,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
> port->edma = NULL; /* Assigned during edma initialization */
> port->qid = port->index - 1;
> port->sw = sw;
> + port->bridge = NULL;
>
> of_get_mac_address(port_node, port->mac);
> if (!is_zero_ether_addr(port->mac))
> @@ -756,3 +1599,58 @@ void ipqess_port_unregister(struct ipqess_port *port)
> free_netdev(netdev);
> }
>
> +/* Utilities */
> +
> +/* Returns true if any port of this switch offloads the given net_device */
> +static bool ipqess_switch_offloads_bridge_port(struct ipqess_switch *sw,
> + const struct net_device *netdev)
> +{
> + struct ipqess_port *port;
> + int i;
> +
> + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> + port = sw->port_list[i];
> + if (port && ipqess_port_offloads_bridge_port(port, netdev))
> + return true;
> + }
> +
> + return false;
> +}
> +
> +/* Returns true if any port of this switch offloads the given bridge */
> +static inline bool
> +ipqess_switch_offloads_bridge_dev(struct ipqess_switch *sw,
> + const struct net_device *bridge_dev)
> +{
> + struct ipqess_port *port;
> + int i;
> +
> + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> + port = sw->port_list[i];
> + if (port && ipqess_port_offloads_bridge_dev(port, bridge_dev))
> + return true;
> + }
> +
> + return false;
> +}
> +
> +bool ipqess_port_recognize_netdev(const struct net_device *netdev)
> +{
> + return netdev->netdev_ops == &ipqess_port_netdev_ops;
> +}
> +
> +bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
> + const struct net_device *foreign_netdev)
> +{
> + struct ipqess_port *port = netdev_priv(netdev);
> + struct ipqess_switch *sw = port->sw;
> +
> + if (netif_is_bridge_master(foreign_netdev))
> + return !ipqess_switch_offloads_bridge_dev(sw, foreign_netdev);
> +
> + if (netif_is_bridge_port(foreign_netdev))
> + return !ipqess_switch_offloads_bridge_port(sw, foreign_netdev);
> +
> + /* Everything else is foreign */
> + return true;
> +}
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> index 19d4b5d73625..00f0dff9c39d 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> @@ -9,6 +9,11 @@
> #include "ipqess_edma.h"
> #include "ipqess_switch.h"
>
> +struct ipqess_bridge {
> + struct net_device *netdev;
> + refcount_t refcount;
> +};
> +
> struct ipqess_port {
> u16 index;
> u16 qid;
> @@ -20,6 +25,7 @@ struct ipqess_port {
> struct device_node *dn;
> struct mii_bus *mii_bus;
> struct net_device *netdev;
> + struct ipqess_bridge *bridge;
> struct devlink_port devlink_port;
>
> u8 stp_state;
> @@ -62,4 +68,31 @@ void ipqess_port_unregister(struct ipqess_port *port);
> /* Defined in ipqess_ethtool.c */
> void ipqess_port_set_ethtool_ops(struct net_device *netdev);
>
> +bool ipqess_port_recognize_netdev(const struct net_device *netdev);
> +bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
> + const struct net_device *foreign_netdev);
> +
> +int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
> + struct netlink_ext_ack *extack);
> +void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br);
> +
> +int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
> + const struct switchdev_attr *attr,
> + struct netlink_ext_ack *extack);
> +
> +void ipqess_port_switchdev_event_work(struct work_struct *work);
> +
> +int ipqess_port_check_8021q_upper(struct net_device *netdev,
> + struct netdev_notifier_changeupper_info *info);
> +
> +struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port);
> +
> +int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
> + const struct switchdev_obj *obj,
> + struct netlink_ext_ack *extack);
> +int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
> + const struct switchdev_obj *obj);
> +
> +bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
> + const struct net_device *netdev);
> #endif
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> index 927f834a62bc..d09d0aa8314f 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> @@ -80,21 +80,8 @@ unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
> int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs)
> {
> struct qca8k_priv *priv = sw->priv;
> - unsigned int secs = msecs / 1000;
> - u32 val;
>
> - /* AGE_TIME reg is set in 7s step */
> - val = secs / 7;
> -
> - /* Handle case with 0 as val to NOT disable
> - * learning
> - */
> - if (!val)
> - val = 1;
> -
> - return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL,
> - QCA8K_ATU_AGE_TIME_MASK,
> - QCA8K_ATU_AGE_TIME(val));
> + return qca8k_set_ageing_time(priv, msecs);
> }
>
> static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
> diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
> index cafb727f4e8b..9ad016f7201e 100644
> --- a/include/linux/dsa/qca8k.h
> +++ b/include/linux/dsa/qca8k.h
> @@ -553,7 +553,8 @@ int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee);
> int qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
>
> /* Common bridge function */
> -void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
> +void qca8k_port_stp_state_set(struct qca8k_priv *priv, int port, u8 state,
> + bool port_learning, int set_learning);
> int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
> struct switchdev_brport_flags flags,
> struct netlink_ext_ack *extack);
> @@ -577,8 +578,8 @@ int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu);
> int qca8k_port_max_mtu(struct dsa_switch *ds, int port);
>
> /* Common fast age function */
> -void qca8k_port_fast_age(struct dsa_switch *ds, int port);
> -int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs);
> +void qca8k_port_fast_age(struct qca8k_priv *priv, int port);
> +int qca8k_set_ageing_time(struct qca8k_priv *priv, unsigned int msecs);
>
> /* Common FDB function */
> int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
> @@ -589,7 +590,7 @@ int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
> int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
> const unsigned char *addr, u16 vid,
> struct dsa_db db);
> -int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
> +int qca8k_port_fdb_dump(struct qca8k_priv *priv, int port,
> dsa_fdb_dump_cb_t *cb, void *data);
> int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
> u16 port_mask, u16 vid);
> @@ -618,13 +619,12 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
> struct dsa_mall_mirror_tc_entry *mirror);
>
> /* Common port VLAN function */
> -int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
> - bool vlan_filtering,
> - struct netlink_ext_ack *extack);
> +int qca8k_port_vlan_filtering(struct qca8k_priv *priv, int port,
> + bool vlan_filtering);
> int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
> bool untagged);
> int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid);
> -int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
> +int qca8k_port_vlan_add(struct qca8k_priv *priv, int port,
> const struct switchdev_obj_port_vlan *vlan,
> struct netlink_ext_ack *extack);
> int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
` (2 preceding siblings ...)
2023-11-16 13:23 ` Wojciech Drewek
@ 2023-11-17 2:07 ` kernel test robot
2023-11-21 14:04 ` kernel test robot
` (4 subsequent siblings)
8 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-11-17 2:07 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all,
Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-6-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
config: i386-kismet-CONFIG_NET_SWITCHDEV-CONFIG_QCOM_IPQ4019_ESS-0-0 (https://download.01.org/0day-ci/archive/20231117/202311170937.Pyml72i2-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20231117/202311170937.Pyml72i2-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311170937.Pyml72i2-lkp@intel.com/
kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for NET_SWITCHDEV when selected by QCOM_IPQ4019_ESS
WARNING: unmet direct dependencies detected for NET_DSA
Depends on [n]: NET [=y] && (BRIDGE [=n] || BRIDGE [=n]=n) && (HSR [=n] || HSR [=n]=n) && INET [=n] && NETDEVICES [=y]
Selected by [y]:
- QCOM_IPQ4019_ESS [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_QUALCOMM [=y] && (OF [=y] && ARCH_QCOM || COMPILE_TEST [=y])
WARNING: unmet direct dependencies detected for NET_SWITCHDEV
Depends on [n]: NET [=y] && INET [=n]
Selected by [y]:
- QCOM_IPQ4019_ESS [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_QUALCOMM [=y] && (OF [=y] && ARCH_QCOM || COMPILE_TEST [=y])
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
` (3 preceding siblings ...)
2023-11-17 2:07 ` kernel test robot
@ 2023-11-21 14:04 ` kernel test robot
2023-12-04 16:26 ` kernel test robot
` (3 subsequent siblings)
8 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-11-21 14:04 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: llvm, oe-kbuild-all, Romain Gantois, Jakub Kicinski, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, devicetree, thomas.petazzoni,
Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-6-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
config: arm64-allyesconfig (https://download.01.org/0day-ci/archive/20231121/202311212153.jL7dVRss-lkp@intel.com/config)
compiler: clang version 17.0.0 (https://github.com/llvm/llvm-project.git 4a5ac14ee968ff0ad5d2cc1ffa0299048db4c88a)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231121/202311212153.jL7dVRss-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311212153.jL7dVRss-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/net/dsa/qca/qca8k-8xxx.c:1982:5: warning: no previous prototype for function 'qca8k_dsa_port_fdb_dump' [-Wmissing-prototypes]
1982 | int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
| ^
drivers/net/dsa/qca/qca8k-8xxx.c:1982:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
1982 | int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
| ^
| static
>> drivers/net/dsa/qca/qca8k-8xxx.c:1988:6: warning: no previous prototype for function 'qca8k_dsa_port_stp_state_set' [-Wmissing-prototypes]
1988 | void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
| ^
drivers/net/dsa/qca/qca8k-8xxx.c:1988:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
1988 | void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
| ^
| static
>> drivers/net/dsa/qca/qca8k-8xxx.c:1995:6: warning: no previous prototype for function 'qca8k_dsa_port_fast_age' [-Wmissing-prototypes]
1995 | void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
| ^
drivers/net/dsa/qca/qca8k-8xxx.c:1995:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
1995 | void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
| ^
| static
>> drivers/net/dsa/qca/qca8k-8xxx.c:2000:5: warning: no previous prototype for function 'qca8k_dsa_set_ageing_time' [-Wmissing-prototypes]
2000 | int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
| ^
drivers/net/dsa/qca/qca8k-8xxx.c:2000:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
2000 | int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
| ^
| static
>> drivers/net/dsa/qca/qca8k-8xxx.c:2005:5: warning: no previous prototype for function 'qca8k_dsa_port_vlan_filtering' [-Wmissing-prototypes]
2005 | int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
| ^
drivers/net/dsa/qca/qca8k-8xxx.c:2005:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
2005 | int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
| ^
| static
>> drivers/net/dsa/qca/qca8k-8xxx.c:2012:5: warning: no previous prototype for function 'qca8k_dsa_vlan_add' [-Wmissing-prototypes]
2012 | int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
| ^
drivers/net/dsa/qca/qca8k-8xxx.c:2012:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
2012 | int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
| ^
| static
6 warnings generated.
vim +/qca8k_dsa_port_fdb_dump +1982 drivers/net/dsa/qca/qca8k-8xxx.c
1981
> 1982 int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
1983 dsa_fdb_dump_cb_t *cb, void *data)
1984 {
1985 return qca8k_port_fdb_dump(ds->priv, port, cb, data);
1986 }
1987
> 1988 void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
1989 u8 state)
1990 {
1991 qca8k_port_stp_state_set(ds->priv, port, state,
1992 dsa_to_port(ds, port)->learning, true);
1993 }
1994
> 1995 void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
1996 {
1997 qca8k_port_fast_age(ds->priv, port);
1998 }
1999
> 2000 int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
2001 {
2002 return qca8k_set_ageing_time(ds->priv, msecs);
2003 }
2004
> 2005 int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
2006 bool vlan_filtering,
2007 struct netlink_ext_ack *extack)
2008 {
2009 return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
2010 }
2011
> 2012 int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
2013 const struct switchdev_obj_port_vlan *vlan,
2014 struct netlink_ext_ack *extack)
2015 {
2016 return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
2017 }
2018
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
` (4 preceding siblings ...)
2023-11-21 14:04 ` kernel test robot
@ 2023-12-04 16:26 ` kernel test robot
2023-12-07 19:06 ` kernel test robot
` (2 subsequent siblings)
8 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-12-04 16:26 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: oe-kbuild-all, Romain Gantois, Jakub Kicinski, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, devicetree, thomas.petazzoni,
Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-6-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
config: arc-randconfig-r112-20231116 (https://download.01.org/0day-ci/archive/20231204/202312042338.JK8rFU7w-lkp@intel.com/config)
compiler: arc-elf-gcc (GCC) 13.2.0
reproduce: (https://download.01.org/0day-ci/archive/20231204/202312042338.JK8rFU7w-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202312042338.JK8rFU7w-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/net/dsa/qca/qca8k-8xxx.c:1982:5: sparse: sparse: symbol 'qca8k_dsa_port_fdb_dump' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1988:6: sparse: sparse: symbol 'qca8k_dsa_port_stp_state_set' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1995:6: sparse: sparse: symbol 'qca8k_dsa_port_fast_age' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2000:5: sparse: sparse: symbol 'qca8k_dsa_set_ageing_time' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2005:5: sparse: sparse: symbol 'qca8k_dsa_port_vlan_filtering' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2012:5: sparse: sparse: symbol 'qca8k_dsa_vlan_add' was not declared. Should it be static?
vim +/qca8k_dsa_port_fdb_dump +1982 drivers/net/dsa/qca/qca8k-8xxx.c
1981
> 1982 int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
1983 dsa_fdb_dump_cb_t *cb, void *data)
1984 {
1985 return qca8k_port_fdb_dump(ds->priv, port, cb, data);
1986 }
1987
> 1988 void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
1989 u8 state)
1990 {
1991 qca8k_port_stp_state_set(ds->priv, port, state,
1992 dsa_to_port(ds, port)->learning, true);
1993 }
1994
> 1995 void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
1996 {
1997 qca8k_port_fast_age(ds->priv, port);
1998 }
1999
> 2000 int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
2001 {
2002 return qca8k_set_ageing_time(ds->priv, msecs);
2003 }
2004
> 2005 int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
2006 bool vlan_filtering,
2007 struct netlink_ext_ack *extack)
2008 {
2009 return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
2010 }
2011
> 2012 int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
2013 const struct switchdev_obj_port_vlan *vlan,
2014 struct netlink_ext_ack *extack)
2015 {
2016 return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
2017 }
2018
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
` (5 preceding siblings ...)
2023-12-04 16:26 ` kernel test robot
@ 2023-12-07 19:06 ` kernel test robot
2023-12-11 20:16 ` kernel test robot
2023-12-15 18:39 ` kernel test robot
8 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-12-07 19:06 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: oe-kbuild-all, Romain Gantois, Jakub Kicinski, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, devicetree, thomas.petazzoni,
Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-6-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
config: arc-randconfig-r112-20231116 (https://download.01.org/0day-ci/archive/20231208/202312080233.sRrkY9Q5-lkp@intel.com/config)
compiler: arc-elf-gcc (GCC) 13.2.0
reproduce: (https://download.01.org/0day-ci/archive/20231208/202312080233.sRrkY9Q5-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202312080233.sRrkY9Q5-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/net/dsa/qca/qca8k-8xxx.c:1982:5: sparse: sparse: symbol 'qca8k_dsa_port_fdb_dump' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1988:6: sparse: sparse: symbol 'qca8k_dsa_port_stp_state_set' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1995:6: sparse: sparse: symbol 'qca8k_dsa_port_fast_age' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2000:5: sparse: sparse: symbol 'qca8k_dsa_set_ageing_time' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2005:5: sparse: sparse: symbol 'qca8k_dsa_port_vlan_filtering' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2012:5: sparse: sparse: symbol 'qca8k_dsa_vlan_add' was not declared. Should it be static?
vim +/qca8k_dsa_port_fdb_dump +1982 drivers/net/dsa/qca/qca8k-8xxx.c
1981
> 1982 int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
1983 dsa_fdb_dump_cb_t *cb, void *data)
1984 {
1985 return qca8k_port_fdb_dump(ds->priv, port, cb, data);
1986 }
1987
> 1988 void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
1989 u8 state)
1990 {
1991 qca8k_port_stp_state_set(ds->priv, port, state,
1992 dsa_to_port(ds, port)->learning, true);
1993 }
1994
> 1995 void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
1996 {
1997 qca8k_port_fast_age(ds->priv, port);
1998 }
1999
> 2000 int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
2001 {
2002 return qca8k_set_ageing_time(ds->priv, msecs);
2003 }
2004
> 2005 int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
2006 bool vlan_filtering,
2007 struct netlink_ext_ack *extack)
2008 {
2009 return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
2010 }
2011
> 2012 int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
2013 const struct switchdev_obj_port_vlan *vlan,
2014 struct netlink_ext_ack *extack)
2015 {
2016 return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
2017 }
2018
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
` (6 preceding siblings ...)
2023-12-07 19:06 ` kernel test robot
@ 2023-12-11 20:16 ` kernel test robot
2023-12-15 18:39 ` kernel test robot
8 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-12-11 20:16 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: oe-kbuild-all, Romain Gantois, Jakub Kicinski, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, devicetree, thomas.petazzoni,
Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-6-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
config: arc-randconfig-r112-20231116 (https://download.01.org/0day-ci/archive/20231212/202312120428.xmRLjyHQ-lkp@intel.com/config)
compiler: arc-elf-gcc (GCC) 13.2.0
reproduce: (https://download.01.org/0day-ci/archive/20231212/202312120428.xmRLjyHQ-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202312120428.xmRLjyHQ-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/net/dsa/qca/qca8k-8xxx.c:1982:5: sparse: sparse: symbol 'qca8k_dsa_port_fdb_dump' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1988:6: sparse: sparse: symbol 'qca8k_dsa_port_stp_state_set' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1995:6: sparse: sparse: symbol 'qca8k_dsa_port_fast_age' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2000:5: sparse: sparse: symbol 'qca8k_dsa_set_ageing_time' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2005:5: sparse: sparse: symbol 'qca8k_dsa_port_vlan_filtering' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2012:5: sparse: sparse: symbol 'qca8k_dsa_vlan_add' was not declared. Should it be static?
vim +/qca8k_dsa_port_fdb_dump +1982 drivers/net/dsa/qca/qca8k-8xxx.c
1981
> 1982 int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
1983 dsa_fdb_dump_cb_t *cb, void *data)
1984 {
1985 return qca8k_port_fdb_dump(ds->priv, port, cb, data);
1986 }
1987
> 1988 void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
1989 u8 state)
1990 {
1991 qca8k_port_stp_state_set(ds->priv, port, state,
1992 dsa_to_port(ds, port)->learning, true);
1993 }
1994
> 1995 void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
1996 {
1997 qca8k_port_fast_age(ds->priv, port);
1998 }
1999
> 2000 int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
2001 {
2002 return qca8k_set_ageing_time(ds->priv, msecs);
2003 }
2004
> 2005 int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
2006 bool vlan_filtering,
2007 struct netlink_ext_ack *extack)
2008 {
2009 return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
2010 }
2011
> 2012 int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
2013 const struct switchdev_obj_port_vlan *vlan,
2014 struct netlink_ext_ack *extack)
2015 {
2016 return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
2017 }
2018
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
` (7 preceding siblings ...)
2023-12-11 20:16 ` kernel test robot
@ 2023-12-15 18:39 ` kernel test robot
8 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2023-12-15 18:39 UTC (permalink / raw)
To: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski
Cc: oe-kbuild-all, Romain Gantois, Jakub Kicinski, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, devicetree, thomas.petazzoni,
Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
Hi Romain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Romain-Gantois/dt-bindings-net-Introduce-the-Qualcomm-IPQESS-Ethernet-switch/20231114-185953
base: net-next/main
patch link: https://lore.kernel.org/r/20231114105600.1012056-6-romain.gantois%40bootlin.com
patch subject: [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
config: arc-randconfig-r112-20231116 (https://download.01.org/0day-ci/archive/20231216/202312160211.TqddYea8-lkp@intel.com/config)
compiler: arc-elf-gcc (GCC) 13.2.0
reproduce: (https://download.01.org/0day-ci/archive/20231216/202312160211.TqddYea8-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202312160211.TqddYea8-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/net/dsa/qca/qca8k-8xxx.c:1982:5: sparse: sparse: symbol 'qca8k_dsa_port_fdb_dump' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1988:6: sparse: sparse: symbol 'qca8k_dsa_port_stp_state_set' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:1995:6: sparse: sparse: symbol 'qca8k_dsa_port_fast_age' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2000:5: sparse: sparse: symbol 'qca8k_dsa_set_ageing_time' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2005:5: sparse: sparse: symbol 'qca8k_dsa_port_vlan_filtering' was not declared. Should it be static?
>> drivers/net/dsa/qca/qca8k-8xxx.c:2012:5: sparse: sparse: symbol 'qca8k_dsa_vlan_add' was not declared. Should it be static?
vim +/qca8k_dsa_port_fdb_dump +1982 drivers/net/dsa/qca/qca8k-8xxx.c
1981
> 1982 int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
1983 dsa_fdb_dump_cb_t *cb, void *data)
1984 {
1985 return qca8k_port_fdb_dump(ds->priv, port, cb, data);
1986 }
1987
> 1988 void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
1989 u8 state)
1990 {
1991 qca8k_port_stp_state_set(ds->priv, port, state,
1992 dsa_to_port(ds, port)->learning, true);
1993 }
1994
> 1995 void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
1996 {
1997 qca8k_port_fast_age(ds->priv, port);
1998 }
1999
> 2000 int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
2001 {
2002 return qca8k_set_ageing_time(ds->priv, msecs);
2003 }
2004
> 2005 int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
2006 bool vlan_filtering,
2007 struct netlink_ext_ack *extack)
2008 {
2009 return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
2010 }
2011
> 2012 int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
2013 const struct switchdev_obj_port_vlan *vlan,
2014 struct netlink_ext_ack *extack)
2015 {
2016 return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
2017 }
2018
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
` (4 preceding siblings ...)
2023-11-14 10:55 ` [PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
@ 2023-11-14 10:55 ` Romain Gantois
2023-11-14 19:14 ` Andrew Lunn
2023-11-14 19:20 ` Andrew Lunn
2023-11-14 10:55 ` [PATCH net-next v3 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver Romain Gantois
` (2 subsequent siblings)
8 siblings, 2 replies; 37+ messages in thread
From: Romain Gantois @ 2023-11-14 10:55 UTC (permalink / raw)
To: davem, Rob Herring, Krzysztof Kozlowski
Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
The IPQESS integrated Ethernet switch found in the IPQ4019 SoC requires
calibration of the PHY link when its ports are brought up. This calibration
procedure requires knowledge of precise timings and vendor-specific
registers on both the PHY and MAC side.
The existing PHY abstraction layer does not allow coordinating this kind of
calibration operation between MAC drivers and PHY drivers. As a
consequence, PHY-specific calibration information has to be included in
Ethernet drivers, since it has to schedule the entire calibration procedure
on it's own.
Add two callbacks that extend the PHY abstraction layer to allow MAC
drivers to start and stop PHY calibration runs in a PHY-model-independent
manner.
Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
include/linux/phy.h | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 3cc52826f18e..b1092b2ecee3 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -1142,6 +1142,13 @@ struct phy_driver {
int (*led_hw_control_get)(struct phy_device *dev, u8 index,
unsigned long *rules);
+ /* @calibration_start: Start calibrating the MAC-to-PHY link. */
+ int (*calibration_start)(struct phy_device *dev);
+
+ /* @calibration_start: Finalize MAC-to-PHY link calibration
+ * and run tests. Returns 0 if the calibration tests are successful.
+ */
+ int (*calibration_stop)(struct phy_device *dev);
};
#define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
struct phy_driver, mdiodrv)
@@ -1770,6 +1777,27 @@ int phy_start_cable_test_tdr(struct phy_device *phydev,
}
#endif
+static inline
+int phy_start_calibration(struct phy_device *phydev)
+{
+ if (!(phydev->drv &&
+ phydev->drv->calibration_start &&
+ phydev->drv->calibration_stop))
+ return -EOPNOTSUPP;
+
+ return phydev->drv->calibration_start(phydev);
+}
+
+static inline
+int phy_stop_calibration(struct phy_device *phydev)
+{
+ if (!(phydev->drv &&
+ phydev->drv->calibration_stop))
+ return -EOPNOTSUPP;
+
+ return phydev->drv->calibration_stop(phydev);
+}
+
static inline void phy_device_reset(struct phy_device *phydev, int value)
{
mdio_device_reset(&phydev->mdio, value);
--
2.42.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver
2023-11-14 10:55 ` [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver Romain Gantois
@ 2023-11-14 19:14 ` Andrew Lunn
2023-11-15 15:31 ` Romain Gantois
2023-11-14 19:20 ` Andrew Lunn
1 sibling, 1 reply; 37+ messages in thread
From: Andrew Lunn @ 2023-11-14 19:14 UTC (permalink / raw)
To: Romain Gantois
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
> +static inline
> +int phy_start_calibration(struct phy_device *phydev)
> +{
> + if (!(phydev->drv &&
> + phydev->drv->calibration_start &&
> + phydev->drv->calibration_stop))
> + return -EOPNOTSUPP;
> +
> + return phydev->drv->calibration_start(phydev);
> +}
> +
> +static inline
> +int phy_stop_calibration(struct phy_device *phydev)
> +{
> + if (!(phydev->drv &&
> + phydev->drv->calibration_stop))
> + return -EOPNOTSUPP;
> +
> + return phydev->drv->calibration_stop(phydev);
> +}
> +
What is the locking model?
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver
2023-11-14 19:14 ` Andrew Lunn
@ 2023-11-15 15:31 ` Romain Gantois
2023-11-15 16:12 ` Andrew Lunn
0 siblings, 1 reply; 37+ messages in thread
From: Romain Gantois @ 2023-11-15 15:31 UTC (permalink / raw)
To: Andrew Lunn
Cc: Romain Gantois, davem, Rob Herring, Krzysztof Kozlowski,
Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev, linux-kernel,
devicetree, thomas.petazzoni, Florian Fainelli, Heiner Kallweit,
Russell King, linux-arm-kernel, Vladimir Oltean, Luka Perkov,
Robert Marko, Andy Gross, Bjorn Andersson, Konrad Dybcio
On Tue, 14 Nov 2023, Andrew Lunn wrote:
> > +static inline
> > +int phy_start_calibration(struct phy_device *phydev)
> > +{
> > + if (!(phydev->drv &&
> > + phydev->drv->calibration_start &&
> > + phydev->drv->calibration_stop))
> > + return -EOPNOTSUPP;
> > +
> > + return phydev->drv->calibration_start(phydev);
> > +}
> > +
> > +static inline
> > +int phy_stop_calibration(struct phy_device *phydev)
> > +{
> > + if (!(phydev->drv &&
> > + phydev->drv->calibration_stop))
> > + return -EOPNOTSUPP;
> > +
> > + return phydev->drv->calibration_stop(phydev);
> > +}
> > +
>
> What is the locking model?
>
> Andrew
>
This driver currently uses an atomic flag to make sure that the calibration
doesn't run twice. It doesn't acquire any locks before calling
phy_start_calibration(), which is a mistake.
I think a good locking model for this would be similar to the one used for
phy_cable_test. The phy_start_calibration() and phy_stop_calibration() wrappers
would acquire a lock on the PHY device and then test phydev->state, to check for
an ongoing calibration. A new enum member such as PHY_CALIB could be defined for
this purpose. The lock would be released by the phylib wrapper once the
phy_driver callback returns.
The problem with this is that one calibration run can access multiple
phy_device instances at the same time, e.g. if a switch is linked to a multiport
PHY via a PSGMII link.
So acquiring a lock on a single phy device isn't enough. Ideally, these
calls could somehow acquire one lock on all the hardware resources of a
multiport PHY simultaneously. From what I've seen, there is no standard kernel
interface that allows MAC drivers to know about link-sharing between phy
devices. I'll have to do more research on this but if you know of an existing
interface that I can use for this, please tell me.
Best,
--
Romain Gantois, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver
2023-11-15 15:31 ` Romain Gantois
@ 2023-11-15 16:12 ` Andrew Lunn
0 siblings, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2023-11-15 16:12 UTC (permalink / raw)
To: Romain Gantois
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
On Wed, Nov 15, 2023 at 04:31:07PM +0100, Romain Gantois wrote:
> On Tue, 14 Nov 2023, Andrew Lunn wrote:
>
> > > +static inline
> > > +int phy_start_calibration(struct phy_device *phydev)
> > > +{
> > > + if (!(phydev->drv &&
> > > + phydev->drv->calibration_start &&
> > > + phydev->drv->calibration_stop))
> > > + return -EOPNOTSUPP;
> > > +
> > > + return phydev->drv->calibration_start(phydev);
> > > +}
> > > +
> > > +static inline
> > > +int phy_stop_calibration(struct phy_device *phydev)
> > > +{
> > > + if (!(phydev->drv &&
> > > + phydev->drv->calibration_stop))
> > > + return -EOPNOTSUPP;
> > > +
> > > + return phydev->drv->calibration_stop(phydev);
> > > +}
> > > +
> >
> > What is the locking model?
> >
> > Andrew
> >
> This driver currently uses an atomic flag to make sure that the calibration
> doesn't run twice. It doesn't acquire any locks before calling
> phy_start_calibration(), which is a mistake.
>
> I think a good locking model for this would be similar to the one used for
> phy_cable_test. The phy_start_calibration() and phy_stop_calibration() wrappers
> would acquire a lock on the PHY device and then test phydev->state, to check for
> an ongoing calibration. A new enum member such as PHY_CALIB could be defined for
> this purpose. The lock would be released by the phylib wrapper once the
> phy_driver callback returns.
>
> The problem with this is that one calibration run can access multiple
> phy_device instances at the same time, e.g. if a switch is linked to a multiport
> PHY via a PSGMII link.
>
> So acquiring a lock on a single phy device isn't enough. Ideally, these
> calls could somehow acquire one lock on all the hardware resources of a
> multiport PHY simultaneously. From what I've seen, there is no standard kernel
> interface that allows MAC drivers to know about link-sharing between phy
> devices. I'll have to do more research on this but if you know of an existing
> interface that I can use for this, please tell me.
Lets get the switch parts merged first, then we can think about this
calibration problem. I need a better understanding of the requirements
before i can suggest something.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver
2023-11-14 10:55 ` [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver Romain Gantois
2023-11-14 19:14 ` Andrew Lunn
@ 2023-11-14 19:20 ` Andrew Lunn
1 sibling, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2023-11-14 19:20 UTC (permalink / raw)
To: Romain Gantois
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
On Tue, Nov 14, 2023 at 11:55:56AM +0100, Romain Gantois wrote:
> The IPQESS integrated Ethernet switch found in the IPQ4019 SoC requires
> calibration of the PHY link when its ports are brought up. This calibration
> procedure requires knowledge of precise timings and vendor-specific
> registers on both the PHY and MAC side.
>
> The existing PHY abstraction layer does not allow coordinating this kind of
> calibration operation between MAC drivers and PHY drivers. As a
> consequence, PHY-specific calibration information has to be included in
> Ethernet drivers, since it has to schedule the entire calibration procedure
> on it's own.
>
> Add two callbacks that extend the PHY abstraction layer to allow MAC
> drivers to start and stop PHY calibration runs in a PHY-model-independent
> manner.
When adding new APIs, you need to add an example of both sides of
it. We can then decide if the API makes sense. I don't see a PHY
driver implementing this API.
Andrew
---
pw-bot: cr
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next v3 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
` (5 preceding siblings ...)
2023-11-14 10:55 ` [PATCH net-next v3 6/8] net: phy: add calibration callbacks to phy_driver Romain Gantois
@ 2023-11-14 10:55 ` Romain Gantois
2023-11-14 10:55 ` [PATCH net-next v3 8/8] ARM: dts: qcom: ipq4019: Add description for the IPQ4019 ESS EDMA and switch Romain Gantois
2023-11-14 18:12 ` [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Andrew Lunn
8 siblings, 0 replies; 37+ messages in thread
From: Romain Gantois @ 2023-11-14 10:55 UTC (permalink / raw)
To: davem, Rob Herring, Krzysztof Kozlowski
Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
The IPQ4019 Ethernet Switch Subsystem uses a PSGMII link to communicate
with a QCA8075 5-port PHY. This 1G link requires calibration before it can
be used reliably.
This commit introduces a calibration procedure followed by thourough
testing of the link between each switch port and its corresponding PHY
port.
Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
drivers/net/ethernet/qualcomm/ipqess/Makefile | 2 +-
.../ethernet/qualcomm/ipqess/ipqess_calib.c | 156 ++++++++++++++++++
.../ethernet/qualcomm/ipqess/ipqess_port.c | 30 ++++
.../ethernet/qualcomm/ipqess/ipqess_port.h | 4 +
include/linux/dsa/qca8k.h | 1 +
5 files changed, 192 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c
diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
index b12142bbc7e5..5e755af579ae 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/Makefile
+++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
@@ -5,4 +5,4 @@
obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
-ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o ipqess_notifiers.o
+ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o ipqess_notifiers.o ipqess_calib.o
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c
new file mode 100644
index 000000000000..da9d492ca7eb
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Calibration procedure for the IPQ4019 PSGMII link
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012, 2020-2021 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016 John Crispin <john@phrozen.org>
+ * Copyright (c) 2022 Robert Marko <robert.marko@sartura.hr>
+ * Copyright (c) 2023 Romain Gantois <romain.gantois@bootlin.com>
+ */
+
+#include <linux/dsa/qca8k.h>
+#include <linux/phylink.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
+#include <linux/regmap.h>
+
+#include "ipqess_port.h"
+#include "ipqess_switch.h"
+
+/* Nonstandard MII registers for the psgmii
+ * device on the IPQ4019 MDIO bus.
+ */
+
+#define PSGMII_RSTCTRL 0x0 /* Reset control register */
+#define PSGMII_RSTCTRL_RST BIT(6)
+#define PSGMII_RSTCTRL_RX20 BIT(2) /* Fix/release RX 20 bit */
+
+#define PSGMII_CDRCTRL 0x1a /* Clock and data recovery control register */
+#define PSGMII_CDRCTRL_RELEASE BIT(12)
+
+/* Retry policy */
+
+#define PSGMII_CALIB_RETRIES 50
+#define PSGMII_CALIB_RETRIES_BURST 5
+#define PSGMII_CALIB_RETRY_DELAY 100
+
+/* PSGMII PHY specific definitions */
+#define PSGMII_VCO_CALIB_INTERVAL 1000000
+#define PSGMII_VCO_CALIB_TIMEOUT 10000
+
+static void ipqess_port_unprep_test(struct ipqess_port *port)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+ /* disable loopback on switch port */
+ qca8k_clear_bits(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+ QCA8K_PORT_LOOKUP_LOOPBACK_EN);
+
+ qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), 0);
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+ QCA8K_PORT_LOOKUP_STATE_DISABLED,
+ QCA8K_PORT_LOOKUP_STATE_DISABLED);
+}
+
+static void ipqess_port_prep_test(struct ipqess_port *port)
+{
+ struct qca8k_priv *priv = port->sw->priv;
+
+ qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index),
+ QCA8K_PORT_STATUS_SPEED_1000
+ | QCA8K_PORT_STATUS_TXMAC
+ | QCA8K_PORT_STATUS_RXMAC
+ | QCA8K_PORT_STATUS_DUPLEX);
+
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+ QCA8K_PORT_LOOKUP_STATE_FORWARD,
+ QCA8K_PORT_LOOKUP_STATE_FORWARD);
+
+ /* put switch port in loopback */
+ qca8k_set_bits(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+ QCA8K_PORT_LOOKUP_LOOPBACK_EN);
+}
+
+static int psgmii_vco_calibrate(struct ipqess_port *port)
+{
+ struct ipqess_switch *sw = port->sw;
+ struct qca8k_priv *priv = sw->priv;
+ struct ipqess_port *other_port;
+ int val, ret, i;
+
+ ret = phy_start_calibration(port->netdev->phydev);
+ if (ret) {
+ dev_err(priv->dev,
+ "PHY VCO calibration PLL not ready\n");
+ return ret;
+ }
+
+ /* Start PSGMIIPHY VCO PLL calibration */
+ ret = regmap_set_bits(priv->psgmii,
+ PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_1,
+ PSGMIIPHY_REG_PLL_VCO_CALIB_RESTART);
+
+ /* Poll for PSGMIIPHY PLL calibration finish - Dakota(IPQ40xx) */
+ ret = regmap_read_poll_timeout(priv->psgmii,
+ PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_2,
+ val,
+ val & PSGMIIPHY_REG_PLL_VCO_CALIB_READY,
+ PSGMII_VCO_CALIB_INTERVAL,
+ PSGMII_VCO_CALIB_TIMEOUT);
+ if (ret) {
+ dev_err(priv->dev,
+ "IPQ PSGMIIPHY VCO calibration PLL not ready\n");
+ return ret;
+ }
+
+ /* Prepare all switch ports, in case we're dealing with a multiport PHY */
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ other_port = sw->port_list[i];
+ if (!other_port)
+ continue;
+ ipqess_port_prep_test(other_port);
+ }
+
+ ret = phy_stop_calibration(port->netdev->phydev);
+
+ for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+ other_port = sw->port_list[i];
+ if (!other_port)
+ continue;
+ ipqess_port_unprep_test(other_port);
+ }
+
+ qca8k_fdb_flush(priv);
+
+ return ret;
+}
+
+int psgmii_calibrate_and_test(struct ipqess_port *port)
+{
+ int ret, attempt;
+
+ for (attempt = 0; attempt <= PSGMII_CALIB_RETRIES; attempt++) {
+ ret = psgmii_vco_calibrate(port);
+ if (!ret) {
+ netdev_dbg(port->netdev,
+ "PSGMII link stabilized after %d attempts\n",
+ attempt + 1);
+ return 0;
+ }
+
+ /* On tested hardware, the link often stabilizes in 4 or 5 retries.
+ * If it still isn't stable, we wait a bit, then try another set
+ * of calibration attempts.
+ */
+ netdev_dbg(port->netdev, "PSGMII link is unstable! Retrying... %d/QCA8K_PSGMII_CALIB_RETRIES\n",
+ attempt + 1);
+ if (attempt % PSGMII_CALIB_RETRIES_BURST == 0)
+ schedule_timeout_interruptible(msecs_to_jiffies(PSGMII_CALIB_RETRY_DELAY));
+ else
+ schedule();
+ }
+
+ netdev_err(port->netdev, "PSGMII work is unstable! Repeated recalibration attempts did not help!\n");
+ return -EFAULT;
+}
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
index 29420820c3d8..ea759707335a 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
@@ -1218,6 +1218,26 @@ int ipqess_port_check_8021q_upper(struct net_device *netdev,
/* phylink ops */
+static int
+ipqess_psgmii_configure(struct ipqess_port *port)
+{
+ int ret = 0;
+
+ /* For this multi-port PHY, we only need one calibration run,
+ * don't rerun it for other ports
+ */
+ if (!atomic_cmpxchg(&port->sw->priv->psgmii_calibrated, 0, 1)) {
+ psgmii_calibrate_and_test(port);
+
+ ret = regmap_clear_bits(port->sw->priv->psgmii, PSGMIIPHY_MODE_CONTROL,
+ PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M);
+ ret = regmap_write(port->sw->priv->psgmii, PSGMIIPHY_TX_CONTROL,
+ PSGMIIPHY_TX_CONTROL_MAGIC_VALUE);
+ }
+
+ return ret;
+}
+
static void
ipqess_phylink_mac_config(struct phylink_config *config,
unsigned int mode,
@@ -1233,12 +1253,22 @@ ipqess_phylink_mac_config(struct phylink_config *config,
case 1:
case 2:
case 3:
+ if (state->interface == PHY_INTERFACE_MODE_PSGMII)
+ if (ipqess_psgmii_configure(port))
+ dev_err(priv->dev,
+ "PSGMII configuration failed!\n");
+ return;
case 4:
case 5:
if (phy_interface_mode_is_rgmii(state->interface))
regmap_set_bits(priv->regmap,
QCA8K_IPQ4019_REG_RGMII_CTRL,
QCA8K_IPQ4019_RGMII_CTRL_CLK);
+
+ if (state->interface == PHY_INTERFACE_MODE_PSGMII)
+ if (ipqess_psgmii_configure(port))
+ dev_err(priv->dev,
+ "PSGMII configuration failed!\n");
return;
default:
dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
index 00f0dff9c39d..bc24a0507702 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
@@ -95,4 +95,8 @@ int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
const struct net_device *netdev);
+
+/* Defined in ipqess_calib.c */
+int psgmii_calibrate_and_test(struct ipqess_port *port);
+
#endif
diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
index 9ad016f7201e..72c488e7c498 100644
--- a/include/linux/dsa/qca8k.h
+++ b/include/linux/dsa/qca8k.h
@@ -496,6 +496,7 @@ struct qca8k_priv {
/* IPQ4019 specific */
struct regmap *psgmii;
+ atomic_t psgmii_calibrated;
};
struct qca8k_mib_desc {
--
2.42.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH net-next v3 8/8] ARM: dts: qcom: ipq4019: Add description for the IPQ4019 ESS EDMA and switch
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
` (6 preceding siblings ...)
2023-11-14 10:55 ` [PATCH net-next v3 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver Romain Gantois
@ 2023-11-14 10:55 ` Romain Gantois
2023-11-14 18:12 ` [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Andrew Lunn
8 siblings, 0 replies; 37+ messages in thread
From: Romain Gantois @ 2023-11-14 10:55 UTC (permalink / raw)
To: davem, Rob Herring, Krzysztof Kozlowski
Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
Bjorn Andersson, Konrad Dybcio
The Qualcomm IPQ4019 includes a modified version of the QCA8K Ethernet
switch. The switch's CPU port is connected to the SoC through the internal
EDMA Ethernet controller. Add support for these two devices, which are
coupled tightly enough to justify treating them as a single device.
Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
.../boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi | 13 +++
arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi | 94 +++++++++++++++++++
2 files changed, 107 insertions(+)
diff --git a/arch/arm/boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi b/arch/arm/boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi
index da67d55fa557..6a185b8b31c6 100644
--- a/arch/arm/boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi
+++ b/arch/arm/boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi
@@ -242,6 +242,19 @@ &mdio {
pinctrl-names = "default";
};
+&switch {
+ status = "okay";
+};
+
+&swport4 {
+ status = "okay";
+ label = "lan";
+};
+
+&swport5 {
+ status = "okay";
+};
+
&wifi0 {
status = "okay";
nvmem-cell-names = "pre-calibration";
diff --git a/arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi b/arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi
index 9844e0b7cff9..5a4e5d408f72 100644
--- a/arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi
@@ -596,6 +596,100 @@ wifi1: wifi@a800000 {
status = "disabled";
};
+ switch: switch@c000000 {
+ compatible = "qcom,ipq4019-ess";
+ reg = <0xc000000 0x80000>, <0x98000 0x800>, <0xc080000 0x8000>;
+ reg-names = "base", "psgmii_phy", "edma";
+ resets = <&gcc ESS_PSGMII_ARES>, <&gcc ESS_RESET>;
+ reset-names = "psgmii", "ess";
+ clocks = <&gcc GCC_ESS_CLK>;
+ clock-names = "ess";
+ mdio = <&mdio>;
+ interrupts = <GIC_SPI 65 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 66 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 67 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 68 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 69 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 70 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 71 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 73 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 74 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 75 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 76 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 77 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 78 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 79 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 80 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 240 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 241 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 242 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 243 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 244 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 245 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 246 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 247 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 248 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 249 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 250 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 251 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 252 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 253 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 254 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 255 IRQ_TYPE_EDGE_RISING>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ swport1: port@1 { /* MAC1 */
+ reg = <1>;
+ label = "lan1";
+ phy-handle = <ðphy0>;
+ phy-mode = "psgmii";
+
+ status = "disabled";
+ };
+
+ swport2: port@2 { /* MAC2 */
+ reg = <2>;
+ label = "lan2";
+ phy-handle = <ðphy1>;
+ phy-mode = "psgmii";
+
+ status = "disabled";
+ };
+
+ swport3: port@3 { /* MAC3 */
+ reg = <3>;
+ label = "lan3";
+ phy-handle = <ðphy2>;
+ phy-mode = "psgmii";
+
+ status = "disabled";
+ };
+
+ swport4: port@4 { /* MAC4 */
+ reg = <4>;
+ label = "lan4";
+ phy-handle = <ðphy3>;
+ phy-mode = "psgmii";
+
+ status = "disabled";
+ };
+
+ swport5: port@5 { /* MAC5 */
+ reg = <5>;
+ label = "wan";
+ phy-handle = <ðphy4>;
+ phy-mode = "psgmii";
+
+ status = "disabled";
+ };
+ };
+ };
+
mdio: mdio@90000 {
#address-cells = <1>;
#size-cells = <0>;
--
2.42.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver
2023-11-14 10:55 [PATCH net-next v3 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
` (7 preceding siblings ...)
2023-11-14 10:55 ` [PATCH net-next v3 8/8] ARM: dts: qcom: ipq4019: Add description for the IPQ4019 ESS EDMA and switch Romain Gantois
@ 2023-11-14 18:12 ` Andrew Lunn
8 siblings, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2023-11-14 18:12 UTC (permalink / raw)
To: Romain Gantois
Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
thomas.petazzoni, Florian Fainelli, Heiner Kallweit, Russell King,
linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
Andy Gross, Bjorn Andersson, Konrad Dybcio
On Tue, Nov 14, 2023 at 11:55:50AM +0100, Romain Gantois wrote:
> Hello everyone,
>
> This is the 3rd iteration on the Qualcomm IPQ4019 Ethernet Switch Subsystem
> driver. I made some patch separation mistakes in the v2, sorry about that.
>
> Notable changes in v3:
> - Fixed formatting of 3/8.
Please wait at least 24 hours between submitted versions. Otherwise
you get people reviewing the wrong version.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread