* [PATCH 3/3] [v5 omap] ARM: dts: omap2: add stlc4560 spi-wireless node
From: Arnd Bergmann @ 2026-05-07 21:24 UTC (permalink / raw)
To: netdev
Cc: Arnd Bergmann, Aaro Koskinen, Andreas Kemnade,
Bartosz Golaszewski, Benoît Cousson, David S. Miller,
Dmitry Torokhov, Eric Dumazet, Felipe Balbi, Jakub Kicinski,
Johannes Berg, Kevin Hilman, Krzysztof Kozlowski, Linus Walleij,
Paolo Abeni, Rob Herring, Roger Quadros, Tony Lindgren,
linux-wireless, devicetree, linux-kernel, linux-arm-kernel,
linux-gpio, linux-omap, Krzysztof Kozlowski
In-Reply-To: <20260507212451.3333185-1-arnd@kernel.org>
From: Arnd Bergmann <arnd@arndb.de>
Converted from the platform_device creation in board-n8x0.c.
Link: https://lore.kernel.org/all/20230314163201.955689-1-arnd@kernel.org/
Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
arch/arm/boot/dts/ti/omap/omap2.dtsi | 4 ++++
arch/arm/boot/dts/ti/omap/omap2420-n8x0-common.dtsi | 12 ++++++++++++
2 files changed, 16 insertions(+)
diff --git a/arch/arm/boot/dts/ti/omap/omap2.dtsi b/arch/arm/boot/dts/ti/omap/omap2.dtsi
index afabb36a8ac1..fdc1790adf43 100644
--- a/arch/arm/boot/dts/ti/omap/omap2.dtsi
+++ b/arch/arm/boot/dts/ti/omap/omap2.dtsi
@@ -129,6 +129,8 @@ i2c2: i2c@48072000 {
};
mcspi1: spi@48098000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
compatible = "ti,omap2-mcspi";
ti,hwmods = "mcspi1";
reg = <0x48098000 0x100>;
@@ -140,6 +142,8 @@ mcspi1: spi@48098000 {
};
mcspi2: spi@4809a000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
compatible = "ti,omap2-mcspi";
ti,hwmods = "mcspi2";
reg = <0x4809a000 0x100>;
diff --git a/arch/arm/boot/dts/ti/omap/omap2420-n8x0-common.dtsi b/arch/arm/boot/dts/ti/omap/omap2420-n8x0-common.dtsi
index 63b0b4921e4e..fe9dd8bbfc85 100644
--- a/arch/arm/boot/dts/ti/omap/omap2420-n8x0-common.dtsi
+++ b/arch/arm/boot/dts/ti/omap/omap2420-n8x0-common.dtsi
@@ -109,3 +109,15 @@ partition@5 {
};
};
};
+
+&mcspi2 {
+ status = "okay";
+
+ wifi@0 {
+ reg = <0>;
+ compatible = "st,stlc4560";
+ spi-max-frequency = <48000000>;
+ interrupts-extended = <&gpio3 23 IRQ_TYPE_EDGE_RISING>;
+ powerdown-gpios = <&gpio4 1 GPIO_ACTIVE_LOW>; /* gpio 97 */
+ };
+};
--
2.39.5
^ permalink raw reply related
* [PATCH 2/3] [v5 net-next] p54spi: convert to devicetree
From: Arnd Bergmann @ 2026-05-07 21:24 UTC (permalink / raw)
To: netdev
Cc: Arnd Bergmann, Aaro Koskinen, Andreas Kemnade,
Bartosz Golaszewski, Benoît Cousson, David S. Miller,
Dmitry Torokhov, Eric Dumazet, Felipe Balbi, Jakub Kicinski,
Johannes Berg, Kevin Hilman, Krzysztof Kozlowski, Linus Walleij,
Paolo Abeni, Rob Herring, Roger Quadros, Tony Lindgren,
linux-wireless, devicetree, linux-kernel, linux-arm-kernel,
linux-gpio, linux-omap, Christian Lamparter
In-Reply-To: <20260507212451.3333185-1-arnd@kernel.org>
From: Arnd Bergmann <arnd@arndb.de>
The Prism54 SPI driver hardcodes GPIO numbers and expects users to
pass them as module parameters, apparently a relic from its life as a
staging driver. This works because there is only one user, the Nokia
N8x0 tablet.
Convert this to the gpio descriptor interface and DT based probing
to improve this and simplify the code at the same time.
Acked-by: Christian Lamparter <chunkeey@gmail.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
v5:
- fix irq assignment # https://sashiko.dev/#/patchset/20260430081242.3686993-1-arnd%40kernel.org
v4:
- make gpio line optional again
- fix EPROBE_DEFER handling
- match only st,stlc4560 compatible value, with binding change
v3:
- rebase an older patch
---
arch/arm/mach-omap2/board-n8x0.c | 18 ------
drivers/net/wireless/intersil/p54/p54spi.c | 67 ++++++++--------------
drivers/net/wireless/intersil/p54/p54spi.h | 3 +
3 files changed, 26 insertions(+), 62 deletions(-)
diff --git a/arch/arm/mach-omap2/board-n8x0.c b/arch/arm/mach-omap2/board-n8x0.c
index 969265d5d5c6..d9acd32c5457 100644
--- a/arch/arm/mach-omap2/board-n8x0.c
+++ b/arch/arm/mach-omap2/board-n8x0.c
@@ -20,7 +20,6 @@
#include <linux/spi/spi.h>
#include <linux/usb/musb.h>
#include <linux/mmc/host.h>
-#include <linux/platform_data/spi-omap2-mcspi.h>
#include <linux/platform_data/mmc-omap.h>
#include <linux/mfd/menelaus.h>
@@ -106,21 +105,6 @@ static void __init n8x0_usb_init(void) {}
#endif /*CONFIG_USB_MUSB_TUSB6010 */
-
-static struct omap2_mcspi_device_config p54spi_mcspi_config = {
- .turbo_mode = 0,
-};
-
-static struct spi_board_info n800_spi_board_info[] __initdata = {
- {
- .modalias = "p54spi",
- .bus_num = 2,
- .chip_select = 0,
- .max_speed_hz = 48000000,
- .controller_data = &p54spi_mcspi_config,
- },
-};
-
#if defined(CONFIG_MENELAUS) && IS_ENABLED(CONFIG_MMC_OMAP)
/*
@@ -524,7 +508,5 @@ omap_late_initcall(n8x0_late_initcall);
void * __init n8x0_legacy_init(void)
{
board_check_revision();
- spi_register_board_info(n800_spi_board_info,
- ARRAY_SIZE(n800_spi_board_info));
return &mmc1_data;
}
diff --git a/drivers/net/wireless/intersil/p54/p54spi.c b/drivers/net/wireless/intersil/p54/p54spi.c
index 9d66dcae54e0..d18be2545028 100644
--- a/drivers/net/wireless/intersil/p54/p54spi.c
+++ b/drivers/net/wireless/intersil/p54/p54spi.c
@@ -8,6 +8,7 @@
*/
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/firmware.h>
@@ -15,7 +16,7 @@
#include <linux/irq.h>
#include <linux/spi/spi.h>
#include <linux/etherdevice.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/slab.h>
#include "p54spi.h"
@@ -30,19 +31,6 @@
MODULE_FIRMWARE("3826.arm");
MODULE_FIRMWARE("3826.eeprom");
-/* gpios should be handled in board files and provided via platform data,
- * but because it's currently impossible for p54spi to have a header file
- * in include/linux, let's use module parameters for now
- */
-
-static int p54spi_gpio_power = 97;
-module_param(p54spi_gpio_power, int, 0444);
-MODULE_PARM_DESC(p54spi_gpio_power, "gpio number for power line");
-
-static int p54spi_gpio_irq = 87;
-module_param(p54spi_gpio_irq, int, 0444);
-MODULE_PARM_DESC(p54spi_gpio_irq, "gpio number for irq line");
-
static void p54spi_spi_read(struct p54s_priv *priv, u8 address,
void *buf, size_t len)
{
@@ -262,14 +250,14 @@ static int p54spi_upload_firmware(struct ieee80211_hw *dev)
static void p54spi_power_off(struct p54s_priv *priv)
{
- disable_irq(gpio_to_irq(p54spi_gpio_irq));
- gpio_set_value(p54spi_gpio_power, 0);
+ disable_irq(priv->irq);
+ gpiod_set_value(priv->gpio_powerdown, 1);
}
static void p54spi_power_on(struct p54s_priv *priv)
{
- gpio_set_value(p54spi_gpio_power, 1);
- enable_irq(gpio_to_irq(p54spi_gpio_irq));
+ gpiod_set_value(priv->gpio_powerdown, 0);
+ enable_irq(priv->irq);
/* need to wait a while before device can be accessed, the length
* is just a guess
@@ -608,31 +596,20 @@ static int p54spi_probe(struct spi_device *spi)
goto err_free;
}
- ret = gpio_request(p54spi_gpio_power, "p54spi power");
- if (ret < 0) {
- dev_err(&priv->spi->dev, "power GPIO request failed: %d", ret);
+ priv->gpio_powerdown = gpiod_get_optional(&spi->dev, "powerdown", GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->gpio_powerdown)) {
+ ret = dev_err_probe(&priv->spi->dev, PTR_ERR(priv->gpio_powerdown),
+ "powerdown GPIO request failed\n");
goto err_free;
}
- ret = gpio_request(p54spi_gpio_irq, "p54spi irq");
- if (ret < 0) {
- dev_err(&priv->spi->dev, "irq GPIO request failed: %d", ret);
- goto err_free_gpio_power;
- }
-
- gpio_direction_output(p54spi_gpio_power, 0);
- gpio_direction_input(p54spi_gpio_irq);
-
- ret = request_irq(gpio_to_irq(p54spi_gpio_irq),
- p54spi_interrupt, IRQF_NO_AUTOEN, "p54spi",
- priv->spi);
+ ret = request_irq(spi->irq, p54spi_interrupt, IRQF_NO_AUTOEN, "p54spi", priv->spi);
if (ret < 0) {
dev_err(&priv->spi->dev, "request_irq() failed");
- goto err_free_gpio_irq;
+ goto err_free_gpio_power;
}
- irq_set_irq_type(gpio_to_irq(p54spi_gpio_irq), IRQ_TYPE_EDGE_RISING);
-
+ priv->irq = spi->irq;
INIT_WORK(&priv->work, p54spi_work);
init_completion(&priv->fw_comp);
INIT_LIST_HEAD(&priv->tx_pending);
@@ -659,11 +636,9 @@ static int p54spi_probe(struct spi_device *spi)
err_free_common:
release_firmware(priv->firmware);
- free_irq(gpio_to_irq(p54spi_gpio_irq), spi);
-err_free_gpio_irq:
- gpio_free(p54spi_gpio_irq);
+ free_irq(priv->irq, spi);
err_free_gpio_power:
- gpio_free(p54spi_gpio_power);
+ gpiod_put(priv->gpio_powerdown);
err_free:
p54_free_common(priv->hw);
return ret;
@@ -675,10 +650,8 @@ static void p54spi_remove(struct spi_device *spi)
p54_unregister_common(priv->hw);
- free_irq(gpio_to_irq(p54spi_gpio_irq), spi);
-
- gpio_free(p54spi_gpio_power);
- gpio_free(p54spi_gpio_irq);
+ free_irq(priv->irq, spi);
+ gpiod_put(priv->gpio_powerdown);
release_firmware(priv->firmware);
mutex_destroy(&priv->mutex);
@@ -686,10 +659,16 @@ static void p54spi_remove(struct spi_device *spi)
p54_free_common(priv->hw);
}
+static const struct of_device_id p54spi_of_ids[] = {
+ { .compatible = "st,stlc4560", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, p54spi_of_ids);
static struct spi_driver p54spi_driver = {
.driver = {
.name = "p54spi",
+ .of_match_table = p54spi_of_ids,
},
.probe = p54spi_probe,
diff --git a/drivers/net/wireless/intersil/p54/p54spi.h b/drivers/net/wireless/intersil/p54/p54spi.h
index e5619a13fd61..118785cc635a 100644
--- a/drivers/net/wireless/intersil/p54/p54spi.h
+++ b/drivers/net/wireless/intersil/p54/p54spi.h
@@ -107,6 +107,9 @@ struct p54s_priv {
enum fw_state fw_state;
const struct firmware *firmware;
+
+ struct gpio_desc *gpio_powerdown;
+ int irq;
};
#endif /* P54SPI_H */
--
2.39.5
^ permalink raw reply related
* [PATCH 1/3] [v5 net-next] dt-bindings: net: add st,stlc4560/p54spi binding
From: Arnd Bergmann @ 2026-05-07 21:24 UTC (permalink / raw)
To: netdev
Cc: Arnd Bergmann, Aaro Koskinen, Andreas Kemnade,
Bartosz Golaszewski, Benoît Cousson, David S. Miller,
Dmitry Torokhov, Eric Dumazet, Felipe Balbi, Jakub Kicinski,
Johannes Berg, Kevin Hilman, Krzysztof Kozlowski, Linus Walleij,
Paolo Abeni, Rob Herring, Roger Quadros, Tony Lindgren,
linux-wireless, devicetree, linux-kernel, linux-arm-kernel,
linux-gpio, linux-omap, Christian Lamparter
In-Reply-To: <20260507212451.3333185-1-arnd@kernel.org>
From: Arnd Bergmann <arnd@arndb.de>
The SPI version of Prism54 was sold under a couple of different
names and supported by the Linux p54spi driver, but there was
never a DT binding for it.
Document the four known names of this device and the properties
that are sufficient for its use on the Nokia N8x0 tablet.
As I don't have this hardware or documentation for it, this is
purely based on existing usage in the driver.
Link: https://lore.kernel.org/all/e8dc9acb-6f85-e0a9-a145-d101ca6da201@gmail.com/
Acked-by: Christian Lamparter <chunkeey@gmail.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
v5: fix name in MAINTAINERS file
v4: renamed file to st,stlc4560, matching the primary compatible string
require st,stlc4560 string
---
.../bindings/net/wireless/st,stlc4560.yaml | 61 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 62 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/wireless/st,stlc4560.yaml
diff --git a/Documentation/devicetree/bindings/net/wireless/st,stlc4560.yaml b/Documentation/devicetree/bindings/net/wireless/st,stlc4560.yaml
new file mode 100644
index 000000000000..a32265c07350
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/wireless/st,stlc4560.yaml
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/wireless/st,stlc4560.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ST/Intersil/Conexant stlc45xx/p54spi/cx3110x SPI wireless device
+
+maintainers:
+ - Christian Lamparter <chunkeey@gmail.com>
+
+description:
+ The SPI variant of the Intersil Prism54 wireless device was sold
+ under a variety of names, including Conexant CX3110x and
+ ST Microelectronics STLC5460.
+
+allOf:
+ - $ref: ieee80211.yaml#
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ oneOf:
+ - const: st,stlc4560
+ - items:
+ - enum:
+ - cnxt,3110x
+ - isil,p54spi
+ - st,stlc4550
+ - const: st,stlc4560
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ powerdown-gpios:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ wifi@0 {
+ compatible = "st,stlc4560";
+ reg = <0>;
+ spi-max-frequency = <48000000>;
+ interrupts-extended = <&gpio 23>;
+ powerdown-gpios = <&gpio 1>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 21c0ef0b9ce5..7defcc6e2072 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20142,6 +20142,7 @@ M: Christian Lamparter <chunkeey@googlemail.com>
L: linux-wireless@vger.kernel.org
S: Maintained
W: https://wireless.wiki.kernel.org/en/users/Drivers/p54
+F: Documentation/devicetree/bindings/net/wireless/st,stlc4560.yaml
F: drivers/net/wireless/intersil/
PACKET SOCKETS
--
2.39.5
^ permalink raw reply related
* [PATCH 0/3] [v5 net-next] wireless: p54 devicetree conversion
From: Arnd Bergmann @ 2026-05-07 21:24 UTC (permalink / raw)
To: netdev
Cc: Arnd Bergmann, Aaro Koskinen, Andreas Kemnade,
Bartosz Golaszewski, Benoît Cousson, David S. Miller,
Dmitry Torokhov, Eric Dumazet, Felipe Balbi, Jakub Kicinski,
Johannes Berg, Kevin Hilman, Krzysztof Kozlowski, Linus Walleij,
Paolo Abeni, Rob Herring, Roger Quadros, Tony Lindgren,
linux-wireless, devicetree, linux-kernel, linux-arm-kernel,
linux-gpio, linux-omap
From: Arnd Bergmann <arnd@arndb.de>
This is an older patch of mine that I lost track of. We already decided
a while ago that the OMAP2 platform should probably be removed entirely,
and this is the only known user, but it's probably a good idea to still
get the driver changes in, in case there are other out-of-tree users.
We probably don't have to worry about bisectability any more though,
so the devicetree and driver changes can just get merged independently
through the OMAP and wireless trees, respectively.
Arnd
---
v4 Link: https://lore.kernel.org/all/20260430081242.3686993-1-arnd@kernel.org/
v3 Link: https://lore.kernel.org/all/20260427142355.2532714-1-arnd@kernel.org/
v2 Link: https://lore.kernel.org/all/20230404082401.1087835-1-arnd@kernel.org/
Cc: "Aaro Koskinen" <aaro.koskinen@iki.fi>
Cc: "Andreas Kemnade" <andreas@kemnade.info>
Cc: "Arnd Bergmann" <arnd@arndb.de>
Cc: "Bartosz Golaszewski" <brgl@kernel.org>
Cc: "Benoît Cousson" <bcousson@baylibre.com>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: "Dmitry Torokhov" <dmitry.torokhov@gmail.com>
Cc: "Eric Dumazet" <edumazet@google.com>
Cc: "Felipe Balbi" <balbi@kernel.org>
Cc: "Jakub Kicinski" <kuba@kernel.org>
Cc: "Johannes Berg" <johannes@sipsolutions.net>
Cc: "Kevin Hilman" <khilman@baylibre.com>
Cc: "Krzysztof Kozlowski" <krzk+dt@kernel.org>
Cc: "Linus Walleij" <linusw@kernel.org>
Cc: "Paolo Abeni" <pabeni@redhat.com>
Cc: "Rob Herring" <robh+dt@kernel.org>
Cc: "Roger Quadros" <rogerq@kernel.org>
Cc: "Tony Lindgren" <tony@atomide.com>
Cc: linux-wireless@vger.kernel.org
Cc: netdev@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-gpio@vger.kernel.org
Cc: linux-omap@vger.kernel.org
Arnd Bergmann (3):
[v5 net-next] dt-bindings: net: add st,stlc4560/p54spi binding
[v5 net-next] p54spi: convert to devicetree
[v5 omap] ARM: dts: omap2: add stlc4560 spi-wireless node
.../bindings/net/wireless/st,stlc4560.yaml | 61 +++++++++++++++++
MAINTAINERS | 1 +
arch/arm/boot/dts/ti/omap/omap2.dtsi | 4 ++
.../dts/ti/omap/omap2420-n8x0-common.dtsi | 12 ++++
arch/arm/mach-omap2/board-n8x0.c | 18 -----
drivers/net/wireless/intersil/p54/p54spi.c | 67 +++++++------------
drivers/net/wireless/intersil/p54/p54spi.h | 3 +
7 files changed, 104 insertions(+), 62 deletions(-)
create mode 100644 Documentation/devicetree/bindings/net/wireless/st,stlc4560.yaml
--
2.39.5
^ permalink raw reply
* Re: [PATCH net-next v6 3/3] gve: implement PTP gettimex64
From: Jacob Keller @ 2026-05-07 21:23 UTC (permalink / raw)
To: Harshitha Ramamurthy, netdev
Cc: joshwash, andrew+netdev, davem, edumazet, kuba, pabeni,
richardcochran, jstultz, tglx, sboyd, willemb, nktgrg, jfraker,
ziweixiao, maolson, jordanrhee, thostet, alok.a.tiwari,
pkaligineedi, horms, dwmw2, yyd, jefrogers, linux-kernel,
Naman Gulati
In-Reply-To: <20260507211304.3046526-4-hramamurthy@google.com>
On 5/7/2026 2:13 PM, Harshitha Ramamurthy wrote:
> From: Jordan Rhee <jordanrhee@google.com>
>
> Enable chrony and phc2sys to synchronize system clock to NIC clock.
>
> Two paths are implemented: a precise path using system counter values
> sampled by the device, and a fallback path using system counter values
> sampled in the driver using ptp_read_system_prets()/postts().
>
> To use the precise path, the current system clocksource must match the
> units returned by the device, which on x86 is X86_TSC and on ARM64 is
> ARM_ARCH_COUNTER. The clockid requested for the cross-timestamp must
> be either CLOCK_REALTIME or CLOCK_MONOTONIC_RAW. These conditions hold
> by default on GCP VMs using Chrony, so we expect the precise path to be
> used the vast majority of the time. If the system clocksource is changed
> to kvm-clock, it activates the fallback path. Ethtool counters have been
> added to count how many times each path is used.
>
> The uncertainty window in the precise path is typically around 1-2us,
> while in the fallback path is around 60-80us.
>
> Stub implementions of adjfine and adjtime are added to avoid NULL
> dereference when phc2sys tries to adjust the clock.
>
> Cc: John Stultz <jstultz@google.com>
> Cc: Thomas Gleixner <tglx@kernel.org>
> Cc: Stephen Boyd <sboyd@kernel.org>
> Cc: David Woodhouse <dwmw2@infradead.org>
> Reviewed-by: Willem de Bruijn <willemb@google.com>
> Reviewed-by: Kevin Yang <yyd@google.com>
> Reviewed-by: Naman Gulati <namangulati@google.com>
> Signed-off-by: Jordan Rhee <jordanrhee@google.com>
> Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
> ---
> Changes in v6:
> - Added a fallback to driver-sampled time sandwich that is used when
> the following conditions are not met:
> - The system clock source is X86_TSC or ARM_ARCH_COUNTER
> - The requested clockid is CLOCK_REALTIME or CLOCK_MONOTONIC_RAW
> - The architecture is x86 or ARM64
> - Added ethtool statistics to count how many cross-timestamps used the
> precise path versus fallback path.
> - Fixed printf format specifier.
> - Added stub implementions of adjtime and adjfine to prevent NULL
> dereference when phc2sys tries to adjust clock.
All excellent improvements.
> - Moved system time snapshot back to gve_ptp_gettimex64() so we can get the
> current system clock source from it. It is OK for it to not be inside
> the mutex or retry loop because lock contention and retries should be
> extremely rare, and chrony filters out bad samples.
>
I'm a bit worried about this part, but if I understand, it would only
affect the fallback variant anyways since the clock samples are taken in
the host in the precise/fast path.
> Changes in v5:
> - Reformulate retry loop in terms of total timeout (Jakub Kicinski)
>
> Changes in v3:
> - Take system time snapshot inside the mutex
> - Return -EOPNOTSUPP if cross-timestamp is requested on an arch other
> than x86 or arm64
>
> Changes in v2:
> - fix compilation warning on ARM by casting cycles_t to u64
> ---
> drivers/net/ethernet/google/gve/gve.h | 8 +
> drivers/net/ethernet/google/gve/gve_adminq.h | 4 +-
> drivers/net/ethernet/google/gve/gve_ethtool.c | 3 +
> drivers/net/ethernet/google/gve/gve_ptp.c | 237 +++++++++++++++++-
> 4 files changed, 243 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
> index 7b69d0cfc0d5..4de3ce60060e 100644
> --- a/drivers/net/ethernet/google/gve/gve.h
> +++ b/drivers/net/ethernet/google/gve/gve.h
> @@ -880,6 +880,14 @@ struct gve_priv {
> u32 stats_report_trigger_cnt; /* count of device-requested stats-reports since last reset */
> u32 suspend_cnt; /* count of times suspended */
> u32 resume_cnt; /* count of times resumed */
> + /* count of cross-timestamps attempted using system timestamps
> + * from the AQ command
> + */
> + u32 ptp_precise_xtstamps;
> + /* count of cross-timestamps attempted using system timestamps sampled
> + * by the driver
> + */
Helpful for analyzing when its not working. Nice!
> +static int gve_cycles_to_clock_fn(ktime_t *device_time,
> + struct system_counterval_t *system_counterval,
> + void *ctx)
> +{
> + struct gve_cycles_to_clock_callback_ctx *context = ctx;
> +
> + *device_time = 0;
> +
> + system_counterval->cycles = context->cycles;
> + system_counterval->use_nsecs = false;
> +
> + if (IS_ENABLED(CONFIG_X86))
> + system_counterval->cs_id = CSID_X86_TSC;
> + else if (IS_ENABLED(CONFIG_ARM64))
> + system_counterval->cs_id = CSID_ARM_ARCH_COUNTER;
> + else
No single kernel can be compiled for both CONFIG_X86 *and* CONFIG_ARM64
simultaneously, so there is no need to check in sequence and an
exclusive if/else path is fine. Ok.
> +
> +static bool
> +gve_can_use_system_ts_from_device(enum clocksource_ids system_clock_source,
> + clockid_t clockid)
> +{
> + if (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC_RAW)
> + return false;
> +
> + /* If the system clock source matches the system clock
> + * returned by the AdminQ command, we can use the system
> + * timestamps returned by the device, otherwise we have to
> + * fall back to sampling system time from the host which
> + * is less accurate.
> + */
> + if (IS_ENABLED(CONFIG_X86))
> + return system_clock_source == CSID_X86_TSC;
> + else if (IS_ENABLED(CONFIG_ARM64))
> + return system_clock_source == CSID_ARM_ARCH_COUNTER;
> +
This feels a bit duplicated to me, since you have the same check
elsewhere. Would it make sense to pull this to its own function check?
Its short enough I think that is only warranted if you have a true
(non-nit) cause to make a v7.
Thus:
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
^ permalink raw reply
* [PATCH net-next v4 10/10] net: airoha: Support multiple LAN/WAN interfaces for hw MAC address configuration
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Madhur Agrawal
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
The EN7581 and AN7583 SoCs provide registers to configure hardware LAN/WAN
MAC addresses, used to determine whether received traffic is destined for
this host or should be forwarded to another device.
The SoC hardware design assumes all interfaces configured as LAN (or WAN)
share a common upper MAC address, which is programmed into the
REG_FE_{LAN,WAN}_MAC_H register. The lower bytes of 'local' addresses can
be expressed as a range via the REG_FE_MAC_LMIN and REG_FE_MAC_LMAX
registers.
Previously, only a single interface was considered when programming these
registers. Extend the logic to derive the correct minimum and maximum
values for REG_FE_MAC_LMIN/REG_FE_MAC_LMAX when two or more interfaces are
configured as LAN or WAN.
Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 73 +++++++++++++++++++++++++++-----
1 file changed, 63 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 8617dd4f7932..fbfefd0ec355 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -71,20 +71,67 @@ static void airoha_qdma_irq_disable(struct airoha_irq_bank *irq_bank,
airoha_qdma_set_irqmask(irq_bank, index, mask, 0);
}
-static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
+static int airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
{
struct airoha_eth *eth = dev->eth;
- u32 val, reg;
+ u8 ref_addr[ETH_ALEN] = {};
+ u32 reg, val, lmin, lmax;
+ int i;
+
+ lmin = (addr[3] << 16) | (addr[4] << 8) | addr[5];
+ lmax = lmin;
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
+ int j;
+
+ if (!port)
+ continue;
+
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ struct airoha_gdm_dev *iter_dev;
+ struct net_device *netdev;
+
+ iter_dev = port->devs[j];
+ if (!iter_dev || iter_dev == dev)
+ continue;
+
+ if (airoha_is_lan_gdm_dev(iter_dev) !=
+ airoha_is_lan_gdm_dev(dev))
+ continue;
+
+ netdev = iter_dev->dev;
+ if (netdev->reg_state != NETREG_REGISTERED)
+ continue;
+
+ ether_addr_copy(ref_addr, netdev->dev_addr);
+ val = (netdev->dev_addr[3] << 16) |
+ (netdev->dev_addr[4] << 8) | netdev->dev_addr[5];
+ if (val < lmin)
+ lmin = val;
+ if (val > lmax)
+ lmax = val;
+ }
+ }
+
+ if (!is_zero_ether_addr(ref_addr) && memcmp(ref_addr, addr, 3)) {
+ /* According to the HW design, hw mac address MS bits
+ * must be the same for each net_device with the same
+ * LAN/WAN configuration.
+ */
+ return -EINVAL;
+ }
reg = airoha_is_lan_gdm_dev(dev) ? REG_FE_LAN_MAC_H : REG_FE_WAN_MAC_H;
val = (addr[0] << 16) | (addr[1] << 8) | addr[2];
airoha_fe_wr(eth, reg, val);
- val = (addr[3] << 16) | (addr[4] << 8) | addr[5];
- airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
- airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
+ airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), lmin);
+ airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), lmax);
airoha_ppe_init_upd_mem(dev);
+
+ return 0;
}
static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
@@ -1792,13 +1839,18 @@ static int airoha_dev_stop(struct net_device *netdev)
static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct sockaddr *addr = p;
int err;
- err = eth_mac_addr(netdev, p);
+ err = eth_prepare_mac_addr_change(netdev, p);
if (err)
return err;
- airoha_set_macaddr(dev, netdev->dev_addr);
+ err = airoha_set_macaddr(dev, addr->sa_data);
+ if (err)
+ return err;
+
+ eth_commit_mac_addr_change(netdev, p);
return 0;
}
@@ -1903,6 +1955,7 @@ static int airoha_dev_init(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
+ int err;
switch (port->id) {
case AIROHA_GDM3_IDX:
@@ -1922,12 +1975,12 @@ static int airoha_dev_init(struct net_device *netdev)
}
airoha_dev_set_qdma(dev);
- airoha_set_macaddr(dev, netdev->dev_addr);
+ err = airoha_set_macaddr(dev, netdev->dev_addr);
+ if (err)
+ return err;
if (!airoha_is_lan_gdm_dev(dev) &&
(port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)) {
- int err;
-
err = airoha_set_gdm2_loopback(dev);
if (err) {
dev->flags &= ~PRIV_FLAG_WAN;
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 09/10] net: airoha: Introduce WAN device flag
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
Introduce WAN flag to specify if a given device is used to transmit/receive
WAN or LAN traffic. Current codebase supports specifying LAN/WAN device
configuration in ndo_init() callback during device bootstrap.
Please note it is possible to specify multiple LAN devices but just a
single WAN one.
Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 69 +++++++++++++++++++++++++-------
drivers/net/ethernet/airoha/airoha_eth.h | 13 +++---
drivers/net/ethernet/airoha/airoha_ppe.c | 2 +-
3 files changed, 62 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 1c4927c1aeb0..8617dd4f7932 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1863,36 +1863,77 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
return 0;
}
-static int airoha_dev_init(struct net_device *netdev)
+static struct airoha_gdm_dev *
+airoha_get_wan_gdm_dev(struct airoha_eth *eth)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
+ int j;
+
+ if (!port)
+ continue;
+
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ struct airoha_gdm_dev *dev = port->devs[j];
+
+ if (dev && !airoha_is_lan_gdm_dev(dev))
+ return dev;
+ }
+ }
+
+ return NULL;
+}
+
+static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev)
{
- struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = dev->eth;
int i;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
dev->dev->irq = dev->qdma->irq_banks[0].irq;
- airoha_set_macaddr(dev, netdev->dev_addr);
+
+ for (i = 0; i < eth->soc->num_ppe; i++)
+ airoha_ppe_set_cpu_port(dev, i, airoha_get_fe_port(dev));
+}
+
+static int airoha_dev_init(struct net_device *netdev)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
switch (port->id) {
case AIROHA_GDM3_IDX:
- case AIROHA_GDM4_IDX:
- /* If GDM2 is active we can't enable loopback */
- if (!eth->ports[1]) {
- int err;
+ case AIROHA_GDM4_IDX: {
+ struct airoha_eth *eth = dev->eth;
- err = airoha_set_gdm2_loopback(dev);
- if (err)
- return err;
- }
+ if (eth->ports[1] || airoha_get_wan_gdm_dev(eth))
+ break;
+ fallthrough;
+ }
+ case AIROHA_GDM2_IDX:
+ /* GDM2 is always used as wan */
+ dev->flags |= PRIV_FLAG_WAN;
break;
default:
break;
}
- for (i = 0; i < eth->soc->num_ppe; i++)
- airoha_ppe_set_cpu_port(dev, i, airoha_get_fe_port(dev));
+ airoha_dev_set_qdma(dev);
+ airoha_set_macaddr(dev, netdev->dev_addr);
+
+ if (!airoha_is_lan_gdm_dev(dev) &&
+ (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)) {
+ int err;
+
+ err = airoha_set_gdm2_loopback(dev);
+ if (err) {
+ dev->flags &= ~PRIV_FLAG_WAN;
+ return err;
+ }
+ }
return 0;
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 3a313ac439e7..5715b03e630b 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -540,12 +540,17 @@ struct airoha_qdma {
u64 fwd_tx_packets;
};
+enum airoha_priv_flags {
+ PRIV_FLAG_WAN = BIT(0),
+};
+
struct airoha_gdm_dev {
struct airoha_gdm_port *port;
struct airoha_qdma *qdma;
struct airoha_eth *eth;
struct net_device *dev;
+ u32 flags;
int nbq;
};
@@ -653,13 +658,7 @@ static inline u16 airoha_qdma_get_txq(struct airoha_qdma *qdma, u16 qid)
static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev)
{
- struct airoha_gdm_port *port = dev->port;
-
- /* GDM1 port on EN7581 SoC is connected to the lan dsa switch.
- * GDM{2,3,4} can be used as wan port connected to an external
- * phy module.
- */
- return port->id == 1;
+ return !(dev->flags & PRIV_FLAG_WAN);
}
static inline bool airoha_is_7581(struct airoha_eth *eth)
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index c4086d29d984..194cd50b2c74 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -350,7 +350,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
return -EINVAL;
port = dev->port;
- if (dsa_port >= 0 || eth->ports[1])
+ if (dsa_port >= 0 || airoha_is_lan_gdm_dev(dev))
pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
: port->id;
else
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 08/10] net: airoha: Do not stop GDM port if it is shared
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
Theoretically, in the current codebase, two independent net_devices can
be connected to the same GDM port so we need to check the GDM port is not
used by any other running net_device before setting the forward
configuration to FE_PSE_PORT_DROP.
Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 36 +++++++++++++++++++++++++-------
drivers/net/ethernet/airoha/airoha_eth.h | 2 ++
2 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 0253919714e0..1c4927c1aeb0 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1714,8 +1714,8 @@ static int airoha_dev_open(struct net_device *netdev)
int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
+ u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
struct airoha_qdma *qdma = dev->qdma;
- u32 pse_port = FE_PSE_PORT_PPE1;
netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(dev, true);
@@ -1729,10 +1729,14 @@ static int airoha_dev_open(struct net_device *netdev)
airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
- airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
- GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
- FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
- FIELD_PREP(GDM_LONG_LEN_MASK, len));
+ cur_len = airoha_fe_get(qdma->eth, REG_GDM_LEN_CFG(port->id),
+ GDM_LONG_LEN_MASK);
+ if (!atomic_read(&port->users) || len > cur_len)
+ airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
+ GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+ FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+ FIELD_PREP(GDM_LONG_LEN_MASK, len));
+ atomic_inc(&port->users);
airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
GLOBAL_CFG_TX_DMA_EN_MASK |
@@ -1762,8 +1766,12 @@ static int airoha_dev_stop(struct net_device *netdev)
for (i = 0; i < netdev->num_tx_queues; i++)
netdev_tx_reset_subqueue(netdev, i);
- airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
- FE_PSE_PORT_DROP);
+ if (atomic_dec_and_test(&port->users)) {
+ airoha_set_vip_for_gdm_port(dev, false);
+ airoha_set_gdm_port_fwd_cfg(qdma->eth,
+ REG_GDM_FWD_CFG(port->id),
+ FE_PSE_PORT_DROP);
+ }
if (atomic_dec_and_test(&qdma->users)) {
airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
@@ -1915,10 +1923,22 @@ static void airoha_dev_get_stats64(struct net_device *netdev,
static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ u32 cur_len, len = ETH_HLEN + mtu + ETH_FCS_LEN;
struct airoha_gdm_port *port = dev->port;
- u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
struct airoha_eth *eth = dev->eth;
+ cur_len = airoha_fe_get(eth, REG_GDM_LEN_CFG(port->id),
+ GDM_LONG_LEN_MASK);
+ if (len < cur_len) {
+ u8 port_refcnt = atomic_read(&port->users);
+
+ /* We can decrease the device MTU just if the GDM port is
+ * not shared or if the other device is not running.
+ */
+ if (port_refcnt > 1 || (port_refcnt && !netif_running(netdev)))
+ return -EBUSY;
+ }
+
airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
GDM_LONG_LEN_MASK,
FIELD_PREP(GDM_LONG_LEN_MASK, len));
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 207c75152fde..3a313ac439e7 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -553,6 +553,8 @@ struct airoha_gdm_port {
struct airoha_gdm_dev *devs[AIROHA_MAX_NUM_GDM_DEVS];
int id;
+ atomic_t users;
+
struct airoha_hw_stats stats;
struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 07/10] net: airoha: Support multiple net_devices for a single FE GDM port
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
EN7581 or AN7583 SoCs support connecting multiple external SerDes (e.g.
Ethernet or USB SerDes) to GDM3 or GDM4 ports via a hw arbiter that
manages the traffic in a TDM manner. As a result multiple net_devices can
connect to the same GDM{3,4} port and there is a theoretical "1:n"
relation between GDM ports and net_devices.
┌─────────────────────────────────┐
│ │ ┌──────┐
│ P1 GDM1 ├────►MT7530│
│ │ └──────┘
│ │ ETH0 (DSA conduit)
│ │
│ PSE/FE │
│ │
│ │
│ │ ┌─────┐
│ P0 CDM1 ├────►QDMA0│
│ P4 P9 GDM4 │ └─────┘
└──┬─────────────────────────┬────┘
│ │
┌──▼──┐ ┌────▼────┐
│ PPE │ │ ARB │
└─────┘ └─┬─────┬─┘
│ │
┌──▼──┐┌─▼───┐
│ ETH ││ USB │
└─────┘└─────┘
ETH1 ETH2
Introduce support for multiple net_devices connected to the same Frame
Engine (FE) GDM port (GDM3 or GDM4) via an external hw arbiter.
Please note GDM1 or GDM2 does not support the connection with the external
arbiter.
Add get_dev_from_sport callback since EN7581 and AN7583 have different
logics for the net_device type connected to GDM3 or GDM4.
Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 271 ++++++++++++++++++++++++-------
drivers/net/ethernet/airoha/airoha_eth.h | 10 +-
drivers/net/ethernet/airoha/airoha_ppe.c | 13 +-
3 files changed, 229 insertions(+), 65 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 786bc677af3c..0253919714e0 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -106,7 +106,7 @@ static int airoha_set_vip_for_gdm_port(struct airoha_gdm_dev *dev, bool enable)
struct airoha_eth *eth = dev->eth;
u32 vip_port;
- vip_port = eth->soc->ops.get_vip_port(port, port->nbq);
+ vip_port = eth->soc->ops.get_vip_port(port, dev->nbq);
if (enable) {
airoha_fe_set(eth, REG_FE_VIP_PORT_EN, vip_port);
airoha_fe_set(eth, REG_FE_IFC_PORT_EN, vip_port);
@@ -565,24 +565,26 @@ static int airoha_qdma_fill_rx_queue(struct airoha_queue *q)
return nframes;
}
-static int airoha_qdma_get_gdm_port(struct airoha_eth *eth,
- struct airoha_qdma_desc *desc)
+static struct airoha_gdm_dev *
+airoha_qdma_get_gdm_dev(struct airoha_eth *eth, struct airoha_qdma_desc *desc)
{
- u32 port, sport, msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
+ struct airoha_gdm_port *port;
+ u16 p, d;
- sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK, msg1);
- switch (sport) {
- case 0x10 ... 0x14:
- port = 0;
- break;
- case 0x2 ... 0x4:
- port = sport - 1;
- break;
- default:
- return -EINVAL;
- }
+ if (eth->soc->ops.get_dev_from_sport(desc, &p, &d))
+ return ERR_PTR(-ENODEV);
- return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
+ if (p >= ARRAY_SIZE(eth->ports))
+ return ERR_PTR(-ENODEV);
+
+ port = eth->ports[p];
+ if (!port)
+ return ERR_PTR(-ENODEV);
+
+ if (d >= ARRAY_SIZE(port->devs))
+ return ERR_PTR(-ENODEV);
+
+ return port->devs[d] ? port->devs[d] : ERR_PTR(-ENODEV);
}
static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
@@ -597,9 +599,8 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
struct airoha_queue_entry *e = &q->entry[q->tail];
struct airoha_qdma_desc *desc = &q->desc[q->tail];
u32 hash, reason, msg1, desc_ctrl;
- struct airoha_gdm_port *port;
- struct net_device *netdev;
- int data_len, len, p;
+ struct airoha_gdm_dev *dev;
+ int data_len, len;
struct page *page;
desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
@@ -621,12 +622,10 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
if (!len || data_len < len)
goto free_frag;
- p = airoha_qdma_get_gdm_port(eth, desc);
- if (p < 0 || !eth->ports[p])
+ dev = airoha_qdma_get_gdm_dev(eth, desc);
+ if (IS_ERR(dev))
goto free_frag;
- port = eth->ports[p];
- netdev = port->dev->dev;
if (!q->skb) { /* first buffer */
q->skb = napi_build_skb(e->buf, q->buf_size);
if (!q->skb)
@@ -634,8 +633,8 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
__skb_put(q->skb, len);
skb_mark_for_recycle(q->skb);
- q->skb->dev = netdev;
- q->skb->protocol = eth_type_trans(q->skb, netdev);
+ q->skb->dev = dev->dev;
+ q->skb->protocol = eth_type_trans(q->skb, dev->dev);
q->skb->ip_summed = CHECKSUM_UNNECESSARY;
skb_record_rx_queue(q->skb, qid);
} else { /* scattered frame */
@@ -653,7 +652,9 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
continue;
- if (netdev_uses_dsa(netdev)) {
+ if (netdev_uses_dsa(dev->dev)) {
+ struct airoha_gdm_port *port = dev->port;
+
/* PPE module requires untagged packets to work
* properly and it provides DSA port index via the
* DMA descriptor. Report DSA tag to the DSA stack
@@ -847,22 +848,27 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
- struct airoha_gdm_dev *dev;
- int j;
+ int d;
if (!port)
continue;
- dev = port->dev;
- if (dev->qdma != qdma)
- continue;
+ for (d = 0; d < ARRAY_SIZE(port->devs); d++) {
+ struct airoha_gdm_dev *dev = port->devs[d];
+ int j;
- dev = port->dev;
- for (j = 0; j < dev->dev->num_tx_queues; j++) {
- if (airoha_qdma_get_txq(qdma, j) != qid)
+ if (!dev)
continue;
- netif_wake_subqueue(dev->dev, j);
+ if (dev->qdma != qdma)
+ continue;
+
+ for (j = 0; j < dev->dev->num_tx_queues; j++) {
+ if (airoha_qdma_get_txq(qdma, j) != qid)
+ continue;
+
+ netif_wake_subqueue(dev->dev, j);
+ }
}
}
q->txq_stopped = false;
@@ -1823,7 +1829,7 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
- src_port = eth->soc->ops.get_sport(port, port->nbq);
+ src_port = eth->soc->ops.get_sport(port, dev->nbq);
if (src_port < 0)
return src_port;
@@ -1840,7 +1846,7 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
airoha_ppe_set_cpu_port(dev, i, AIROHA_GDM2_IDX);
if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
- u32 mask = FC_ID_OF_SRC_PORT_MASK(port->nbq);
+ u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq);
airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
__field_prep(mask, AIROHA_GDM2_IDX));
@@ -2044,7 +2050,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
}
fport = airoha_get_fe_port(dev);
- msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+ msg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev->nbq) |
+ FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
q = &qdma->q_tx[qid];
@@ -2961,12 +2968,15 @@ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
+ int j;
if (!port)
continue;
- if (port->dev == dev)
- return true;
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ if (port->devs[j] == dev)
+ return true;
+ }
}
return false;
@@ -2974,10 +2984,11 @@ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
static int airoha_alloc_gdm_device(struct airoha_eth *eth,
struct airoha_gdm_port *port,
- struct device_node *np)
+ int nbq, struct device_node *np)
{
- struct airoha_gdm_dev *dev;
struct net_device *netdev;
+ struct airoha_gdm_dev *dev;
+ u8 index;
int err;
netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
@@ -2997,7 +3008,6 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
NETIF_F_HW_TC;
netdev->features |= netdev->hw_features;
netdev->vlan_features = netdev->hw_features;
- netdev->dev.of_node = np;
SET_NETDEV_DEV(netdev, eth->dev);
/* reserve hw queues for HTB offloading */
@@ -3015,11 +3025,25 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
netdev->dev_addr);
}
+ /* Allowed nbq for EN7581 on GDM3 port are 4 and 5 for PCIE0
+ * and PCIE1 respectively.
+ */
+ index = nbq;
+ if (index && airoha_is_7581(eth) && port->id == AIROHA_GDM3_IDX)
+ index -= 4;
+
+ if (index >= ARRAY_SIZE(port->devs) || port->devs[index]) {
+ dev_err(eth->dev, "invalid nbq id: %d\n", nbq);
+ return -EINVAL;
+ }
+
+ netdev->dev.of_node = of_node_get(np);
dev = netdev_priv(netdev);
dev->dev = netdev;
dev->port = port;
- port->dev = dev;
dev->eth = eth;
+ dev->nbq = nbq;
+ port->devs[index] = dev;
return 0;
}
@@ -3029,7 +3053,8 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
{
const __be32 *id_ptr = of_get_property(np, "reg", NULL);
struct airoha_gdm_port *port;
- int err, p;
+ struct device_node *node;
+ int err, nbq, p, d = 0;
u32 id;
if (!id_ptr) {
@@ -3057,15 +3082,51 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
u64_stats_init(&port->stats.syncp);
spin_lock_init(&port->stats.lock);
port->id = id;
- /* XXX: Read nbq from DTS */
- port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
eth->ports[p] = port;
err = airoha_metadata_dst_alloc(port);
if (err)
return err;
- return airoha_alloc_gdm_device(eth, port, np);
+ /* Default nbq value to ensure backward compatibility */
+ nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
+
+ for_each_child_of_node(np, node) {
+ /* Multiple external serdes connected to the FE GDM port via an
+ * external arbiter.
+ */
+ const __be32 *nbq_ptr;
+
+ if (!of_device_is_compatible(node, "airoha,eth-port"))
+ continue;
+
+ d++;
+ if (!of_device_is_available(node))
+ continue;
+
+ nbq_ptr = of_get_property(node, "reg", NULL);
+ if (!nbq_ptr) {
+ dev_err(eth->dev, "missing nbq id\n");
+ of_node_put(node);
+ return -EINVAL;
+ }
+
+ /* Verify the provided nbq parameter is valid */
+ nbq = be32_to_cpup(nbq_ptr);
+ err = eth->soc->ops.get_sport(port, nbq);
+ if (err < 0) {
+ of_node_put(node);
+ return err;
+ }
+
+ err = airoha_alloc_gdm_device(eth, port, nbq, node);
+ if (err) {
+ of_node_put(node);
+ return err;
+ }
+ }
+
+ return !d ? airoha_alloc_gdm_device(eth, port, nbq, np) : 0;
}
static int airoha_register_gdm_devices(struct airoha_eth *eth)
@@ -3074,14 +3135,22 @@ static int airoha_register_gdm_devices(struct airoha_eth *eth)
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
- int err;
+ int j;
if (!port)
continue;
- err = register_netdev(port->dev->dev);
- if (err)
- return err;
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ struct airoha_gdm_dev *dev = port->devs[j];
+ int err;
+
+ if (!dev)
+ continue;
+
+ err = register_netdev(dev->dev);
+ if (err)
+ return err;
+ }
}
set_bit(DEV_STATE_REGISTERED, ð->state);
@@ -3188,14 +3257,23 @@ static int airoha_probe(struct platform_device *pdev)
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
- struct airoha_gdm_dev *dev;
+ int j;
if (!port)
continue;
- dev = port->dev;
- if (dev && dev->dev->reg_state == NETREG_REGISTERED)
- unregister_netdev(dev->dev);
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ struct airoha_gdm_dev *dev = port->devs[j];
+ struct net_device *netdev;
+
+ if (!dev)
+ continue;
+
+ netdev = dev->dev;
+ of_node_put(netdev->dev.of_node);
+ if (netdev->reg_state == NETREG_REGISTERED)
+ unregister_netdev(netdev);
+ }
airoha_metadata_dst_free(port);
}
airoha_hw_cleanup(eth);
@@ -3216,14 +3294,22 @@ static void airoha_remove(struct platform_device *pdev)
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
- struct airoha_gdm_dev *dev;
+ int j;
if (!port)
continue;
- dev = port->dev;
- if (dev)
- unregister_netdev(dev->dev);
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ struct airoha_gdm_dev *dev = port->devs[j];
+ struct net_device *netdev;
+
+ if (!dev)
+ continue;
+
+ netdev = dev->dev;
+ of_node_put(netdev->dev.of_node);
+ unregister_netdev(netdev);
+ }
airoha_metadata_dst_free(port);
}
airoha_hw_cleanup(eth);
@@ -3286,6 +3372,39 @@ static u32 airoha_en7581_get_vip_port(struct airoha_gdm_port *port, int nbq)
return 0;
}
+static int airoha_en7581_get_dev_from_sport(struct airoha_qdma_desc *desc,
+ u16 *port, u16 *dev)
+{
+ u32 sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK,
+ le32_to_cpu(READ_ONCE(desc->msg1)));
+
+ *dev = 0;
+ switch (sport) {
+ case 0x10 ... 0x14:
+ *port = 0; /* GDM1 */
+ break;
+ case 0x2 ... 0x4:
+ *port = sport - 1;
+ break;
+ case HSGMII_LAN_7581_PCIE1_SRCPORT:
+ *dev = 1;
+ fallthrough;
+ case HSGMII_LAN_7581_PCIE0_SRCPORT:
+ *port = 2; /* GDM3 */
+ break;
+ case HSGMII_LAN_7581_USB_SRCPORT:
+ *dev = 1;
+ fallthrough;
+ case HSGMII_LAN_7581_ETH_SRCPORT:
+ *port = 3; /* GDM4 */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static const char * const an7583_xsi_rsts_names[] = {
"xsi-mac",
"hsi0-mac",
@@ -3335,6 +3454,36 @@ static u32 airoha_an7583_get_vip_port(struct airoha_gdm_port *port, int nbq)
return 0;
}
+static int airoha_an7583_get_dev_from_sport(struct airoha_qdma_desc *desc,
+ u16 *port, u16 *dev)
+{
+ u32 sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK,
+ le32_to_cpu(READ_ONCE(desc->msg1)));
+
+ *dev = 0;
+ switch (sport) {
+ case 0x10 ... 0x14:
+ *port = 0; /* GDM1 */
+ break;
+ case 0x2 ... 0x4:
+ *port = sport - 1;
+ break;
+ case HSGMII_LAN_7583_ETH_SRCPORT:
+ *port = 2; /* GDM3 */
+ break;
+ case HSGMII_LAN_7583_USB_SRCPORT:
+ *dev = 1;
+ fallthrough;
+ case HSGMII_LAN_7583_PCIE_SRCPORT:
+ *port = 3; /* GDM4 */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static const struct airoha_eth_soc_data en7581_soc_data = {
.version = 0x7581,
.xsi_rsts_names = en7581_xsi_rsts_names,
@@ -3343,6 +3492,7 @@ static const struct airoha_eth_soc_data en7581_soc_data = {
.ops = {
.get_sport = airoha_en7581_get_sport,
.get_vip_port = airoha_en7581_get_vip_port,
+ .get_dev_from_sport = airoha_en7581_get_dev_from_sport,
},
};
@@ -3354,6 +3504,7 @@ static const struct airoha_eth_soc_data an7583_soc_data = {
.ops = {
.get_sport = airoha_an7583_get_sport,
.get_vip_port = airoha_an7583_get_vip_port,
+ .get_dev_from_sport = airoha_an7583_get_dev_from_sport,
},
};
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 3e93919a175c..207c75152fde 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -17,6 +17,7 @@
#include <net/dsa.h>
#define AIROHA_MAX_NUM_GDM_PORTS 4
+#define AIROHA_MAX_NUM_GDM_DEVS 2
#define AIROHA_MAX_NUM_QDMA 2
#define AIROHA_MAX_NUM_IRQ_BANKS 4
#define AIROHA_MAX_DSA_PORTS 7
@@ -542,14 +543,15 @@ struct airoha_qdma {
struct airoha_gdm_dev {
struct airoha_gdm_port *port;
struct airoha_qdma *qdma;
- struct net_device *dev;
struct airoha_eth *eth;
+ struct net_device *dev;
+
+ int nbq;
};
struct airoha_gdm_port {
- struct airoha_gdm_dev *dev;
+ struct airoha_gdm_dev *devs[AIROHA_MAX_NUM_GDM_DEVS];
int id;
- int nbq;
struct airoha_hw_stats stats;
@@ -585,6 +587,8 @@ struct airoha_eth_soc_data {
struct {
int (*get_sport)(struct airoha_gdm_port *port, int nbq);
u32 (*get_vip_port)(struct airoha_gdm_port *port, int nbq);
+ int (*get_dev_from_sport)(struct airoha_qdma_desc *desc,
+ u16 *port, u16 *dev);
} ops;
};
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 047141b2d6d8..c4086d29d984 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -169,6 +169,7 @@ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
for (p = 0; p < ARRAY_SIZE(eth->ports); p++) {
struct airoha_gdm_port *port = eth->ports[p];
+ int j;
airoha_fe_rmw(eth, REG_PPE_MTU(i, p),
FP0_EGRESS_MTU_MASK |
@@ -180,8 +181,16 @@ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
if (!port)
continue;
- airoha_ppe_set_cpu_port(port->dev, i,
- airoha_get_fe_port(port->dev));
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ struct airoha_gdm_dev *dev = port->devs[j];
+ u8 fport;
+
+ if (!dev)
+ continue;
+
+ fport = airoha_get_fe_port(dev);
+ airoha_ppe_set_cpu_port(dev, i, fport);
+ }
}
}
}
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 06/10] net: airoha: Move {cpu,fwd}_tx_packets in airoha_qdma struct
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
Since now multiple net_devices connected to different QDMA blocks can
share the same GDM port, cpu_tx_packets and fwd_tx_packets fields can
be overwritten with the value from a different QDMA block. In order to
fix the issue move cpu_tx_packets and fwd_tx_packets fields from
airoha_gdm_port struct to airoha_qdma one.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 15 +++++++--------
drivers/net/ethernet/airoha/airoha_eth.h | 8 ++++----
2 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 69a4c2e0d58b..786bc677af3c 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2306,19 +2306,18 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
struct tc_ets_qopt_offload *opt)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = dev->qdma;
- u64 cpu_tx_packets = airoha_qdma_rr(dev->qdma,
- REG_CNTR_VAL(channel << 1));
- u64 fwd_tx_packets = airoha_qdma_rr(dev->qdma,
+ u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
+ u64 fwd_tx_packets = airoha_qdma_rr(qdma,
REG_CNTR_VAL((channel << 1) + 1));
- u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
- (fwd_tx_packets - port->fwd_tx_packets);
+ u64 tx_packets = (cpu_tx_packets - qdma->cpu_tx_packets) +
+ (fwd_tx_packets - qdma->fwd_tx_packets);
_bstats_update(opt->stats.bstats, 0, tx_packets);
- port->cpu_tx_packets = cpu_tx_packets;
- port->fwd_tx_packets = fwd_tx_packets;
+ qdma->cpu_tx_packets = cpu_tx_packets;
+ qdma->fwd_tx_packets = fwd_tx_packets;
return 0;
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 34f1fef51640..3e93919a175c 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -533,6 +533,10 @@ struct airoha_qdma {
struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
+
+ /* qos stats counters */
+ u64 cpu_tx_packets;
+ u64 fwd_tx_packets;
};
struct airoha_gdm_dev {
@@ -549,10 +553,6 @@ struct airoha_gdm_port {
struct airoha_hw_stats stats;
- /* qos stats counters */
- u64 cpu_tx_packets;
- u64 fwd_tx_packets;
-
struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
};
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 05/10] net: airoha: Move qos_sq_bmap in airoha_qdma struct
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
Since now multiple net_devices connected to different QDMA blocks can
share the same GDM port, qos_sq_bmap field can be overwritten with the
configuration obtained from a net_device connected to a different QDMA
block. In order to fix the issue move qos_sq_bmap field from
airoha_gdm_port struct to airoha_qdma one.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 20 ++++++++++----------
drivers/net/ethernet/airoha/airoha_eth.h | 4 ++--
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 080705e2f58d..69a4c2e0d58b 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2600,7 +2600,7 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
int err, num_tx_queues = netdev->real_num_tx_queues;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = dev->qdma;
if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
@@ -2627,7 +2627,7 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
return err;
}
- set_bit(channel, port->qos_sq_bmap);
+ set_bit(channel, qdma->qos_sq_bmap);
opt->qid = AIROHA_NUM_TX_RING + channel;
return 0;
@@ -2811,11 +2811,11 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = dev->qdma;
netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
- clear_bit(queue, port->qos_sq_bmap);
+ clear_bit(queue, qdma->qos_sq_bmap);
}
static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
@@ -2823,9 +2823,9 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
{
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = dev->qdma;
- if (!test_bit(channel, port->qos_sq_bmap)) {
+ if (!test_bit(channel, qdma->qos_sq_bmap)) {
NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
return -EINVAL;
}
@@ -2838,10 +2838,10 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
static int airoha_tc_htb_destroy(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = dev->qdma;
int q;
- for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
+ for_each_set_bit(q, qdma->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
airoha_tc_remove_htb_queue(netdev, q);
return 0;
@@ -2852,9 +2852,9 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
{
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = dev->qdma;
- if (!test_bit(channel, port->qos_sq_bmap)) {
+ if (!test_bit(channel, qdma->qos_sq_bmap)) {
NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
return -EINVAL;
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 18a9dfd75d44..34f1fef51640 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -531,6 +531,8 @@ struct airoha_qdma {
struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
+
+ DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
};
struct airoha_gdm_dev {
@@ -547,8 +549,6 @@ struct airoha_gdm_port {
struct airoha_hw_stats stats;
- DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
-
/* qos stats counters */
u64 cpu_tx_packets;
u64 fwd_tx_packets;
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 04/10] net: airoha: Rely on airoha_gdm_dev pointer in airhoa_is_lan_gdm_port()
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
Rename airhoa_is_lan_gdm_port in airhoa_is_lan_gdm_dev. Moreover, rely
on airoha_gdm_dev pointer in airhoa_is_lan_gdm_dev() instead of
airoha_gdm_port one.
This is a preliminary patch to support multiple net_devices connected to
the same GDM{3,4} port via an external hw arbiter.
Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 6 ++----
drivers/net/ethernet/airoha/airoha_eth.h | 4 +++-
drivers/net/ethernet/airoha/airoha_ppe.c | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 1798b4a6cf5d..080705e2f58d 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -73,12 +73,10 @@ static void airoha_qdma_irq_disable(struct airoha_irq_bank *irq_bank,
static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
{
- struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = dev->eth;
u32 val, reg;
- reg = airoha_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
- : REG_FE_WAN_MAC_H;
+ reg = airoha_is_lan_gdm_dev(dev) ? REG_FE_LAN_MAC_H : REG_FE_WAN_MAC_H;
val = (addr[0] << 16) | (addr[1] << 8) | addr[2];
airoha_fe_wr(eth, reg, val);
@@ -1859,7 +1857,7 @@ static int airoha_dev_init(struct net_device *netdev)
int i;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
- dev->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
+ dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
dev->dev->irq = dev->qdma->irq_banks[0].irq;
airoha_set_macaddr(dev, netdev->dev_addr);
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 21d308b1f087..18a9dfd75d44 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -645,8 +645,10 @@ static inline u16 airoha_qdma_get_txq(struct airoha_qdma *qdma, u16 qid)
return qid % ARRAY_SIZE(qdma->q_tx);
}
-static inline bool airoha_is_lan_gdm_port(struct airoha_gdm_port *port)
+static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev)
{
+ struct airoha_gdm_port *port = dev->port;
+
/* GDM1 port on EN7581 SoC is connected to the lan dsa switch.
* GDM{2,3,4} can be used as wan port connected to an external
* phy module.
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 22f5f1bae730..047141b2d6d8 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -362,7 +362,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
/* For downlink traffic consume SRAM memory for hw
* forwarding descriptors queue.
*/
- if (airoha_is_lan_gdm_port(port))
+ if (airoha_is_lan_gdm_dev(dev))
val |= AIROHA_FOE_IB2_FAST_PATH;
if (dsa_port >= 0)
val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ,
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 03/10] net: airoha: Move airoha_qdma pointer in airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
Move airoha_qdma pointer from airoha_gdm_port struct to airoha_gdm_dev
one since the QDMA block used depends on the particular net_device
WAN/LAN configuration and in the current codebase net_device pointer is
associated to airoha_gdm_dev struct.
This is a preliminary patch to support multiple net_devices connected
to the same GDM{3,4} port via an external hw arbiter.
Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 101 +++++++++++++++----------------
drivers/net/ethernet/airoha/airoha_eth.h | 9 ++-
drivers/net/ethernet/airoha/airoha_ppe.c | 17 +++---
3 files changed, 61 insertions(+), 66 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 4af64f182968..1798b4a6cf5d 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -71,9 +71,10 @@ static void airoha_qdma_irq_disable(struct airoha_irq_bank *irq_bank,
airoha_qdma_set_irqmask(irq_bank, index, mask, 0);
}
-static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
+static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
{
- struct airoha_eth *eth = port->qdma->eth;
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
u32 val, reg;
reg = airoha_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
@@ -85,7 +86,7 @@ static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
- airoha_ppe_init_upd_mem(port);
+ airoha_ppe_init_upd_mem(dev);
}
static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
@@ -101,10 +102,10 @@ static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
FIELD_PREP(GDM_UCFQ_MASK, val));
}
-static int airoha_set_vip_for_gdm_port(struct airoha_gdm_port *port,
- bool enable)
+static int airoha_set_vip_for_gdm_port(struct airoha_gdm_dev *dev, bool enable)
{
- struct airoha_eth *eth = port->qdma->eth;
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
u32 vip_port;
vip_port = eth->soc->ops.get_vip_port(port, port->nbq);
@@ -854,7 +855,8 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
if (!port)
continue;
- if (port->qdma != qdma)
+ dev = port->dev;
+ if (dev->qdma != qdma)
continue;
dev = port->dev;
@@ -1558,9 +1560,10 @@ static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
}
}
-static void airoha_update_hw_stats(struct airoha_gdm_port *port)
+static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
{
- struct airoha_eth *eth = port->qdma->eth;
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
u32 val, i = 0;
spin_lock(&port->stats.lock);
@@ -1707,11 +1710,11 @@ static int airoha_dev_open(struct net_device *netdev)
int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- struct airoha_qdma *qdma = port->qdma;
+ struct airoha_qdma *qdma = dev->qdma;
u32 pse_port = FE_PSE_PORT_PPE1;
netif_tx_start_all_queues(netdev);
- err = airoha_set_vip_for_gdm_port(port, true);
+ err = airoha_set_vip_for_gdm_port(dev, true);
if (err)
return err;
@@ -1747,11 +1750,11 @@ static int airoha_dev_stop(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- struct airoha_qdma *qdma = port->qdma;
+ struct airoha_qdma *qdma = dev->qdma;
int i;
netif_tx_disable(netdev);
- airoha_set_vip_for_gdm_port(port, false);
+ airoha_set_vip_for_gdm_port(dev, false);
for (i = 0; i < netdev->num_tx_queues; i++)
netdev_tx_reset_subqueue(netdev, i);
@@ -1777,21 +1780,21 @@ static int airoha_dev_stop(struct net_device *netdev)
static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
int err;
err = eth_mac_addr(netdev, p);
if (err)
return err;
- airoha_set_macaddr(port, netdev->dev_addr);
+ airoha_set_macaddr(dev, netdev->dev_addr);
return 0;
}
-static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
+static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
{
- struct airoha_eth *eth = port->qdma->eth;
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
u32 val, pse_port, chan;
int i, src_port;
@@ -1836,7 +1839,7 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
__field_prep(SP_CPORT_MASK(val), FE_PSE_PORT_CDM2));
for (i = 0; i < eth->soc->num_ppe; i++)
- airoha_ppe_set_cpu_port(port, i, AIROHA_GDM2_IDX);
+ airoha_ppe_set_cpu_port(dev, i, AIROHA_GDM2_IDX);
if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
u32 mask = FC_ID_OF_SRC_PORT_MASK(port->nbq);
@@ -1856,9 +1859,9 @@ static int airoha_dev_init(struct net_device *netdev)
int i;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
- port->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
- dev->dev->irq = port->qdma->irq_banks[0].irq;
- airoha_set_macaddr(port, netdev->dev_addr);
+ dev->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
+ dev->dev->irq = dev->qdma->irq_banks[0].irq;
+ airoha_set_macaddr(dev, netdev->dev_addr);
switch (port->id) {
case AIROHA_GDM3_IDX:
@@ -1867,7 +1870,7 @@ static int airoha_dev_init(struct net_device *netdev)
if (!eth->ports[1]) {
int err;
- err = airoha_set_gdm2_loopback(port);
+ err = airoha_set_gdm2_loopback(dev);
if (err)
return err;
}
@@ -1877,8 +1880,7 @@ static int airoha_dev_init(struct net_device *netdev)
}
for (i = 0; i < eth->soc->num_ppe; i++)
- airoha_ppe_set_cpu_port(port, i,
- airoha_get_fe_port(port));
+ airoha_ppe_set_cpu_port(dev, i, airoha_get_fe_port(dev));
return 0;
}
@@ -1890,7 +1892,7 @@ static void airoha_dev_get_stats64(struct net_device *netdev,
struct airoha_gdm_port *port = dev->port;
unsigned int start;
- airoha_update_hw_stats(port);
+ airoha_update_hw_stats(dev);
do {
start = u64_stats_fetch_begin(&port->stats.syncp);
storage->rx_packets = port->stats.rx_ok_pkts;
@@ -1910,8 +1912,8 @@ static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- struct airoha_eth *eth = port->qdma->eth;
u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
+ struct airoha_eth *eth = dev->eth;
airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
GDM_LONG_LEN_MASK,
@@ -1985,10 +1987,10 @@ static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
#endif
}
-int airoha_get_fe_port(struct airoha_gdm_port *port)
+int airoha_get_fe_port(struct airoha_gdm_dev *dev)
{
- struct airoha_qdma *qdma = port->qdma;
- struct airoha_eth *eth = qdma->eth;
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
switch (eth->soc->version) {
case 0x7583:
@@ -2005,8 +2007,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
- struct airoha_qdma *qdma = port->qdma;
+ struct airoha_qdma *qdma = dev->qdma;
u32 nr_frags, tag, msg0, msg1, len;
struct airoha_queue_entry *e;
struct netdev_queue *txq;
@@ -2044,7 +2045,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
}
}
- fport = airoha_get_fe_port(port);
+ fport = airoha_get_fe_port(dev);
msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
@@ -2147,8 +2148,7 @@ static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *info)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
- struct airoha_eth *eth = port->qdma->eth;
+ struct airoha_eth *eth = dev->eth;
strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
@@ -2161,7 +2161,7 @@ static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
struct airoha_gdm_port *port = dev->port;
unsigned int start;
- airoha_update_hw_stats(port);
+ airoha_update_hw_stats(dev);
do {
start = u64_stats_fetch_begin(&port->stats.syncp);
stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
@@ -2201,7 +2201,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
ARRAY_SIZE(hw_stats->rx_len) + 1);
*ranges = airoha_ethtool_rmon_ranges;
- airoha_update_hw_stats(port);
+ airoha_update_hw_stats(dev);
do {
int i;
@@ -2221,18 +2221,17 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
const u16 *weights, u8 n_weights)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
int i;
for (i = 0; i < AIROHA_NUM_TX_RING; i++)
- airoha_qdma_clear(port->qdma, REG_QUEUE_CLOSE_CFG(channel),
+ airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel),
TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
for (i = 0; i < n_weights; i++) {
u32 status;
int err;
- airoha_qdma_wr(port->qdma, REG_TXWRR_WEIGHT_CFG,
+ airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG,
TWRR_RW_CMD_MASK |
FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
@@ -2240,13 +2239,12 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
err = read_poll_timeout(airoha_qdma_rr, status,
status & TWRR_RW_CMD_DONE,
USEC_PER_MSEC, 10 * USEC_PER_MSEC,
- true, port->qdma,
- REG_TXWRR_WEIGHT_CFG);
+ true, dev->qdma, REG_TXWRR_WEIGHT_CFG);
if (err)
return err;
}
- airoha_qdma_rmw(port->qdma, REG_CHAN_QOS_MODE(channel >> 3),
+ airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
CHAN_QOS_MODE_MASK(channel),
__field_prep(CHAN_QOS_MODE_MASK(channel), mode));
@@ -2312,9 +2310,9 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
+ u64 cpu_tx_packets = airoha_qdma_rr(dev->qdma,
REG_CNTR_VAL(channel << 1));
- u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
+ u64 fwd_tx_packets = airoha_qdma_rr(dev->qdma,
REG_CNTR_VAL((channel << 1) + 1));
u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
(fwd_tx_packets - port->fwd_tx_packets);
@@ -2578,17 +2576,16 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
u32 bucket_size)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
int i, err;
for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
- err = airoha_qdma_set_trtcm_config(port->qdma, channel,
+ err = airoha_qdma_set_trtcm_config(dev->qdma, channel,
REG_EGRESS_TRTCM_CFG, i,
!!rate, TRTCM_METER_MODE);
if (err)
return err;
- err = airoha_qdma_set_trtcm_token_bucket(port->qdma, channel,
+ err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
REG_EGRESS_TRTCM_CFG,
i, rate, bucket_size);
if (err)
@@ -2638,11 +2635,11 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
return 0;
}
-static int airoha_qdma_set_rx_meter(struct airoha_gdm_port *port,
+static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
u32 rate, u32 bucket_size,
enum trtcm_unit_type unit_type)
{
- struct airoha_qdma *qdma = port->qdma;
+ struct airoha_qdma *qdma = dev->qdma;
int i;
for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
@@ -2721,7 +2718,6 @@ static int airoha_dev_tc_matchall(struct net_device *netdev,
{
enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
u32 rate = 0, bucket_size = 0;
switch (f->command) {
@@ -2746,7 +2742,7 @@ static int airoha_dev_tc_matchall(struct net_device *netdev,
fallthrough;
}
case TC_CLSMATCHALL_DESTROY:
- return airoha_qdma_set_rx_meter(port, rate, bucket_size,
+ return airoha_qdma_set_rx_meter(dev, rate, bucket_size,
unit_type);
default:
return -EOPNOTSUPP;
@@ -2758,8 +2754,7 @@ static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
{
struct net_device *netdev = cb_priv;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
- struct airoha_eth *eth = port->qdma->eth;
+ struct airoha_eth *eth = dev->eth;
if (!tc_can_offload(netdev))
return -EOPNOTSUPP;
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 21f149aa9c6b..21d308b1f087 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -535,12 +535,12 @@ struct airoha_qdma {
struct airoha_gdm_dev {
struct airoha_gdm_port *port;
+ struct airoha_qdma *qdma;
struct net_device *dev;
struct airoha_eth *eth;
};
struct airoha_gdm_port {
- struct airoha_qdma *qdma;
struct airoha_gdm_dev *dev;
int id;
int nbq;
@@ -664,19 +664,18 @@ static inline bool airoha_is_7583(struct airoha_eth *eth)
return eth->soc->version == 0x7583;
}
-int airoha_get_fe_port(struct airoha_gdm_port *port);
+int airoha_get_fe_port(struct airoha_gdm_dev *dev);
bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
struct airoha_gdm_dev *dev);
-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
- u8 fport);
+void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
u16 hash, bool rx_wlan);
int airoha_ppe_setup_tc_block_cb(struct airoha_ppe_dev *dev, void *type_data);
int airoha_ppe_init(struct airoha_eth *eth);
void airoha_ppe_deinit(struct airoha_eth *eth);
-void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port);
+void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev);
u32 airoha_ppe_get_total_num_entries(struct airoha_ppe *ppe);
struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,
u32 hash);
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index af7af4097b98..22f5f1bae730 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -84,9 +84,9 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
AIROHA_FOE_IB1_BIND_TIMESTAMP);
}
-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id, u8 fport)
+void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
{
- struct airoha_qdma *qdma = port->qdma;
+ struct airoha_qdma *qdma = dev->qdma;
struct airoha_eth *eth = qdma->eth;
u8 qdma_id = qdma - ð->qdma[0];
u32 fe_cpu_port;
@@ -180,8 +180,8 @@ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
if (!port)
continue;
- airoha_ppe_set_cpu_port(port, i,
- airoha_get_fe_port(port));
+ airoha_ppe_set_cpu_port(port->dev, i,
+ airoha_get_fe_port(port->dev));
}
}
}
@@ -1473,11 +1473,12 @@ void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
airoha_ppe_foe_insert_entry(ppe, skb, hash, rx_wlan);
}
-void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
+void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev)
{
- struct airoha_eth *eth = port->qdma->eth;
- struct net_device *dev = port->dev->dev;
- const u8 *addr = dev->dev_addr;
+ struct airoha_gdm_port *port = dev->port;
+ struct net_device *netdev = dev->dev;
+ struct airoha_eth *eth = dev->eth;
+ const u8 *addr = netdev->dev_addr;
u32 val;
val = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | addr[5];
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 02/10] net: airoha: Introduce airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
As a result multiple net_devices can connect to the same GDM{3,4} port
and there is a theoretical "1:n" relation between GDM port and
net_devices.
Introduce airoha_gdm_dev struct to collect net_device related info (e.g.
net_device and external phy pointer). Please note this is just a
preliminary patch and we are still supporting a single net_device for
each GDM port. Subsequent patches will add support for multiple net_devices
connected to the same GDM port.
Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 309 ++++++++++++++++++-------------
drivers/net/ethernet/airoha/airoha_eth.h | 13 +-
drivers/net/ethernet/airoha/airoha_ppe.c | 17 +-
3 files changed, 203 insertions(+), 136 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index f71fb18197ec..4af64f182968 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -599,6 +599,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
struct airoha_qdma_desc *desc = &q->desc[q->tail];
u32 hash, reason, msg1, desc_ctrl;
struct airoha_gdm_port *port;
+ struct net_device *netdev;
int data_len, len, p;
struct page *page;
@@ -626,6 +627,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
goto free_frag;
port = eth->ports[p];
+ netdev = port->dev->dev;
if (!q->skb) { /* first buffer */
q->skb = napi_build_skb(e->buf, q->buf_size);
if (!q->skb)
@@ -633,8 +635,8 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
__skb_put(q->skb, len);
skb_mark_for_recycle(q->skb);
- q->skb->dev = port->dev;
- q->skb->protocol = eth_type_trans(q->skb, port->dev);
+ q->skb->dev = netdev;
+ q->skb->protocol = eth_type_trans(q->skb, netdev);
q->skb->ip_summed = CHECKSUM_UNNECESSARY;
skb_record_rx_queue(q->skb, qid);
} else { /* scattered frame */
@@ -652,7 +654,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
continue;
- if (netdev_uses_dsa(port->dev)) {
+ if (netdev_uses_dsa(netdev)) {
/* PPE module requires untagged packets to work
* properly and it provides DSA port index via the
* DMA descriptor. Report DSA tag to the DSA stack
@@ -846,6 +848,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
+ struct airoha_gdm_dev *dev;
int j;
if (!port)
@@ -854,11 +857,12 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
if (port->qdma != qdma)
continue;
- for (j = 0; j < port->dev->num_tx_queues; j++) {
+ dev = port->dev;
+ for (j = 0; j < dev->dev->num_tx_queues; j++) {
if (airoha_qdma_get_txq(qdma, j) != qid)
continue;
- netif_wake_subqueue(port->dev, j);
+ netif_wake_subqueue(dev->dev, j);
}
}
q->txq_stopped = false;
@@ -1698,19 +1702,20 @@ static void airoha_update_hw_stats(struct airoha_gdm_port *port)
spin_unlock(&port->stats.lock);
}
-static int airoha_dev_open(struct net_device *dev)
+static int airoha_dev_open(struct net_device *netdev)
{
- int err, len = ETH_HLEN + dev->mtu + ETH_FCS_LEN;
- struct airoha_gdm_port *port = netdev_priv(dev);
+ int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_qdma *qdma = port->qdma;
u32 pse_port = FE_PSE_PORT_PPE1;
- netif_tx_start_all_queues(dev);
+ netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(port, true);
if (err)
return err;
- if (netdev_uses_dsa(dev))
+ if (netdev_uses_dsa(netdev))
airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
else
@@ -1738,16 +1743,17 @@ static int airoha_dev_open(struct net_device *dev)
return 0;
}
-static int airoha_dev_stop(struct net_device *dev)
+static int airoha_dev_stop(struct net_device *netdev)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_qdma *qdma = port->qdma;
int i;
- netif_tx_disable(dev);
+ netif_tx_disable(netdev);
airoha_set_vip_for_gdm_port(port, false);
- for (i = 0; i < dev->num_tx_queues; i++)
- netdev_tx_reset_subqueue(dev, i);
+ for (i = 0; i < netdev->num_tx_queues; i++)
+ netdev_tx_reset_subqueue(netdev, i);
airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
FE_PSE_PORT_DROP);
@@ -1768,16 +1774,17 @@ static int airoha_dev_stop(struct net_device *dev)
return 0;
}
-static int airoha_dev_set_macaddr(struct net_device *dev, void *p)
+static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
int err;
- err = eth_mac_addr(dev, p);
+ err = eth_mac_addr(netdev, p);
if (err)
return err;
- airoha_set_macaddr(port, dev->dev_addr);
+ airoha_set_macaddr(port, netdev->dev_addr);
return 0;
}
@@ -1841,16 +1848,17 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
return 0;
}
-static int airoha_dev_init(struct net_device *dev)
+static int airoha_dev_init(struct net_device *netdev)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
- struct airoha_eth *eth = port->eth;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
int i;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
port->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
- port->dev->irq = port->qdma->irq_banks[0].irq;
- airoha_set_macaddr(port, dev->dev_addr);
+ dev->dev->irq = port->qdma->irq_banks[0].irq;
+ airoha_set_macaddr(port, netdev->dev_addr);
switch (port->id) {
case AIROHA_GDM3_IDX:
@@ -1875,10 +1883,11 @@ static int airoha_dev_init(struct net_device *dev)
return 0;
}
-static void airoha_dev_get_stats64(struct net_device *dev,
+static void airoha_dev_get_stats64(struct net_device *netdev,
struct rtnl_link_stats64 *storage)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
unsigned int start;
airoha_update_hw_stats(port);
@@ -1897,36 +1906,39 @@ static void airoha_dev_get_stats64(struct net_device *dev,
} while (u64_stats_fetch_retry(&port->stats.syncp, start));
}
-static int airoha_dev_change_mtu(struct net_device *dev, int mtu)
+static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = port->qdma->eth;
u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
GDM_LONG_LEN_MASK,
FIELD_PREP(GDM_LONG_LEN_MASK, len));
- WRITE_ONCE(dev->mtu, mtu);
+ WRITE_ONCE(netdev->mtu, mtu);
return 0;
}
-static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
+static u16 airoha_dev_select_queue(struct net_device *netdev,
+ struct sk_buff *skb,
struct net_device *sb_dev)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
int queue, channel;
/* For dsa device select QoS channel according to the dsa user port
* index, rely on port id otherwise. Select QoS queue based on the
* skb priority.
*/
- channel = netdev_uses_dsa(dev) ? skb_get_queue_mapping(skb) : port->id;
+ channel = netdev_uses_dsa(netdev) ? skb_get_queue_mapping(skb) : port->id;
channel = channel % AIROHA_NUM_QOS_CHANNELS;
queue = (skb->priority - 1) % AIROHA_NUM_QOS_QUEUES; /* QoS queue */
queue = channel * AIROHA_NUM_QOS_QUEUES + queue;
- return queue < dev->num_tx_queues ? queue : 0;
+ return queue < netdev->num_tx_queues ? queue : 0;
}
static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
@@ -1990,9 +2002,10 @@ int airoha_get_fe_port(struct airoha_gdm_port *port)
}
static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
- struct net_device *dev)
+ struct net_device *netdev)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_qdma *qdma = port->qdma;
u32 nr_frags, tag, msg0, msg1, len;
struct airoha_queue_entry *e;
@@ -2005,7 +2018,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
u8 fport;
qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
- tag = airoha_get_dsa_tag(skb, dev);
+ tag = airoha_get_dsa_tag(skb, netdev);
msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
qid / AIROHA_NUM_QOS_QUEUES) |
@@ -2041,7 +2054,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
spin_lock_bh(&q->lock);
- txq = skb_get_tx_queue(dev, skb);
+ txq = skb_get_tx_queue(netdev, skb);
nr_frags = 1 + skb_shinfo(skb)->nr_frags;
if (q->queued + nr_frags >= q->ndesc) {
@@ -2065,9 +2078,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
dma_addr_t addr;
u32 val;
- addr = dma_map_single(dev->dev.parent, data, len,
+ addr = dma_map_single(netdev->dev.parent, data, len,
DMA_TO_DEVICE);
- if (unlikely(dma_mapping_error(dev->dev.parent, addr)))
+ if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
goto error_unmap;
list_move_tail(&e->list, &tx_list);
@@ -2116,7 +2129,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
error_unmap:
list_for_each_entry(e, &tx_list, list) {
- dma_unmap_single(dev->dev.parent, e->dma_addr, e->dma_len,
+ dma_unmap_single(netdev->dev.parent, e->dma_addr, e->dma_len,
DMA_TO_DEVICE);
e->dma_addr = 0;
}
@@ -2125,25 +2138,27 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
spin_unlock_bh(&q->lock);
error:
dev_kfree_skb_any(skb);
- dev->stats.tx_dropped++;
+ netdev->stats.tx_dropped++;
return NETDEV_TX_OK;
}
-static void airoha_ethtool_get_drvinfo(struct net_device *dev,
+static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *info)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = port->qdma->eth;
strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
}
-static void airoha_ethtool_get_mac_stats(struct net_device *dev,
+static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
struct ethtool_eth_mac_stats *stats)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
unsigned int start;
airoha_update_hw_stats(port);
@@ -2171,11 +2186,12 @@ static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
};
static void
-airoha_ethtool_get_rmon_stats(struct net_device *dev,
+airoha_ethtool_get_rmon_stats(struct net_device *netdev,
struct ethtool_rmon_stats *stats,
const struct ethtool_rmon_hist_range **ranges)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_hw_stats *hw_stats = &port->stats;
unsigned int start;
@@ -2200,11 +2216,12 @@ airoha_ethtool_get_rmon_stats(struct net_device *dev,
} while (u64_stats_fetch_retry(&port->stats.syncp, start));
}
-static int airoha_qdma_set_chan_tx_sched(struct net_device *dev,
+static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
int channel, enum tx_sched_mode mode,
const u16 *weights, u8 n_weights)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
int i;
for (i = 0; i < AIROHA_NUM_TX_RING; i++)
@@ -2289,10 +2306,12 @@ static int airoha_qdma_set_tx_ets_sched(struct net_device *dev, int channel,
ARRAY_SIZE(w));
}
-static int airoha_qdma_get_tx_ets_stats(struct net_device *dev, int channel,
+static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
struct tc_ets_qopt_offload *opt)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+
u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
REG_CNTR_VAL(channel << 1));
u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
@@ -2554,11 +2573,12 @@ static int airoha_qdma_set_trtcm_token_bucket(struct airoha_qdma *qdma,
mode, val);
}
-static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
+static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
int channel, u32 rate,
u32 bucket_size)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
int i, err;
for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
@@ -2578,20 +2598,22 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
return 0;
}
-static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
+static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
struct tc_htb_qopt_offload *opt)
{
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
- int err, num_tx_queues = dev->real_num_tx_queues;
- struct airoha_gdm_port *port = netdev_priv(dev);
+ int err, num_tx_queues = netdev->real_num_tx_queues;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
return -EINVAL;
}
- err = airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
+ err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
+ opt->quantum);
if (err) {
NL_SET_ERR_MSG_MOD(opt->extack,
"failed configuring htb offload");
@@ -2601,9 +2623,10 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
if (opt->command == TC_HTB_NODE_MODIFY)
return 0;
- err = netif_set_real_num_tx_queues(dev, num_tx_queues + 1);
+ err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
if (err) {
- airoha_qdma_set_tx_rate_limit(dev, channel, 0, opt->quantum);
+ airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
+ opt->quantum);
NL_SET_ERR_MSG_MOD(opt->extack,
"failed setting real_num_tx_queues");
return err;
@@ -2693,11 +2716,12 @@ static int airoha_tc_matchall_act_validate(struct tc_cls_matchall_offload *f)
return 0;
}
-static int airoha_dev_tc_matchall(struct net_device *dev,
+static int airoha_dev_tc_matchall(struct net_device *netdev,
struct tc_cls_matchall_offload *f)
{
enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
u32 rate = 0, bucket_size = 0;
switch (f->command) {
@@ -2732,18 +2756,19 @@ static int airoha_dev_tc_matchall(struct net_device *dev,
static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
void *type_data, void *cb_priv)
{
- struct net_device *dev = cb_priv;
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct net_device *netdev = cb_priv;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = port->qdma->eth;
- if (!tc_can_offload(dev))
+ if (!tc_can_offload(netdev))
return -EOPNOTSUPP;
switch (type) {
case TC_SETUP_CLSFLOWER:
return airoha_ppe_setup_tc_block_cb(ð->ppe->dev, type_data);
case TC_SETUP_CLSMATCHALL:
- return airoha_dev_tc_matchall(dev, type_data);
+ return airoha_dev_tc_matchall(netdev, type_data);
default:
return -EOPNOTSUPP;
}
@@ -2790,47 +2815,51 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
}
}
-static void airoha_tc_remove_htb_queue(struct net_device *dev, int queue)
+static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
- netif_set_real_num_tx_queues(dev, dev->real_num_tx_queues - 1);
- airoha_qdma_set_tx_rate_limit(dev, queue + 1, 0, 0);
+ netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
+ airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
clear_bit(queue, port->qos_sq_bmap);
}
-static int airoha_tc_htb_delete_leaf_queue(struct net_device *dev,
+static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
struct tc_htb_qopt_offload *opt)
{
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
if (!test_bit(channel, port->qos_sq_bmap)) {
NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
return -EINVAL;
}
- airoha_tc_remove_htb_queue(dev, channel);
+ airoha_tc_remove_htb_queue(netdev, channel);
return 0;
}
-static int airoha_tc_htb_destroy(struct net_device *dev)
+static int airoha_tc_htb_destroy(struct net_device *netdev)
{
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
int q;
for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
- airoha_tc_remove_htb_queue(dev, q);
+ airoha_tc_remove_htb_queue(netdev, q);
return 0;
}
-static int airoha_tc_get_htb_get_leaf_queue(struct net_device *dev,
+static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
struct tc_htb_qopt_offload *opt)
{
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
if (!test_bit(channel, port->qos_sq_bmap)) {
NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
@@ -2866,8 +2895,8 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
return 0;
}
-static int airoha_dev_tc_setup(struct net_device *dev, enum tc_setup_type type,
- void *type_data)
+static int airoha_dev_tc_setup(struct net_device *dev,
+ enum tc_setup_type type, void *type_data)
{
switch (type) {
case TC_SETUP_QDISC_ETS:
@@ -2933,25 +2962,81 @@ static void airoha_metadata_dst_free(struct airoha_gdm_port *port)
}
}
-bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
- struct airoha_gdm_port *port)
+bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+ struct airoha_gdm_dev *dev)
{
int i;
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
- if (eth->ports[i] == port)
+ struct airoha_gdm_port *port = eth->ports[i];
+
+ if (!port)
+ continue;
+
+ if (port->dev == dev)
return true;
}
return false;
}
+static int airoha_alloc_gdm_device(struct airoha_eth *eth,
+ struct airoha_gdm_port *port,
+ struct device_node *np)
+{
+ struct airoha_gdm_dev *dev;
+ struct net_device *netdev;
+ int err;
+
+ netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
+ AIROHA_NUM_NETDEV_TX_RINGS,
+ AIROHA_NUM_RX_RING);
+ if (!netdev) {
+ dev_err(eth->dev, "alloc_etherdev failed\n");
+ return -ENOMEM;
+ }
+
+ netdev->netdev_ops = &airoha_netdev_ops;
+ netdev->ethtool_ops = &airoha_ethtool_ops;
+ netdev->max_mtu = AIROHA_MAX_MTU;
+ netdev->watchdog_timeo = 5 * HZ;
+ netdev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO6 |
+ NETIF_F_IPV6_CSUM | NETIF_F_SG | NETIF_F_TSO |
+ NETIF_F_HW_TC;
+ netdev->features |= netdev->hw_features;
+ netdev->vlan_features = netdev->hw_features;
+ netdev->dev.of_node = np;
+ SET_NETDEV_DEV(netdev, eth->dev);
+
+ /* reserve hw queues for HTB offloading */
+ err = netif_set_real_num_tx_queues(netdev, AIROHA_NUM_TX_RING);
+ if (err)
+ return err;
+
+ err = of_get_ethdev_address(np, netdev);
+ if (err) {
+ if (err == -EPROBE_DEFER)
+ return err;
+
+ eth_hw_addr_random(netdev);
+ dev_info(eth->dev, "generated random MAC address %pM\n",
+ netdev->dev_addr);
+ }
+
+ dev = netdev_priv(netdev);
+ dev->dev = netdev;
+ dev->port = port;
+ port->dev = dev;
+ dev->eth = eth;
+
+ return 0;
+}
+
static int airoha_alloc_gdm_port(struct airoha_eth *eth,
struct device_node *np)
{
const __be32 *id_ptr = of_get_property(np, "reg", NULL);
struct airoha_gdm_port *port;
- struct net_device *dev;
int err, p;
u32 id;
@@ -2973,53 +3058,22 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
return -EINVAL;
}
- dev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*port),
- AIROHA_NUM_NETDEV_TX_RINGS,
- AIROHA_NUM_RX_RING);
- if (!dev) {
- dev_err(eth->dev, "alloc_etherdev failed\n");
+ port = devm_kzalloc(eth->dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
return -ENOMEM;
- }
-
- dev->netdev_ops = &airoha_netdev_ops;
- dev->ethtool_ops = &airoha_ethtool_ops;
- dev->max_mtu = AIROHA_MAX_MTU;
- dev->watchdog_timeo = 5 * HZ;
- dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
- NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
- NETIF_F_SG | NETIF_F_TSO |
- NETIF_F_HW_TC;
- dev->features |= dev->hw_features;
- dev->vlan_features = dev->hw_features;
- dev->dev.of_node = np;
- SET_NETDEV_DEV(dev, eth->dev);
-
- /* reserve hw queues for HTB offloading */
- err = netif_set_real_num_tx_queues(dev, AIROHA_NUM_TX_RING);
- if (err)
- return err;
-
- err = of_get_ethdev_address(np, dev);
- if (err) {
- if (err == -EPROBE_DEFER)
- return err;
-
- eth_hw_addr_random(dev);
- dev_info(eth->dev, "generated random MAC address %pM\n",
- dev->dev_addr);
- }
- port = netdev_priv(dev);
u64_stats_init(&port->stats.syncp);
spin_lock_init(&port->stats.lock);
- port->eth = eth;
- port->dev = dev;
port->id = id;
/* XXX: Read nbq from DTS */
port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
eth->ports[p] = port;
- return airoha_metadata_dst_alloc(port);
+ err = airoha_metadata_dst_alloc(port);
+ if (err)
+ return err;
+
+ return airoha_alloc_gdm_device(eth, port, np);
}
static int airoha_register_gdm_devices(struct airoha_eth *eth)
@@ -3033,7 +3087,7 @@ static int airoha_register_gdm_devices(struct airoha_eth *eth)
if (!port)
continue;
- err = register_netdev(port->dev);
+ err = register_netdev(port->dev->dev);
if (err)
return err;
}
@@ -3142,12 +3196,14 @@ static int airoha_probe(struct platform_device *pdev)
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
+ struct airoha_gdm_dev *dev;
if (!port)
continue;
- if (port->dev->reg_state == NETREG_REGISTERED)
- unregister_netdev(port->dev);
+ dev = port->dev;
+ if (dev && dev->dev->reg_state == NETREG_REGISTERED)
+ unregister_netdev(dev->dev);
airoha_metadata_dst_free(port);
}
airoha_hw_cleanup(eth);
@@ -3168,11 +3224,14 @@ static void airoha_remove(struct platform_device *pdev)
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
+ struct airoha_gdm_dev *dev;
if (!port)
continue;
- unregister_netdev(port->dev);
+ dev = port->dev;
+ if (dev)
+ unregister_netdev(dev->dev);
airoha_metadata_dst_free(port);
}
airoha_hw_cleanup(eth);
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 58530d096de7..21f149aa9c6b 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -533,10 +533,15 @@ struct airoha_qdma {
struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
};
+struct airoha_gdm_dev {
+ struct airoha_gdm_port *port;
+ struct net_device *dev;
+ struct airoha_eth *eth;
+};
+
struct airoha_gdm_port {
struct airoha_qdma *qdma;
- struct airoha_eth *eth;
- struct net_device *dev;
+ struct airoha_gdm_dev *dev;
int id;
int nbq;
@@ -660,8 +665,8 @@ static inline bool airoha_is_7583(struct airoha_eth *eth)
}
int airoha_get_fe_port(struct airoha_gdm_port *port);
-bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
- struct airoha_gdm_port *port);
+bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+ struct airoha_gdm_dev *dev);
void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
u8 fport);
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 26da519236bf..af7af4097b98 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -298,12 +298,12 @@ static void airoha_ppe_foe_set_bridge_addrs(struct airoha_foe_bridge *br,
static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
struct airoha_foe_entry *hwe,
- struct net_device *dev, int type,
+ struct net_device *netdev, int type,
struct airoha_flow_data *data,
int l4proto)
{
u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
- int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&dev);
+ int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev);
struct airoha_foe_mac_info_common *l2;
u8 smac_id = 0xf;
@@ -319,10 +319,11 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
hwe->ib1 = val;
val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f);
- if (dev) {
+ if (netdev) {
struct airoha_wdma_info info = {};
- if (!airoha_ppe_get_wdma_info(dev, data->eth.h_dest, &info)) {
+ if (!airoha_ppe_get_wdma_info(netdev, data->eth.h_dest,
+ &info)) {
val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) |
FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT,
FE_PSE_PORT_CDM4);
@@ -332,12 +333,14 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
FIELD_PREP(AIROHA_FOE_MAC_WDMA_WCID,
info.wcid);
} else {
- struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port;
u8 pse_port, channel;
- if (!airoha_is_valid_gdm_port(eth, port))
+ if (!airoha_is_valid_gdm_dev(eth, dev))
return -EINVAL;
+ port = dev->port;
if (dsa_port >= 0 || eth->ports[1])
pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
: port->id;
@@ -1473,7 +1476,7 @@ void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
{
struct airoha_eth *eth = port->qdma->eth;
- struct net_device *dev = port->dev;
+ struct net_device *dev = port->dev->dev;
const u8 *addr = dev->dev_addr;
u32 val;
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 01/10] dt-bindings: net: airoha: Add EN7581 ethernet-ports properties
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree
In-Reply-To: <20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org>
EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
As a result multiple net_devices can connect to the same GDM{3,4} port
and there is a theoretical "1:n" relation between GDM ports and
net_devices.
Introduce the ethernet-port property in order to model a given net_device
that is connected via the external arbiter to the GDM{3,4} port (that
is represented by the ethernet property. Please note GDM1 or GDM2 does not
support the connection with the external arbiter and are represented
by ethernet property.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
.../devicetree/bindings/net/airoha,en7581-eth.yaml | 52 +++++++++++++++++++++-
1 file changed, 51 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml b/Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
index fbe2ddcdd909..73ba37872247 100644
--- a/Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
+++ b/Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
@@ -130,6 +130,38 @@ patternProperties:
maximum: 4
description: GMAC port identifier
+ '#address-cells':
+ const: 1
+ '#size-cells':
+ const: 0
+
+ allOf:
+ - if:
+ properties:
+ reg:
+ items:
+ - enum:
+ - 3
+ - 4
+ then:
+ patternProperties:
+ "^ethernet-port@[0-5]$":
+ type: object
+ unevaluatedProperties: false
+ description: External ethernet port ID available on the GDM port
+
+ properties:
+ compatible:
+ const: airoha,eth-port
+
+ reg:
+ maxItems: 1
+ description: External ethernet port identifier
+
+ required:
+ - compatible
+ - reg
+
required:
- reg
- compatible
@@ -191,9 +223,27 @@ examples:
#address-cells = <1>;
#size-cells = <0>;
- mac: ethernet@1 {
+ mac1: ethernet@1 {
compatible = "airoha,eth-mac";
reg = <1>;
};
+
+ mac4: ethernet@4 {
+ compatible = "airoha,eth-mac";
+ reg = <4>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-port@0 {
+ compatible = "airoha,eth-port";
+ reg = <0>;
+ };
+
+ ethernet-port@1 {
+ compatible = "airoha,eth-port";
+ reg = <1>;
+ };
+ };
};
};
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v4 00/10] net: airoha: Support multiple net_devices connected to the same GDM port
From: Lorenzo Bianconi @ 2026-05-07 21:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu, Madhur Agrawal
EN7581 or AN7583 SoCs support connecting multiple external SerDes (e.g.
Ethernet or USB SerDes) to GDM3 or GDM4 ports via a hw arbiter that
manages the traffic in a TDM manner. As a result multiple net_devices can
connect to the same GDM{3,4} port and there is a theoretical "1:n"
relation between GDM ports and net_devices.
┌─────────────────────────────────┐
│ │ ┌──────┐
│ P1 GDM1 ├────►MT7530│
│ │ └──────┘
│ │ ETH0 (DSA conduit)
│ │
│ PSE/FE │
│ │
│ │
│ │ ┌─────┐
│ P0 CDM1 ├────►QDMA0│
│ P4 P9 GDM4 │ └─────┘
└──┬─────────────────────────┬────┘
│ │
┌──▼──┐ ┌────▼────┐
│ PPE │ │ ARB │
└─────┘ └─┬─────┬─┘
│ │
┌──▼──┐┌─▼───┐
│ ETH ││ USB │
└─────┘└─────┘
ETH1 ETH2
This series introduces support for multiple net_devices connected to the
same Frame Engine (FE) GDM port (GDM3 or GDM4) via an external hw
arbiter. Please note GDM1 or GDM2 does not support the connection with
the external arbiter.
---
Changes in v4:
- Make ethernet-port property available just for GDM3 and GDM4 in DTS
specification
- Move cpu_tx_packets, fwd_tx_packets qos_sq_bmap fields in airoha_qdma
struct
- Fix of_node leak removing the net_device in airoha_remove() or
airoha_probe() error path
- Fix nbq backward compatibility
- Link to v3: https://lore.kernel.org/r/20260406-airoha-eth-multi-serdes-v3-0-ab6ea49d59ff@kernel.org
Changes in v3:
- Fix MTU and VIP configuration when the GDM port is shared between
multiple net_devices.
- Add sanity check for nbq parameter.
- Add missing of_node_get() for net_device np node.
- Check if GDM port is shared before decresing device MTU.
- Move port forward configuration in airoha_dev_stop() before
configuring DMA tx/rx engine.
- Introduce PRIV_FLAG_WAN parameter.
- Link to v2: https://lore.kernel.org/r/20260401-airoha-eth-multi-serdes-v2-0-ac427ae4beeb@kernel.org
Changes in v2:
- Rename multiplexer in arbiter in the commit logs.
- Rebase on top of net-next main branch.
- Add missing PPE cpu port configuration for GDM2 when loopback is
enabled.
- Link to v1: https://lore.kernel.org/r/20260329-airoha-eth-multi-serdes-v1-0-00f52dc360ca@kernel.org
---
Lorenzo Bianconi (10):
dt-bindings: net: airoha: Add EN7581 ethernet-ports properties
net: airoha: Introduce airoha_gdm_dev struct
net: airoha: Move airoha_qdma pointer in airoha_gdm_dev struct
net: airoha: Rely on airoha_gdm_dev pointer in airhoa_is_lan_gdm_port()
net: airoha: Move qos_sq_bmap in airoha_qdma struct
net: airoha: Move {cpu,fwd}_tx_packets in airoha_qdma struct
net: airoha: Support multiple net_devices for a single FE GDM port
net: airoha: Do not stop GDM port if it is shared
net: airoha: Introduce WAN device flag
net: airoha: Support multiple LAN/WAN interfaces for hw MAC address configuration
.../devicetree/bindings/net/airoha,en7581-eth.yaml | 52 +-
drivers/net/ethernet/airoha/airoha_eth.c | 778 +++++++++++++++------
drivers/net/ethernet/airoha/airoha_eth.h | 49 +-
drivers/net/ethernet/airoha/airoha_ppe.c | 45 +-
4 files changed, 657 insertions(+), 267 deletions(-)
---
base-commit: 6a4c4656b0d2d4056a1f0c35442db4e8a5cf8021
change-id: 20260324-airoha-eth-multi-serdes-fb4b556ee756
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply
* Re: [PATCH net-next v6 2/3] gve: make nic clock reads thread safe
From: Jacob Keller @ 2026-05-07 21:15 UTC (permalink / raw)
To: Harshitha Ramamurthy, netdev
Cc: joshwash, andrew+netdev, davem, edumazet, kuba, pabeni,
richardcochran, jstultz, tglx, sboyd, willemb, nktgrg, jfraker,
ziweixiao, maolson, jordanrhee, thostet, alok.a.tiwari,
pkaligineedi, horms, dwmw2, yyd, jefrogers, linux-kernel
In-Reply-To: <20260507211304.3046526-3-hramamurthy@google.com>
On 5/7/2026 2:13 PM, Harshitha Ramamurthy wrote:
> From: Ankit Garg <nktgrg@google.com>
>
> Add a mutex to protect the shared DMA buffer that receives NIC
> timestamp reports. The NIC timestamp will be read from two different
> threads: the periodic worker and upcoming `gettimex64`.
>
> Move clock registration to the last step of initialization to ensure
> that all data needed by the clock module is initialized before
> the clock is exposed to usermode.
>
> Reviewed-by: Joshua Washington <joshwash@google.com>
> Signed-off-by: Ankit Garg <nktgrg@google.com>
> Signed-off-by: Jordan Rhee <jordanrhee@google.com>
> Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
> ---
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
^ permalink raw reply
* Re: [PATCH net-next v6 1/3] gve: skip error logging for retryable AdminQ commands
From: Jacob Keller @ 2026-05-07 21:14 UTC (permalink / raw)
To: Harshitha Ramamurthy, netdev
Cc: joshwash, andrew+netdev, davem, edumazet, kuba, pabeni,
richardcochran, jstultz, tglx, sboyd, willemb, nktgrg, jfraker,
ziweixiao, maolson, jordanrhee, thostet, alok.a.tiwari,
pkaligineedi, horms, dwmw2, yyd, jefrogers, linux-kernel
In-Reply-To: <20260507211304.3046526-2-hramamurthy@google.com>
On 5/7/2026 2:13 PM, Harshitha Ramamurthy wrote:
> From: Jordan Rhee <jordanrhee@google.com>
>
> AdminQ commands may return -EAGAIN under certain transient conditions.
> These commands are intended to be retried by the driver, so logging
> a formal error to the system log is misleading and creates
> unnecessary noise.
>
> Modify the logging logic to skip the error message when the result
> is -EAGAIN, and move logging to dev_err_ratelimited() to avoid
> spamming the log.
>
Excellent!
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
> Reviewed-by: Joshua Washington <joshwash@google.com>
> Signed-off-by: Jordan Rhee <jordanrhee@google.com>
> Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
> ---
> Changes in v4:
> - call out change to dev_err_ratelimited() in the commit message (Jacob Keller)
> - remove extra print when adminQ status is GVE_ADMINQ_COMMAND_UNSET (Jacob Keller)
> ---
> drivers/net/ethernet/google/gve/gve_adminq.c | 27 +++++++++++++++-----
> 1 file changed, 20 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c
> index 08587bf40ed4..a65b14835aa0 100644
> --- a/drivers/net/ethernet/google/gve/gve_adminq.c
> +++ b/drivers/net/ethernet/google/gve/gve_adminq.c
> @@ -416,16 +416,10 @@ static bool gve_adminq_wait_for_cmd(struct gve_priv *priv, u32 prod_cnt)
>
> static int gve_adminq_parse_err(struct gve_priv *priv, u32 status)
> {
> - if (status != GVE_ADMINQ_COMMAND_PASSED &&
> - status != GVE_ADMINQ_COMMAND_UNSET) {
> - dev_err(&priv->pdev->dev, "AQ command failed with status %d\n", status);
> - priv->adminq_cmd_fail++;
> - }
> switch (status) {
> case GVE_ADMINQ_COMMAND_PASSED:
> return 0;
> case GVE_ADMINQ_COMMAND_UNSET:
> - dev_err(&priv->pdev->dev, "parse_aq_err: err and status both unset, this should not be possible.\n");
> return -EINVAL;
> case GVE_ADMINQ_COMMAND_ERROR_ABORTED:
> case GVE_ADMINQ_COMMAND_ERROR_CANCELLED:
> @@ -455,6 +449,16 @@ static int gve_adminq_parse_err(struct gve_priv *priv, u32 status)
> }
> }
>
> +static bool gve_adminq_is_retryable(enum gve_adminq_opcodes opcode)
> +{
> + switch (opcode) {
> + case GVE_ADMINQ_REPORT_NIC_TIMESTAMP:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> /* Flushes all AQ commands currently queued and waits for them to complete.
> * If there are failures, it will return the first error.
> */
> @@ -482,9 +486,18 @@ static int gve_adminq_kick_and_wait(struct gve_priv *priv)
> cmd = &priv->adminq[i & priv->adminq_mask];
> status = be32_to_cpu(READ_ONCE(cmd->status));
> err = gve_adminq_parse_err(priv, status);
> - if (err)
> + if (err) {
> + enum gve_adminq_opcodes opcode =
> + be32_to_cpu(READ_ONCE(cmd->opcode));
> + priv->adminq_cmd_fail++;
> + if (!gve_adminq_is_retryable(opcode) || err != -EAGAIN)
> + dev_err_ratelimited(&priv->pdev->dev,
> + "AQ command %d failed with status %d\n",
> + opcode, status);
> +
> // Return the first error if we failed.
> return err;
> + }
> }
>
> return 0;
^ permalink raw reply
* Re: [PATCH net 09/13] ice: fix setting RSS VSI hash for E830
From: Jacob Keller @ 2026-05-07 21:13 UTC (permalink / raw)
To: Marcin Szycik, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Piotr Kwapulinski,
Aleksandr Loktionov, Arkadiusz Kubalewski, Maciej Fijalkowski,
Joshua Hay, Madhu Chittim, Willem de Bruijn, Dave Ertman,
Ivan Vecera, Grzegorz Nitka
Cc: netdev, stable
In-Reply-To: <3abb7d8f-82eb-46e8-8243-fc6f596ab84c@linux.intel.com>
On 5/7/2026 9:59 AM, Marcin Szycik wrote:
>
>
> On 07.05.2026 13:47, Marcin Szycik wrote:
>>
>>
>> On 06.05.2026 23:06, Jacob Keller wrote:
>>> On 5/4/2026 10:14 PM, Jacob Keller wrote:
>>>> From: Marcin Szycik <marcin.szycik@linux.intel.com>
>>>>
>>>> ice_set_rss_hfunc() performs a VSI update, in which it sets hashing
>>>> function, leaving other VSI options unchanged. However, ::q_opt_flags is
>>>> mistakenly set to the value of another field, instead of its original
>>>> value, probably due to a typo. What happens next is hardware-dependent:
>>>>
>>>> On E810, only the first bit is meaningful (see
>>>> ICE_AQ_VSI_Q_OPT_PE_FLTR_EN) and can potentially end up in a different
>>>> state than before VSI update.
>>>>
>>>> On E830, some of the remaining bits are not reserved. Setting them
>>>> to some unrelated values can cause the firmware to reject the update
>>>> because of invalid settings, or worse - succeed.
>>>>
>>>> Reproducer:
>>>> sudo ethtool -X $PF1 equal 8
>>>>
>>>> Output in dmesg:
>>>> Failed to configure RSS hash for VSI 6, error -5
>>>>
>>>> Fixes: 352e9bf23813 ("ice: enable symmetric-xor RSS for Toeplitz hash function")
>>>> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
>>>> Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
>>>> Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
>>>> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
>>>> ---
>>>> drivers/net/ethernet/intel/ice/ice_main.c | 2 +-
>>>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>>>
>>>> diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
>>>> index 1d1947a7fe11..c52c465280f7 100644
>>>> --- a/drivers/net/ethernet/intel/ice/ice_main.c
>>>> +++ b/drivers/net/ethernet/intel/ice/ice_main.c
>>>> @@ -8046,7 +8046,7 @@ int ice_set_rss_hfunc(struct ice_vsi *vsi, u8 hfunc)
>>>> ctx->info.q_opt_rss |=
>>>> FIELD_PREP(ICE_AQ_VSI_Q_OPT_RSS_HASH_M, hfunc);
>>>> ctx->info.q_opt_tc = vsi->info.q_opt_tc;
>>>> - ctx->info.q_opt_flags = vsi->info.q_opt_rss;
>>>> + ctx->info.q_opt_flags = vsi->info.q_opt_flags;
>>>>
>>>> err = ice_update_vsi(hw, vsi->idx, ctx, NULL);
>>>> if (err) {
>>>>
>>>
>>> Sashiko complains about ice_set_rss_hfunc() but it is unrelated to this fix:
>>>
>>>> While looking at this function, I noticed a pre-existing issue regarding the
>>>> hardware cache. Does calling ice_update_vsi() with a local context leave the
>>>> global hw->vsi_ctx[vsi->idx] out of sync?
>>>> If ice_update_vsi() succeeds, vsi->info.q_opt_rss is updated, but
>>>> hw->vsi_ctx[vsi->idx]->info.q_opt_rss is not.
>>>> When an unrelated feature such as RDMA filtering is subsequently toggled via
>>>> ice_cfg_rdma_fltr(), could it retrieve this stale cached context via
>>>> ice_get_vsi_ctx() and copy the stale q_opt_rss value back into its command
>>>> buffer?
>>
>> Yes.
>>
>>>> Could this cause the firmware to silently revert the RSS hash function to its
>>>> previous configuration during the next ice_update_vsi() call?
>>
>> No, because the context object passed to ice_update_vsi() only sets
>> ctx->info.valid_sections for the sections it wants to update, so unrelated
>> values are not updated in HW.
>>
Ok.
>> Looking at other ice_update_vsi() calls, most of the times the context object is
>> being allocated, not taken from cache. It's not immediately clear to me what
>> purpose does hw->vsi_ctx[] serve - it only appears to be used in
>> ice_cfg_rdma_fltr() (correct me if I'm wrong), where options from the cached
>> context are being read to fill the unchanged fields in the updated section.
>> This seems to be the equivalent of keeping track of context values in vsi->info,
>> which is what almost all ice_update_vsi() callers do.
>> If I had to guess, I'd say hw->vsi_ctx[] could probably be removed and vsi->info
>> used instead, but maybe I'm missing something.
>>
>> TLDR I think this is just old, inconsistent code that could be improved, but it
>> needs some investigation. Until we don't have a clear signal that there's a bug,
>> I wouldn't touch it.
>>
Makes sense. Its probably better to leave it as-is for now. Either way,
I think these shouldn't block the obvious fix.
>>>> There also appears to be a pre-existing issue with state desynchronization
>>>> when handling failures. After ice_update_vsi() succeeds, vsi->rss_hfunc
>>>> is updated, but ice_set_rss_hfunc() then calls ice_set_rss_cfg_symm() at the
>>>> end of the function:
>>>> ice_set_rss_hfunc() {
>>>> ...
>>>> err = ice_update_vsi(hw, vsi->idx, ctx, NULL);
>>>> if (err) {
>>>> ...
>>>> } else {
>>>> vsi->info.q_opt_rss = ctx->info.q_opt_rss;
>>>> vsi->rss_hfunc = hfunc;
>>>> ...
>>>> }
>>>> ...
>>>> return ice_set_rss_cfg_symm(hw, vsi, symm);
>>>> }
>>>> If ice_set_rss_cfg_symm() fails, the error is returned but vsi->rss_hfunc
>>>> remains updated. If the user retries the command, the early check:
>>>> if (hfunc == vsi->rss_hfunc)
>>>> return 0;
>>>> evaluates to true and returns success immediately.
>>>> Could this skip the ice_set_rss_cfg_symm() retry entirely, leaving the
>>>> hardware's flow director rules in a non-symmetric state permanently while
>>>> falsely reporting success?
>>
>> This looks valid.
>
> On second thought, if we decide to rollback changes to VSI on ice_set_rss_cfg_symm()
> fail, we must call ice_update_vsi(), which then can also fail, still leaving us with
> hfunc programmed and symmetry not set. I'm not sure if it's worth adding rollback
> that can fail and still leave us with the original problem. User would just see 2 errors
> instead of 1.
>> Thanks,
>> Marcin
>>
Either way, its a pre-existing issue that could warrant an investigation
and a patch, not a reason to modify or delay this bug fix.
Thanks,
Jake
>>> Someone from the ice team should look into this and determine whether or
>>> not its valid.
>
>
^ permalink raw reply
* [PATCH net-next v6 3/3] gve: implement PTP gettimex64
From: Harshitha Ramamurthy @ 2026-05-07 21:13 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, richardcochran, jstultz, tglx, sboyd, willemb, nktgrg,
jfraker, ziweixiao, maolson, jordanrhee, thostet, alok.a.tiwari,
pkaligineedi, horms, dwmw2, jacob.e.keller, yyd, jefrogers,
linux-kernel, Naman Gulati
In-Reply-To: <20260507211304.3046526-1-hramamurthy@google.com>
From: Jordan Rhee <jordanrhee@google.com>
Enable chrony and phc2sys to synchronize system clock to NIC clock.
Two paths are implemented: a precise path using system counter values
sampled by the device, and a fallback path using system counter values
sampled in the driver using ptp_read_system_prets()/postts().
To use the precise path, the current system clocksource must match the
units returned by the device, which on x86 is X86_TSC and on ARM64 is
ARM_ARCH_COUNTER. The clockid requested for the cross-timestamp must
be either CLOCK_REALTIME or CLOCK_MONOTONIC_RAW. These conditions hold
by default on GCP VMs using Chrony, so we expect the precise path to be
used the vast majority of the time. If the system clocksource is changed
to kvm-clock, it activates the fallback path. Ethtool counters have been
added to count how many times each path is used.
The uncertainty window in the precise path is typically around 1-2us,
while in the fallback path is around 60-80us.
Stub implementions of adjfine and adjtime are added to avoid NULL
dereference when phc2sys tries to adjust the clock.
Cc: John Stultz <jstultz@google.com>
Cc: Thomas Gleixner <tglx@kernel.org>
Cc: Stephen Boyd <sboyd@kernel.org>
Cc: David Woodhouse <dwmw2@infradead.org>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Reviewed-by: Kevin Yang <yyd@google.com>
Reviewed-by: Naman Gulati <namangulati@google.com>
Signed-off-by: Jordan Rhee <jordanrhee@google.com>
Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
---
Changes in v6:
- Added a fallback to driver-sampled time sandwich that is used when
the following conditions are not met:
- The system clock source is X86_TSC or ARM_ARCH_COUNTER
- The requested clockid is CLOCK_REALTIME or CLOCK_MONOTONIC_RAW
- The architecture is x86 or ARM64
- Added ethtool statistics to count how many cross-timestamps used the
precise path versus fallback path.
- Fixed printf format specifier.
- Added stub implementions of adjtime and adjfine to prevent NULL
dereference when phc2sys tries to adjust clock.
- Moved system time snapshot back to gve_ptp_gettimex64() so we can get the
current system clock source from it. It is OK for it to not be inside
the mutex or retry loop because lock contention and retries should be
extremely rare, and chrony filters out bad samples.
Changes in v5:
- Reformulate retry loop in terms of total timeout (Jakub Kicinski)
Changes in v3:
- Take system time snapshot inside the mutex
- Return -EOPNOTSUPP if cross-timestamp is requested on an arch other
than x86 or arm64
Changes in v2:
- fix compilation warning on ARM by casting cycles_t to u64
---
drivers/net/ethernet/google/gve/gve.h | 8 +
drivers/net/ethernet/google/gve/gve_adminq.h | 4 +-
drivers/net/ethernet/google/gve/gve_ethtool.c | 3 +
drivers/net/ethernet/google/gve/gve_ptp.c | 237 +++++++++++++++++-
4 files changed, 243 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
index 7b69d0cfc0d5..4de3ce60060e 100644
--- a/drivers/net/ethernet/google/gve/gve.h
+++ b/drivers/net/ethernet/google/gve/gve.h
@@ -880,6 +880,14 @@ struct gve_priv {
u32 stats_report_trigger_cnt; /* count of device-requested stats-reports since last reset */
u32 suspend_cnt; /* count of times suspended */
u32 resume_cnt; /* count of times resumed */
+ /* count of cross-timestamps attempted using system timestamps
+ * from the AQ command
+ */
+ u32 ptp_precise_xtstamps;
+ /* count of cross-timestamps attempted using system timestamps sampled
+ * by the driver
+ */
+ u32 ptp_fallback_xtstamps;
struct workqueue_struct *gve_wq;
struct work_struct service_task;
struct work_struct stats_report_task;
diff --git a/drivers/net/ethernet/google/gve/gve_adminq.h b/drivers/net/ethernet/google/gve/gve_adminq.h
index 22a74b6aa17e..e6dcf6da9091 100644
--- a/drivers/net/ethernet/google/gve/gve_adminq.h
+++ b/drivers/net/ethernet/google/gve/gve_adminq.h
@@ -411,8 +411,8 @@ static_assert(sizeof(struct gve_adminq_report_nic_ts) == 16);
struct gve_nic_ts_report {
__be64 nic_timestamp; /* NIC clock in nanoseconds */
- __be64 reserved1;
- __be64 reserved2;
+ __be64 pre_cycles; /* System cycle counter before NIC clock read */
+ __be64 post_cycles; /* System cycle counter after NIC clock read */
__be64 reserved3;
__be64 reserved4;
};
diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c
index 4fd7e8a442c5..8a088dcc3603 100644
--- a/drivers/net/ethernet/google/gve/gve_ethtool.c
+++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
@@ -46,6 +46,7 @@ static const char gve_gstrings_main_stats[][ETH_GSTRING_LEN] = {
"rx_hsplit_unsplit_pkt",
"interface_up_cnt", "interface_down_cnt", "reset_cnt",
"page_alloc_fail", "dma_mapping_error", "stats_report_trigger_cnt",
+ "ptp_precise_xtstamps", "ptp_fallback_xtstamps",
};
static const char gve_gstrings_rx_stats[][ETH_GSTRING_LEN] = {
@@ -269,6 +270,8 @@ gve_get_ethtool_stats(struct net_device *netdev,
data[i++] = priv->page_alloc_fail;
data[i++] = priv->dma_mapping_error;
data[i++] = priv->stats_report_trigger_cnt;
+ data[i++] = priv->ptp_precise_xtstamps;
+ data[i++] = priv->ptp_fallback_xtstamps;
i = GVE_MAIN_STATS_LEN;
rx_base_stats_idx = 0;
diff --git a/drivers/net/ethernet/google/gve/gve_ptp.c b/drivers/net/ethernet/google/gve/gve_ptp.c
index ad15f1209a83..9711521ce1d0 100644
--- a/drivers/net/ethernet/google/gve/gve_ptp.c
+++ b/drivers/net/ethernet/google/gve/gve_ptp.c
@@ -10,28 +10,251 @@
/* Interval to schedule a nic timestamp calibration, 250ms. */
#define GVE_NIC_TS_SYNC_INTERVAL_MS 250
+/*
+ * Stores cycle counter samples in get_cycles() units from a
+ * sandwiched NIC clock read
+ */
+struct gve_sysclock_sample {
+ /* Cycle counter from NIC before clock read */
+ u64 nic_pre_cycles;
+ /* Cycle counter from NIC after clock read */
+ u64 nic_post_cycles;
+ /* Cycle counter from host before issuing AQ command */
+ cycles_t host_pre_cycles;
+ /* Cycle counter from host after AQ command returns */
+ cycles_t host_post_cycles;
+};
+
+/*
+ * Read NIC clock by issuing the AQ command. The command is subject to
+ * rate limiting and may need to be retried. Requires nic_ts_read_lock
+ * to be held.
+ */
+static int gve_ptp_read_timestamp(struct gve_ptp *ptp, cycles_t *pre_cycles,
+ cycles_t *post_cycles)
+{
+ unsigned long deadline = jiffies + msecs_to_jiffies(100);
+ unsigned long delay_us = 1000;
+ int err;
+
+ lockdep_assert_held(&ptp->nic_ts_read_lock);
+
+ do {
+ *pre_cycles = get_cycles();
+ err = gve_adminq_report_nic_ts(ptp->priv,
+ ptp->nic_ts_report_bus);
+
+ /* Prevent get_cycles() from being speculatively executed
+ * before the AdminQ command
+ */
+ rmb();
+ *post_cycles = get_cycles();
+ if (likely(err != -EAGAIN))
+ return err;
+
+ fsleep(delay_us);
+
+ /* Exponential backoff */
+ delay_us *= 2;
+ } while (time_before(jiffies, deadline));
+
+ return -ETIMEDOUT;
+}
+
/* Read the nic timestamp from hardware via the admin queue. */
-static int gve_clock_nic_ts_read(struct gve_ptp *ptp, u64 *nic_raw)
+static int gve_clock_nic_ts_read(struct gve_ptp *ptp, u64 *nic_raw,
+ struct gve_sysclock_sample *sysclock)
{
+ cycles_t host_pre_cycles, host_post_cycles;
+ struct gve_nic_ts_report *ts_report;
int err;
mutex_lock(&ptp->nic_ts_read_lock);
- err = gve_adminq_report_nic_ts(ptp->priv, ptp->nic_ts_report_bus);
- if (err)
+ err = gve_ptp_read_timestamp(ptp, &host_pre_cycles, &host_post_cycles);
+ if (err) {
+ dev_err_ratelimited(&ptp->priv->pdev->dev,
+ "AdminQ timestamp read failed: %d\n", err);
goto out;
+ }
+
+ ts_report = ptp->nic_ts_report;
+ *nic_raw = be64_to_cpu(ts_report->nic_timestamp);
- *nic_raw = be64_to_cpu(ptp->nic_ts_report->nic_timestamp);
+ if (sysclock) {
+ sysclock->nic_pre_cycles = be64_to_cpu(ts_report->pre_cycles);
+ sysclock->nic_post_cycles = be64_to_cpu(ts_report->post_cycles);
+ sysclock->host_pre_cycles = host_pre_cycles;
+ sysclock->host_post_cycles = host_post_cycles;
+ }
out:
mutex_unlock(&ptp->nic_ts_read_lock);
return err;
}
+struct gve_cycles_to_clock_callback_ctx {
+ u64 cycles;
+};
+
+static int gve_cycles_to_clock_fn(ktime_t *device_time,
+ struct system_counterval_t *system_counterval,
+ void *ctx)
+{
+ struct gve_cycles_to_clock_callback_ctx *context = ctx;
+
+ *device_time = 0;
+
+ system_counterval->cycles = context->cycles;
+ system_counterval->use_nsecs = false;
+
+ if (IS_ENABLED(CONFIG_X86))
+ system_counterval->cs_id = CSID_X86_TSC;
+ else if (IS_ENABLED(CONFIG_ARM64))
+ system_counterval->cs_id = CSID_ARM_ARCH_COUNTER;
+ else
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+/*
+ * Convert a raw cycle count (e.g. from get_cycles()) to the system clock
+ * type specified by clockid. The system_time_snapshot must be taken before
+ * the cycle counter is sampled.
+ */
+static int gve_cycles_to_timespec64(struct gve_priv *priv, clockid_t clockid,
+ struct system_time_snapshot *snap,
+ u64 cycles, struct timespec64 *ts)
+{
+ struct gve_cycles_to_clock_callback_ctx ctx = {0};
+ struct system_device_crosststamp xtstamp;
+ int err;
+
+ ctx.cycles = cycles;
+ err = get_device_system_crosststamp(gve_cycles_to_clock_fn, &ctx, snap,
+ &xtstamp);
+ if (err) {
+ dev_err_ratelimited(&priv->pdev->dev,
+ "get_device_system_crosststamp() failed to convert %llu cycles to system time: %d\n",
+ cycles,
+ err);
+ return err;
+ }
+
+ switch (clockid) {
+ case CLOCK_REALTIME:
+ *ts = ktime_to_timespec64(xtstamp.sys_realtime);
+ break;
+ case CLOCK_MONOTONIC_RAW:
+ *ts = ktime_to_timespec64(xtstamp.sys_monoraw);
+ break;
+ default:
+ dev_err_ratelimited(&priv->pdev->dev,
+ "Cycle count conversion to clockid %d not supported\n",
+ clockid);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static bool
+gve_can_use_system_ts_from_device(enum clocksource_ids system_clock_source,
+ clockid_t clockid)
+{
+ if (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC_RAW)
+ return false;
+
+ /* If the system clock source matches the system clock
+ * returned by the AdminQ command, we can use the system
+ * timestamps returned by the device, otherwise we have to
+ * fall back to sampling system time from the host which
+ * is less accurate.
+ */
+ if (IS_ENABLED(CONFIG_X86))
+ return system_clock_source == CSID_X86_TSC;
+ else if (IS_ENABLED(CONFIG_ARM64))
+ return system_clock_source == CSID_ARM_ARCH_COUNTER;
+
+ return false;
+}
+
static int gve_ptp_gettimex64(struct ptp_clock_info *info,
struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
- return -EOPNOTSUPP;
+ struct gve_ptp *ptp = container_of(info, struct gve_ptp, info);
+ struct gve_sysclock_sample sysclock = {0};
+ bool use_system_ts_from_device = false;
+ struct gve_priv *priv = ptp->priv;
+ struct system_time_snapshot snap;
+ u64 nic_ts;
+ int err;
+
+ if (sts) {
+ /* This snapshot is used both to query the current system
+ * clocksource and to convert the cycle counts returned
+ * by the AdminQ command to ktime. It does not need to be
+ * taken inside the retry loop because retries and lock
+ * contention are expected to be extremely rare.
+ *
+ * If the system clock source changes between here and
+ * when get_device_system_crosststamp() is called,
+ * get_device_system_crosststamp() will fail which will
+ * cause one failed sample, and the next one will succeed.
+ */
+ ktime_get_snapshot(&snap);
+ use_system_ts_from_device =
+ gve_can_use_system_ts_from_device(snap.cs_id,
+ sts->clockid);
+ if (use_system_ts_from_device)
+ priv->ptp_precise_xtstamps++;
+ else
+ priv->ptp_fallback_xtstamps++;
+ }
+
+ if (unlikely(!use_system_ts_from_device))
+ ptp_read_system_prets(sts);
+
+ err = gve_clock_nic_ts_read(ptp, &nic_ts, sts ? &sysclock : NULL);
+ if (err)
+ return err;
+
+ if (unlikely(!use_system_ts_from_device))
+ ptp_read_system_postts(sts);
+
+ if (sts && likely(use_system_ts_from_device)) {
+ /* Reject samples with out of order system clock values.
+ * Firmware must return valid non-zero cycle counts.
+ */
+ if (!(sysclock.host_pre_cycles <= sysclock.nic_pre_cycles &&
+ sysclock.nic_pre_cycles <= sysclock.nic_post_cycles &&
+ sysclock.nic_post_cycles <= sysclock.host_post_cycles)) {
+ dev_err_ratelimited(&priv->pdev->dev,
+ "AdminQ system clock cycle counts out of order. Expecting %llu <= %llu <= %llu <= %llu\n",
+ (u64)sysclock.host_pre_cycles,
+ sysclock.nic_pre_cycles,
+ sysclock.nic_post_cycles,
+ (u64)sysclock.host_post_cycles);
+ return -EBADMSG;
+ }
+
+ err = gve_cycles_to_timespec64(priv, sts->clockid, &snap,
+ sysclock.nic_pre_cycles,
+ &sts->pre_ts);
+ if (err)
+ return err;
+
+ err = gve_cycles_to_timespec64(priv, sts->clockid, &snap,
+ sysclock.nic_post_cycles,
+ &sts->post_ts);
+ if (err)
+ return err;
+ }
+
+ *ts = ns_to_timespec64(nic_ts);
+
+ return 0;
}
static int gve_ptp_settime64(struct ptp_clock_info *info,
@@ -50,7 +273,7 @@ static long gve_ptp_do_aux_work(struct ptp_clock_info *info)
if (gve_get_reset_in_progress(priv) || !gve_get_admin_queue_ok(priv))
goto out;
- err = gve_clock_nic_ts_read(ptp, &nic_raw);
+ err = gve_clock_nic_ts_read(ptp, &nic_raw, NULL);
if (err) {
dev_err_ratelimited(&priv->pdev->dev, "%s read err %d\n",
__func__, err);
@@ -93,7 +316,7 @@ int gve_init_clock(struct gve_priv *priv)
goto free_ptp;
}
- err = gve_clock_nic_ts_read(ptp, &nic_raw);
+ err = gve_clock_nic_ts_read(ptp, &nic_raw, NULL);
if (err) {
dev_err(&priv->pdev->dev, "failed to read NIC clock %d\n", err);
goto free_dma_mem;
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH net-next v6 2/3] gve: make nic clock reads thread safe
From: Harshitha Ramamurthy @ 2026-05-07 21:13 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, richardcochran, jstultz, tglx, sboyd, willemb, nktgrg,
jfraker, ziweixiao, maolson, jordanrhee, thostet, alok.a.tiwari,
pkaligineedi, horms, dwmw2, jacob.e.keller, yyd, jefrogers,
linux-kernel
In-Reply-To: <20260507211304.3046526-1-hramamurthy@google.com>
From: Ankit Garg <nktgrg@google.com>
Add a mutex to protect the shared DMA buffer that receives NIC
timestamp reports. The NIC timestamp will be read from two different
threads: the periodic worker and upcoming `gettimex64`.
Move clock registration to the last step of initialization to ensure
that all data needed by the clock module is initialized before
the clock is exposed to usermode.
Reviewed-by: Joshua Washington <joshwash@google.com>
Signed-off-by: Ankit Garg <nktgrg@google.com>
Signed-off-by: Jordan Rhee <jordanrhee@google.com>
Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
---
Changes in v3:
- Reorder init/teardown to register PTP clock last, and simplify code
- Move ptp-related members from gve_priv to gve_ptp
- Only assign priv->ptp after ptp module is successfully initialized
---
drivers/net/ethernet/google/gve/gve.h | 12 +-
drivers/net/ethernet/google/gve/gve_ethtool.c | 3 +-
drivers/net/ethernet/google/gve/gve_ptp.c | 134 ++++++++----------
3 files changed, 63 insertions(+), 86 deletions(-)
diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
index 1d66d3834f7e..7b69d0cfc0d5 100644
--- a/drivers/net/ethernet/google/gve/gve.h
+++ b/drivers/net/ethernet/google/gve/gve.h
@@ -792,6 +792,9 @@ struct gve_ptp {
struct ptp_clock_info info;
struct ptp_clock *clock;
struct gve_priv *priv;
+ struct mutex nic_ts_read_lock; /* Protects nic_ts_report */
+ struct gve_nic_ts_report *nic_ts_report;
+ dma_addr_t nic_ts_report_bus;
};
struct gve_priv {
@@ -923,8 +926,6 @@ struct gve_priv {
bool nic_timestamp_supported;
struct gve_ptp *ptp;
struct kernel_hwtstamp_config ts_config;
- struct gve_nic_ts_report *nic_ts_report;
- dma_addr_t nic_ts_report_bus;
u64 last_sync_nic_counter; /* Clock counter from last NIC TS report */
};
@@ -1201,7 +1202,7 @@ static inline bool gve_supports_xdp_xmit(struct gve_priv *priv)
static inline bool gve_is_clock_enabled(struct gve_priv *priv)
{
- return priv->nic_ts_report;
+ return priv->ptp;
}
/* gqi napi handler defined in gve_main.c */
@@ -1321,14 +1322,9 @@ int gve_flow_rules_reset(struct gve_priv *priv);
int gve_init_rss_config(struct gve_priv *priv, u16 num_queues);
/* PTP and timestamping */
#if IS_ENABLED(CONFIG_PTP_1588_CLOCK)
-int gve_clock_nic_ts_read(struct gve_priv *priv);
int gve_init_clock(struct gve_priv *priv);
void gve_teardown_clock(struct gve_priv *priv);
#else /* CONFIG_PTP_1588_CLOCK */
-static inline int gve_clock_nic_ts_read(struct gve_priv *priv)
-{
- return -EOPNOTSUPP;
-}
static inline int gve_init_clock(struct gve_priv *priv)
{
diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c
index dc2213b5ce24..4fd7e8a442c5 100644
--- a/drivers/net/ethernet/google/gve/gve_ethtool.c
+++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
@@ -972,8 +972,7 @@ static int gve_get_ts_info(struct net_device *netdev,
info->rx_filters |= BIT(HWTSTAMP_FILTER_NONE) |
BIT(HWTSTAMP_FILTER_ALL);
- if (priv->ptp)
- info->phc_index = ptp_clock_index(priv->ptp->clock);
+ info->phc_index = ptp_clock_index(priv->ptp->clock);
}
return 0;
diff --git a/drivers/net/ethernet/google/gve/gve_ptp.c b/drivers/net/ethernet/google/gve/gve_ptp.c
index 06b1cf4a5efc..ad15f1209a83 100644
--- a/drivers/net/ethernet/google/gve/gve_ptp.c
+++ b/drivers/net/ethernet/google/gve/gve_ptp.c
@@ -11,19 +11,20 @@
#define GVE_NIC_TS_SYNC_INTERVAL_MS 250
/* Read the nic timestamp from hardware via the admin queue. */
-int gve_clock_nic_ts_read(struct gve_priv *priv)
+static int gve_clock_nic_ts_read(struct gve_ptp *ptp, u64 *nic_raw)
{
- u64 nic_raw;
int err;
- err = gve_adminq_report_nic_ts(priv, priv->nic_ts_report_bus);
+ mutex_lock(&ptp->nic_ts_read_lock);
+ err = gve_adminq_report_nic_ts(ptp->priv, ptp->nic_ts_report_bus);
if (err)
- return err;
+ goto out;
- nic_raw = be64_to_cpu(priv->nic_ts_report->nic_timestamp);
- WRITE_ONCE(priv->last_sync_nic_counter, nic_raw);
+ *nic_raw = be64_to_cpu(ptp->nic_ts_report->nic_timestamp);
- return 0;
+out:
+ mutex_unlock(&ptp->nic_ts_read_lock);
+ return err;
}
static int gve_ptp_gettimex64(struct ptp_clock_info *info,
@@ -41,17 +42,21 @@ static int gve_ptp_settime64(struct ptp_clock_info *info,
static long gve_ptp_do_aux_work(struct ptp_clock_info *info)
{
- const struct gve_ptp *ptp = container_of(info, struct gve_ptp, info);
+ struct gve_ptp *ptp = container_of(info, struct gve_ptp, info);
struct gve_priv *priv = ptp->priv;
+ u64 nic_raw;
int err;
if (gve_get_reset_in_progress(priv) || !gve_get_admin_queue_ok(priv))
goto out;
- err = gve_clock_nic_ts_read(priv);
- if (err && net_ratelimit())
- dev_err(&priv->pdev->dev,
- "%s read err %d\n", __func__, err);
+ err = gve_clock_nic_ts_read(ptp, &nic_raw);
+ if (err) {
+ dev_err_ratelimited(&priv->pdev->dev, "%s read err %d\n",
+ __func__, err);
+ goto out;
+ }
+ WRITE_ONCE(priv->last_sync_nic_counter, nic_raw);
out:
return msecs_to_jiffies(GVE_NIC_TS_SYNC_INTERVAL_MS);
@@ -65,94 +70,71 @@ static const struct ptp_clock_info gve_ptp_caps = {
.do_aux_work = gve_ptp_do_aux_work,
};
-static int gve_ptp_init(struct gve_priv *priv)
+int gve_init_clock(struct gve_priv *priv)
{
struct gve_ptp *ptp;
+ u64 nic_raw;
int err;
- priv->ptp = kzalloc_obj(*priv->ptp);
- if (!priv->ptp)
+ ptp = kzalloc_obj(*priv->ptp);
+ if (!ptp)
return -ENOMEM;
- ptp = priv->ptp;
ptp->info = gve_ptp_caps;
- ptp->clock = ptp_clock_register(&ptp->info, &priv->pdev->dev);
-
- if (IS_ERR(ptp->clock)) {
- dev_err(&priv->pdev->dev, "PTP clock registration failed\n");
- err = PTR_ERR(ptp->clock);
- goto free_ptp;
- }
-
ptp->priv = priv;
- return 0;
-
-free_ptp:
- kfree(ptp);
- priv->ptp = NULL;
- return err;
-}
-
-static void gve_ptp_release(struct gve_priv *priv)
-{
- struct gve_ptp *ptp = priv->ptp;
-
- if (!ptp)
- return;
-
- if (ptp->clock)
- ptp_clock_unregister(ptp->clock);
-
- kfree(ptp);
- priv->ptp = NULL;
-}
-
-int gve_init_clock(struct gve_priv *priv)
-{
- int err;
-
- err = gve_ptp_init(priv);
- if (err)
- return err;
-
- priv->nic_ts_report =
+ mutex_init(&ptp->nic_ts_read_lock);
+ ptp->nic_ts_report =
dma_alloc_coherent(&priv->pdev->dev,
sizeof(struct gve_nic_ts_report),
- &priv->nic_ts_report_bus,
- GFP_KERNEL);
- if (!priv->nic_ts_report) {
+ &ptp->nic_ts_report_bus, GFP_KERNEL);
+ if (!ptp->nic_ts_report) {
dev_err(&priv->pdev->dev, "%s dma alloc error\n", __func__);
err = -ENOMEM;
- goto release_ptp;
+ goto free_ptp;
}
- err = gve_clock_nic_ts_read(priv);
+
+ err = gve_clock_nic_ts_read(ptp, &nic_raw);
if (err) {
dev_err(&priv->pdev->dev, "failed to read NIC clock %d\n", err);
- goto release_nic_ts_report;
+ goto free_dma_mem;
}
- ptp_schedule_worker(priv->ptp->clock,
+ WRITE_ONCE(priv->last_sync_nic_counter, nic_raw);
+
+ ptp->clock = ptp_clock_register(&ptp->info, &priv->pdev->dev);
+ if (IS_ERR(ptp->clock)) {
+ dev_err(&priv->pdev->dev, "PTP clock registration failed\n");
+ err = PTR_ERR(ptp->clock);
+ goto free_dma_mem;
+ }
+
+ priv->ptp = ptp;
+ ptp_schedule_worker(ptp->clock,
msecs_to_jiffies(GVE_NIC_TS_SYNC_INTERVAL_MS));
return 0;
-release_nic_ts_report:
- dma_free_coherent(&priv->pdev->dev,
- sizeof(struct gve_nic_ts_report),
- priv->nic_ts_report, priv->nic_ts_report_bus);
- priv->nic_ts_report = NULL;
-release_ptp:
- gve_ptp_release(priv);
+free_dma_mem:
+ dma_free_coherent(&priv->pdev->dev, sizeof(struct gve_nic_ts_report),
+ ptp->nic_ts_report, ptp->nic_ts_report_bus);
+ ptp->nic_ts_report = NULL;
+free_ptp:
+ mutex_destroy(&ptp->nic_ts_read_lock);
+ kfree(ptp);
return err;
}
void gve_teardown_clock(struct gve_priv *priv)
{
- gve_ptp_release(priv);
+ struct gve_ptp *ptp = priv->ptp;
- if (priv->nic_ts_report) {
- dma_free_coherent(&priv->pdev->dev,
- sizeof(struct gve_nic_ts_report),
- priv->nic_ts_report, priv->nic_ts_report_bus);
- priv->nic_ts_report = NULL;
- }
+ if (!ptp)
+ return;
+
+ priv->ptp = NULL;
+ ptp_clock_unregister(ptp->clock);
+ dma_free_coherent(&priv->pdev->dev, sizeof(struct gve_nic_ts_report),
+ ptp->nic_ts_report, ptp->nic_ts_report_bus);
+ ptp->nic_ts_report = NULL;
+ mutex_destroy(&ptp->nic_ts_read_lock);
+ kfree(ptp);
}
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH net-next v6 1/3] gve: skip error logging for retryable AdminQ commands
From: Harshitha Ramamurthy @ 2026-05-07 21:13 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, richardcochran, jstultz, tglx, sboyd, willemb, nktgrg,
jfraker, ziweixiao, maolson, jordanrhee, thostet, alok.a.tiwari,
pkaligineedi, horms, dwmw2, jacob.e.keller, yyd, jefrogers,
linux-kernel
In-Reply-To: <20260507211304.3046526-1-hramamurthy@google.com>
From: Jordan Rhee <jordanrhee@google.com>
AdminQ commands may return -EAGAIN under certain transient conditions.
These commands are intended to be retried by the driver, so logging
a formal error to the system log is misleading and creates
unnecessary noise.
Modify the logging logic to skip the error message when the result
is -EAGAIN, and move logging to dev_err_ratelimited() to avoid
spamming the log.
Reviewed-by: Joshua Washington <joshwash@google.com>
Signed-off-by: Jordan Rhee <jordanrhee@google.com>
Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
---
Changes in v4:
- call out change to dev_err_ratelimited() in the commit message (Jacob Keller)
- remove extra print when adminQ status is GVE_ADMINQ_COMMAND_UNSET (Jacob Keller)
---
drivers/net/ethernet/google/gve/gve_adminq.c | 27 +++++++++++++++-----
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c
index 08587bf40ed4..a65b14835aa0 100644
--- a/drivers/net/ethernet/google/gve/gve_adminq.c
+++ b/drivers/net/ethernet/google/gve/gve_adminq.c
@@ -416,16 +416,10 @@ static bool gve_adminq_wait_for_cmd(struct gve_priv *priv, u32 prod_cnt)
static int gve_adminq_parse_err(struct gve_priv *priv, u32 status)
{
- if (status != GVE_ADMINQ_COMMAND_PASSED &&
- status != GVE_ADMINQ_COMMAND_UNSET) {
- dev_err(&priv->pdev->dev, "AQ command failed with status %d\n", status);
- priv->adminq_cmd_fail++;
- }
switch (status) {
case GVE_ADMINQ_COMMAND_PASSED:
return 0;
case GVE_ADMINQ_COMMAND_UNSET:
- dev_err(&priv->pdev->dev, "parse_aq_err: err and status both unset, this should not be possible.\n");
return -EINVAL;
case GVE_ADMINQ_COMMAND_ERROR_ABORTED:
case GVE_ADMINQ_COMMAND_ERROR_CANCELLED:
@@ -455,6 +449,16 @@ static int gve_adminq_parse_err(struct gve_priv *priv, u32 status)
}
}
+static bool gve_adminq_is_retryable(enum gve_adminq_opcodes opcode)
+{
+ switch (opcode) {
+ case GVE_ADMINQ_REPORT_NIC_TIMESTAMP:
+ return true;
+ default:
+ return false;
+ }
+}
+
/* Flushes all AQ commands currently queued and waits for them to complete.
* If there are failures, it will return the first error.
*/
@@ -482,9 +486,18 @@ static int gve_adminq_kick_and_wait(struct gve_priv *priv)
cmd = &priv->adminq[i & priv->adminq_mask];
status = be32_to_cpu(READ_ONCE(cmd->status));
err = gve_adminq_parse_err(priv, status);
- if (err)
+ if (err) {
+ enum gve_adminq_opcodes opcode =
+ be32_to_cpu(READ_ONCE(cmd->opcode));
+ priv->adminq_cmd_fail++;
+ if (!gve_adminq_is_retryable(opcode) || err != -EAGAIN)
+ dev_err_ratelimited(&priv->pdev->dev,
+ "AQ command %d failed with status %d\n",
+ opcode, status);
+
// Return the first error if we failed.
return err;
+ }
}
return 0;
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH net-next v6 0/3] gve: add support for PTP gettimex64
From: Harshitha Ramamurthy @ 2026-05-07 21:13 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, richardcochran, jstultz, tglx, sboyd, willemb, nktgrg,
jfraker, ziweixiao, maolson, jordanrhee, thostet, alok.a.tiwari,
pkaligineedi, horms, dwmw2, jacob.e.keller, yyd, jefrogers,
linux-kernel
From: Jordan Rhee <jordanrhee@google.com>
This patch series adds support to obtain near-simultaneous NIC and
system timestamps with gettimex64. This enables daemons like
chrony and phc2sys to synchronize the system clock to the NIC clock.
GVE does not have direct register access to the NIC hardware clock, so
it must issue an AdminQ command to read the NIC clock. Two paths
for obtaining a cross-timestamp are implemented: a precise path using
system counter values sampled by the device, and a fallback path using
system counter values sampled in the driver using
ptp_read_system_prets()/postts().
To use the precise path, the current system clocksource must match the
units returned by the device, which on x86 is X86_TSC and on ARM64 is
ARM_ARCH_COUNTER. The clockid requested for the cross-timestamp must
be either CLOCK_REALTIME or CLOCK_MONOTONIC_RAW. These conditions hold
by default on GCP VMs using Chrony, so we expect the precise path to be
used the vast majority of the time. If the system clocksource is changed
to kvm-clock, it activates the fallback path. Ethtool counters have been
added to count how many times each path is used.
The uncertainty window in the precise path is typically around 1-2us,
while in the fallback path is around 60-80us. This table shows a
comparison in chrony tracking statistics between the precise path and
fallback path. The RMS offset is nearly 4 orders of magnitude smaller
in the precise path.
| | Fallback Path | Precise path |
| --------------- | --------------------- | ------------------------ |
| System time | 0.000000005 s slow | 0.000000001 s fast |
| Last offset | +0.000005606 seconds | +0.000000001 seconds |
| RMS offset | 0.000009020 seconds | 0.000000002 seconds |
| Frequency | 4.115 ppm fast | 0.362 ppm fast |
| Residual freq | +2.515 ppm | +0.000 ppm |
| Skew | 18.480 ppm | 0.001 ppm |
| Root delay | 0.000000001 seconds | 0.000000001 seconds |
| Root dispersion | 0.000081905 seconds | 0.000001169 seconds |
| Update interval | 0.5 seconds | 0.5 seconds |
| Leap status | Normal | Normal |
The first two patches pave the way for the PTP implementation by
quieting excessive logging and refactoring an existing routine for
thread safety.
---
Changelog:
V6:
- Added a fallback to driver-sampled time sandwich that is used when
the following conditions are not met:
- The system clock source is X86_TSC or ARM_ARCH_COUNTER
- The requested clockid is CLOCK_REALTIME or CLOCK_MONOTONIC_RAW
- The architecture is x86 or ARM64
- Added ethtool statistics to count how many cross-timestamps used the
precise path versus fallback path.
- Fixed printf format specifier.
- Added stub implementions of adjtime and adjfine to prevent NULL
dereference when phc2sys tries to adjust clock.
- Moved system time snapshot back to gve_ptp_gettimex64() so we can get
the current system clock source from it. It is OK for it to be outside
the mutex and retry loop because lock contention and retries should be
extremely rare, and chrony filters out bad samples.
- Link to v5: https://lore.kernel.org/netdev/20260429012819.3102675-1-hramamurthy@google.com/
V5:
- Reformulate retry loop in terms of total timeout instead of retry
count (Jakub Kicinski)
- Link to v4: https://lore.kernel.org/netdev/20260406234002.3610542-1-hramamurthy@google.com/
V4:
- Call out change to dev_err_ratelimited() in patch 1 commit message (Jacob Keller)
- Ensure only one log is emitted when command returns GVE_ADMINQ_COMMAND_UNSET (Jacob Keller)
- Link to v3: https://lore.kernel.org/netdev/20260403194427.1830609-1-hramamurthy@google.com/
V3:
- Take system time snapshot inside the mutex
- Return -EOPNOTSUPP if cross-timestamp is requested on an arch other
than x86 or arm64
- Fix initialization to only register PTP clock once all data is
initialized
- Link to v2: https://lore.kernel.org/netdev/20260326224527.1044097-1-hramamurthy@google.com/
V2:
- Fixed compilation warning on ARM by casting to u64
- Link to v1: https://lore.kernel.org/netdev/20260323234829.3185051-1-hramamurthy@google.com/
---
Ankit Garg (1):
gve: make nic clock reads thread safe
Jordan Rhee (2):
gve: skip error logging for retryable AdminQ commands
gve: implement PTP gettimex64
drivers/net/ethernet/google/gve/gve.h | 20 +-
drivers/net/ethernet/google/gve/gve_adminq.c | 27 +-
drivers/net/ethernet/google/gve/gve_adminq.h | 4 +-
drivers/net/ethernet/google/gve/gve_ethtool.c | 6 +-
drivers/net/ethernet/google/gve/gve_ptp.c | 357 ++++++++++++++----
5 files changed, 319 insertions(+), 95 deletions(-)
base-commit: 8c699be3dad7bba87cdda485dc099226cfc2f706
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply
* Re: [PATCH] devlink/param: replace deprecated strcpy() with strscpy()
From: David Laight @ 2026-05-07 21:10 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Álvaro Costa, jiri, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, open list:DEVLINK, open list
In-Reply-To: <20260507075234.1630060f@kernel.org>
On Thu, 7 May 2026 07:52:34 -0700
Jakub Kicinski <kuba@kernel.org> wrote:
> On Thu, 7 May 2026 09:04:45 +0100 David Laight wrote:
> > > case DEVLINK_PARAM_TYPE_STRING:
> > > - len = strnlen(nla_data(param_data), nla_len(param_data));
> > > - if (len == nla_len(param_data) ||
> > > - len >= __DEVLINK_PARAM_MAX_STRING_VALUE)
> > > + len = strscpy(value->vstr, nla_data(param_data));
> > > + if (len < 0)
> > > return -EINVAL;
> > > - strcpy(value->vstr, nla_data(param_data));
> >
> > The only sensible thing here is to replace the strcpy() with:
> > memcpy(value->vstr, nla_data(param_data), len + 1);
>
> That'd probably be a good move, to avoid getting the broken strscpy()
> conversion submissions. Care to send a patch?
I'll lob one in soon.
I'm working my way through a compile that errors strcpy() except for
constant strings into arrays (ie the safe ones).
Why is it that the worst examples is in the 'secerity code' :-)
-- David
^ permalink raw reply
* Re: [PATCH batadv 1/1] batman-adv: fix tp_meter counter underflow during shutdown
From: Sven Eckelmann @ 2026-05-07 20:56 UTC (permalink / raw)
To: Ren Wei
Cc: b.a.t.m.a.n, netdev, marek.lindner, sw, antonio, sven, yuantan098,
yifanwucs, tomapufckgml, bird, rakukuip
In-Reply-To: <df73e607bda0c84b22d64d80f8ac887190242baf.1778118303.git.rakukuip@gmail.com>
On Thu, 07 May 2026 23:49:46 +0800, Ren Wei <n05ec@lzu.edu.cn> wrote:
> diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
> index 2e42f6b348c8..4c582443f67c 100644
> --- a/net/batman-adv/tp_meter.c
> +++ b/net/batman-adv/tp_meter.c
> @@ -435,7 +435,7 @@ static void batadv_tp_sender_end(struct batadv_priv *bat_priv,
> static void batadv_tp_sender_shutdown(struct batadv_tp_vars *tp_vars,
> enum batadv_tp_meter_reason reason)
> {
> - if (!atomic_dec_and_test(&tp_vars->sending))
> + if (atomic_xchg(&tp_vars->sending, 0) != 1)
> return;
>
> tp_vars->reason = reason;
What about:
/* ensure nobody else tries to stop the thread now */
if (atomic_dec_and_test(&tp_vars->sending))
tp_vars->reason = err;
break;
in batadv_tp_send()? If shutdown is called and then batadv_tp_send reaches
this part, isn't this also ending up at -1?
Regarding the netdev mail situation: Please don't send it to
netdev@vger.kernel.org directly - they want us to first handle it internally
before forwarding it later to netdev. This should reduce the patch volume for
them. There is already a change to the MAINTAINERS file to remove the
batman-adv paths from the NETWORKING section (netdev@....) queued up
- but it was not yet forwarded to net-next (and I am just wondering whether I
should ask Simon to directly submit it to net).
Regards,
Sven
--
Sven Eckelmann <sven@narfation.org>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox