* [PATCH net-next] net: airoha: better handle MIBs for GDM ports with multiple devs attached
@ 2026-06-11 10:43 Lorenzo Bianconi
2026-06-12 11:04 ` Lorenzo Bianconi
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Lorenzo Bianconi @ 2026-06-11 10:43 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, Christian Marangi
In the context of a GDM port that can have multiple net_devices attached
(GDM3 and GDM4), the HW counters (MIBs) are global for the GDM port.
This cause duplicated stats reported to the kernel for the related
net_device.
The SoC supports a split MIB feature where each counter is tracked based
on the relevant HW channel (NBQ) to account for this scenario and
provide a way to select the related counter on accessing the MIB
registers.
Enable this feature for GDM3 and GDM4 and configure the relevant HW
channel before updating the HW stats to report correct HW counter to the
kernel for the related interface.
Move the stats struct from port to dev since HW counter are now specific
to the network device instead of the GDM port. Refactor
airoha_update_hw_stats() to take airoha_eth and airoha_gdm_port
parameters since the function operates on the entire port.
Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 191 +++++++++++++++++--------------
drivers/net/ethernet/airoha/airoha_eth.h | 7 +-
2 files changed, 112 insertions(+), 86 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 5a8e84fa9918..7676ec9b3129 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -556,6 +556,14 @@ static int airoha_fe_init(struct airoha_eth *eth)
airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM3_IDX), GDM_PAD_EN_MASK);
airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM4_IDX), GDM_PAD_EN_MASK);
+ /* Enable split for MIB counters for GDM3 and GDM4 */
+ airoha_fe_set(eth, REG_FE_GDM_MIB_CFG(AIROHA_GDM3_IDX),
+ FE_GDM_TX_MIB_SPLIT_EN_MASK |
+ FE_GDM_RX_MIB_SPLIT_EN_MASK);
+ airoha_fe_set(eth, REG_FE_GDM_MIB_CFG(AIROHA_GDM4_IDX),
+ FE_GDM_TX_MIB_SPLIT_EN_MASK |
+ FE_GDM_RX_MIB_SPLIT_EN_MASK);
+
airoha_fe_crsn_qsel_init(eth);
airoha_fe_clear(eth, REG_FE_CPORT_CFG, FE_CPORT_QUEUE_XFC_MASK);
@@ -1626,149 +1634,169 @@ static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
}
}
-static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
+static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
{
struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = dev->eth;
u32 val, i = 0;
- spin_lock(&port->stats.lock);
- u64_stats_update_begin(&port->stats.syncp);
+ /* Read relevant MIB for GDM with multiple port attached */
+ if (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)
+ airoha_fe_rmw(eth, REG_FE_GDM_MIB_CFG(port->id),
+ FE_TX_MIB_ID_MASK | FE_RX_MIB_ID_MASK,
+ FIELD_PREP(FE_TX_MIB_ID_MASK, dev->nbq) |
+ FIELD_PREP(FE_RX_MIB_ID_MASK, dev->nbq));
+
+ u64_stats_update_begin(&dev->stats.syncp);
/* TX */
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
- port->stats.tx_ok_pkts += ((u64)val << 32);
+ dev->stats.tx_ok_pkts += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
- port->stats.tx_ok_pkts += val;
+ dev->stats.tx_ok_pkts += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
- port->stats.tx_ok_bytes += ((u64)val << 32);
+ dev->stats.tx_ok_bytes += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
- port->stats.tx_ok_bytes += val;
+ dev->stats.tx_ok_bytes += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
- port->stats.tx_drops += val;
+ dev->stats.tx_drops += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
- port->stats.tx_broadcast += val;
+ dev->stats.tx_broadcast += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
- port->stats.tx_multicast += val;
+ dev->stats.tx_multicast += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
- port->stats.tx_len[i] += val;
+ dev->stats.tx_len[i] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
- port->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
- port->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
- port->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
- port->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
- port->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
- port->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
- port->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
- port->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
- port->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
- port->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
- port->stats.tx_len[i] += ((u64)val << 32);
+ dev->stats.tx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
- port->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
- port->stats.tx_len[i++] += val;
+ dev->stats.tx_len[i++] += val;
/* RX */
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
- port->stats.rx_ok_pkts += ((u64)val << 32);
+ dev->stats.rx_ok_pkts += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
- port->stats.rx_ok_pkts += val;
+ dev->stats.rx_ok_pkts += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
- port->stats.rx_ok_bytes += ((u64)val << 32);
+ dev->stats.rx_ok_bytes += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
- port->stats.rx_ok_bytes += val;
+ dev->stats.rx_ok_bytes += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
- port->stats.rx_drops += val;
+ dev->stats.rx_drops += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
- port->stats.rx_broadcast += val;
+ dev->stats.rx_broadcast += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
- port->stats.rx_multicast += val;
+ dev->stats.rx_multicast += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
- port->stats.rx_errors += val;
+ dev->stats.rx_errors += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
- port->stats.rx_crc_error += val;
+ dev->stats.rx_crc_error += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
- port->stats.rx_over_errors += val;
+ dev->stats.rx_over_errors += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
- port->stats.rx_fragment += val;
+ dev->stats.rx_fragment += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
- port->stats.rx_jabber += val;
+ dev->stats.rx_jabber += val;
i = 0;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
- port->stats.rx_len[i] += val;
+ dev->stats.rx_len[i] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
- port->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
- port->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
- port->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
- port->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
- port->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
- port->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
- port->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
- port->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
- port->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
- port->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
- port->stats.rx_len[i] += ((u64)val << 32);
+ dev->stats.rx_len[i] += ((u64)val << 32);
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
- port->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i++] += val;
val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
- port->stats.rx_len[i++] += val;
+ dev->stats.rx_len[i++] += val;
+
+ u64_stats_update_end(&dev->stats.syncp);
+}
- /* reset mib counters */
- airoha_fe_set(eth, REG_FE_GDM_MIB_CLEAR(port->id),
+static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
+{
+ struct airoha_gdm_port *port = dev->port;
+ int i;
+
+ spin_lock(&port->stats_lock);
+
+ for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
+ if (port->devs[i])
+ airoha_dev_get_hw_stats(port->devs[i]);
+ }
+
+ /* Reset MIB counters */
+ airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
- u64_stats_update_end(&port->stats.syncp);
- spin_unlock(&port->stats.lock);
+ spin_unlock(&port->stats_lock);
}
static int airoha_dev_open(struct net_device *netdev)
@@ -2043,23 +2071,22 @@ static void airoha_dev_get_stats64(struct net_device *netdev,
struct rtnl_link_stats64 *storage)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
unsigned int start;
airoha_update_hw_stats(dev);
do {
- start = u64_stats_fetch_begin(&port->stats.syncp);
- storage->rx_packets = port->stats.rx_ok_pkts;
- storage->tx_packets = port->stats.tx_ok_pkts;
- storage->rx_bytes = port->stats.rx_ok_bytes;
- storage->tx_bytes = port->stats.tx_ok_bytes;
- storage->multicast = port->stats.rx_multicast;
- storage->rx_errors = port->stats.rx_errors;
- storage->rx_dropped = port->stats.rx_drops;
- storage->tx_dropped = port->stats.tx_drops;
- storage->rx_crc_errors = port->stats.rx_crc_error;
- storage->rx_over_errors = port->stats.rx_over_errors;
- } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ start = u64_stats_fetch_begin(&dev->stats.syncp);
+ storage->rx_packets = dev->stats.rx_ok_pkts;
+ storage->tx_packets = dev->stats.tx_ok_pkts;
+ storage->rx_bytes = dev->stats.rx_ok_bytes;
+ storage->tx_bytes = dev->stats.tx_ok_bytes;
+ storage->multicast = dev->stats.rx_multicast;
+ storage->rx_errors = dev->stats.rx_errors;
+ storage->rx_dropped = dev->stats.rx_drops;
+ storage->tx_dropped = dev->stats.tx_drops;
+ storage->rx_crc_errors = dev->stats.rx_crc_error;
+ storage->rx_over_errors = dev->stats.rx_over_errors;
+ } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
}
static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
@@ -2310,20 +2337,19 @@ static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
struct ethtool_eth_mac_stats *stats)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
unsigned int start;
airoha_update_hw_stats(dev);
do {
- start = u64_stats_fetch_begin(&port->stats.syncp);
- stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
- stats->OctetsTransmittedOK = port->stats.tx_ok_bytes;
- stats->MulticastFramesXmittedOK = port->stats.tx_multicast;
- stats->BroadcastFramesXmittedOK = port->stats.tx_broadcast;
- stats->FramesReceivedOK = port->stats.rx_ok_pkts;
- stats->OctetsReceivedOK = port->stats.rx_ok_bytes;
- stats->BroadcastFramesReceivedOK = port->stats.rx_broadcast;
- } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ start = u64_stats_fetch_begin(&dev->stats.syncp);
+ stats->FramesTransmittedOK = dev->stats.tx_ok_pkts;
+ stats->OctetsTransmittedOK = dev->stats.tx_ok_bytes;
+ stats->MulticastFramesXmittedOK = dev->stats.tx_multicast;
+ stats->BroadcastFramesXmittedOK = dev->stats.tx_broadcast;
+ stats->FramesReceivedOK = dev->stats.rx_ok_pkts;
+ stats->OctetsReceivedOK = dev->stats.rx_ok_bytes;
+ stats->BroadcastFramesReceivedOK = dev->stats.rx_broadcast;
+ } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
}
static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
@@ -2343,8 +2369,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
const struct ethtool_rmon_hist_range **ranges)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
- struct airoha_hw_stats *hw_stats = &port->stats;
+ struct airoha_hw_stats *hw_stats = &dev->stats;
unsigned int start;
BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
@@ -2357,7 +2382,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
do {
int i;
- start = u64_stats_fetch_begin(&port->stats.syncp);
+ start = u64_stats_fetch_begin(&dev->stats.syncp);
stats->fragments = hw_stats->rx_fragment;
stats->jabbers = hw_stats->rx_jabber;
for (i = 0; i < ARRAY_SIZE(airoha_ethtool_rmon_ranges) - 1;
@@ -2365,7 +2390,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
stats->hist[i] = hw_stats->rx_len[i];
stats->hist_tx[i] = hw_stats->tx_len[i];
}
- } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
}
static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
@@ -3205,6 +3230,7 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
netdev->dev.of_node = of_node_get(np);
dev = netdev_priv(netdev);
+ u64_stats_init(&dev->stats.syncp);
dev->port = port;
dev->eth = eth;
dev->nbq = nbq;
@@ -3244,9 +3270,8 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
if (!port)
return -ENOMEM;
- u64_stats_init(&port->stats.syncp);
- spin_lock_init(&port->stats.lock);
port->id = id;
+ spin_lock_init(&port->stats_lock);
eth->ports[p] = port;
err = airoha_metadata_dst_alloc(port);
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 8f42973f9cf5..46b1c31939de 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -215,8 +215,6 @@ struct airoha_tx_irq_queue {
};
struct airoha_hw_stats {
- /* protect concurrent hw_stats accesses */
- spinlock_t lock;
struct u64_stats_sync syncp;
/* get_stats64 */
@@ -554,6 +552,8 @@ struct airoha_gdm_dev {
u32 flags;
int nbq;
+
+ struct airoha_hw_stats stats;
};
struct airoha_gdm_port {
@@ -561,7 +561,8 @@ struct airoha_gdm_port {
int id;
int users;
- struct airoha_hw_stats stats;
+ /* protect concurrent hw_stats accesses */
+ spinlock_t stats_lock;
struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
};
---
base-commit: c8459ee2fef502d6ef6c063751c33d9ac7943eab
change-id: 20260611-airoha-eth-multi-serdes-stats-df2dc16c2dd6
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net-next] net: airoha: better handle MIBs for GDM ports with multiple devs attached
2026-06-11 10:43 [PATCH net-next] net: airoha: better handle MIBs for GDM ports with multiple devs attached Lorenzo Bianconi
@ 2026-06-12 11:04 ` Lorenzo Bianconi
2026-06-12 14:20 ` Lorenzo Bianconi
2026-06-12 17:08 ` Simon Horman
2 siblings, 0 replies; 4+ messages in thread
From: Lorenzo Bianconi @ 2026-06-12 11:04 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: linux-arm-kernel, linux-mediatek, netdev, Christian Marangi
[-- Attachment #1: Type: text/plain, Size: 9195 bytes --]
> In the context of a GDM port that can have multiple net_devices attached
> (GDM3 and GDM4), the HW counters (MIBs) are global for the GDM port.
> This cause duplicated stats reported to the kernel for the related
> net_device.
> The SoC supports a split MIB feature where each counter is tracked based
> on the relevant HW channel (NBQ) to account for this scenario and
> provide a way to select the related counter on accessing the MIB
> registers.
> Enable this feature for GDM3 and GDM4 and configure the relevant HW
> channel before updating the HW stats to report correct HW counter to the
> kernel for the related interface.
> Move the stats struct from port to dev since HW counter are now specific
> to the network device instead of the GDM port. Refactor
> airoha_update_hw_stats() to take airoha_eth and airoha_gdm_port
> parameters since the function operates on the entire port.
>
> Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
commenting on sashiko's report:
https://sashiko.dev/#/patchset/20260611-airoha-eth-multi-serdes-stats-v1-1-42442ae42064%40kernel.org
[...]
>
> - /* reset mib counters */
> - airoha_fe_set(eth, REG_FE_GDM_MIB_CLEAR(port->id),
> +static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
> +{
> + struct airoha_gdm_port *port = dev->port;
> + int i;
> +
> + spin_lock(&port->stats_lock);
> +
> + for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
> + if (port->devs[i])
> + airoha_dev_get_hw_stats(port->devs[i]);
> + }
> +
> + /* Reset MIB counters */
> + airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
> FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
- Does clearing the MIB counters outside the device loop cause issues with the
split MIB feature?
airoha_dev_get_hw_stats() modifies REG_FE_GDM_MIB_CFG to select the specific
nbq channel. Since REG_FE_GDM_MIB_CLEAR is called after the loop, does it only
clear the hardware counters for the last selected nbq? If so, the uncleared
counters for the other channels might accumulate in hardware, and adding them
repeatedly to the software stats (e.g., dev->stats.tx_ok_pkts += val) could
result in exponential growth.
Alternatively, if REG_FE_GDM_MIB_CLEAR globally clears all nbq counters for
the port, calling it here could drop packets that arrived for earlier devices
between their respective read operations and this clear.
- REG_FE_GDM_MIB_CLEAR is global and it clears all nbq counters for the port.
There's a tiny window where packets arriving for dev[0] between its read
and the global clear are lost, but that's microseconds of register reads,
so I guess that's negligible.
Regards,
Lorenzo
>
> - u64_stats_update_end(&port->stats.syncp);
> - spin_unlock(&port->stats.lock);
> + spin_unlock(&port->stats_lock);
> }
>
> static int airoha_dev_open(struct net_device *netdev)
> @@ -2043,23 +2071,22 @@ static void airoha_dev_get_stats64(struct net_device *netdev,
> struct rtnl_link_stats64 *storage)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
> unsigned int start;
>
> airoha_update_hw_stats(dev);
> do {
> - start = u64_stats_fetch_begin(&port->stats.syncp);
> - storage->rx_packets = port->stats.rx_ok_pkts;
> - storage->tx_packets = port->stats.tx_ok_pkts;
> - storage->rx_bytes = port->stats.rx_ok_bytes;
> - storage->tx_bytes = port->stats.tx_ok_bytes;
> - storage->multicast = port->stats.rx_multicast;
> - storage->rx_errors = port->stats.rx_errors;
> - storage->rx_dropped = port->stats.rx_drops;
> - storage->tx_dropped = port->stats.tx_drops;
> - storage->rx_crc_errors = port->stats.rx_crc_error;
> - storage->rx_over_errors = port->stats.rx_over_errors;
> - } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> + start = u64_stats_fetch_begin(&dev->stats.syncp);
> + storage->rx_packets = dev->stats.rx_ok_pkts;
> + storage->tx_packets = dev->stats.tx_ok_pkts;
> + storage->rx_bytes = dev->stats.rx_ok_bytes;
> + storage->tx_bytes = dev->stats.tx_ok_bytes;
> + storage->multicast = dev->stats.rx_multicast;
> + storage->rx_errors = dev->stats.rx_errors;
> + storage->rx_dropped = dev->stats.rx_drops;
> + storage->tx_dropped = dev->stats.tx_drops;
> + storage->rx_crc_errors = dev->stats.rx_crc_error;
> + storage->rx_over_errors = dev->stats.rx_over_errors;
> + } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
> }
>
> static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
> @@ -2310,20 +2337,19 @@ static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
> struct ethtool_eth_mac_stats *stats)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
> unsigned int start;
>
> airoha_update_hw_stats(dev);
> do {
> - start = u64_stats_fetch_begin(&port->stats.syncp);
> - stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
> - stats->OctetsTransmittedOK = port->stats.tx_ok_bytes;
> - stats->MulticastFramesXmittedOK = port->stats.tx_multicast;
> - stats->BroadcastFramesXmittedOK = port->stats.tx_broadcast;
> - stats->FramesReceivedOK = port->stats.rx_ok_pkts;
> - stats->OctetsReceivedOK = port->stats.rx_ok_bytes;
> - stats->BroadcastFramesReceivedOK = port->stats.rx_broadcast;
> - } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> + start = u64_stats_fetch_begin(&dev->stats.syncp);
> + stats->FramesTransmittedOK = dev->stats.tx_ok_pkts;
> + stats->OctetsTransmittedOK = dev->stats.tx_ok_bytes;
> + stats->MulticastFramesXmittedOK = dev->stats.tx_multicast;
> + stats->BroadcastFramesXmittedOK = dev->stats.tx_broadcast;
> + stats->FramesReceivedOK = dev->stats.rx_ok_pkts;
> + stats->OctetsReceivedOK = dev->stats.rx_ok_bytes;
> + stats->BroadcastFramesReceivedOK = dev->stats.rx_broadcast;
> + } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
> }
>
> static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
> @@ -2343,8 +2369,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
> const struct ethtool_rmon_hist_range **ranges)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
> - struct airoha_hw_stats *hw_stats = &port->stats;
> + struct airoha_hw_stats *hw_stats = &dev->stats;
> unsigned int start;
>
> BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
> @@ -2357,7 +2382,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
> do {
> int i;
>
> - start = u64_stats_fetch_begin(&port->stats.syncp);
> + start = u64_stats_fetch_begin(&dev->stats.syncp);
> stats->fragments = hw_stats->rx_fragment;
> stats->jabbers = hw_stats->rx_jabber;
> for (i = 0; i < ARRAY_SIZE(airoha_ethtool_rmon_ranges) - 1;
> @@ -2365,7 +2390,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
> stats->hist[i] = hw_stats->rx_len[i];
> stats->hist_tx[i] = hw_stats->tx_len[i];
> }
> - } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> + } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
> }
>
> static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
> @@ -3205,6 +3230,7 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
>
> netdev->dev.of_node = of_node_get(np);
> dev = netdev_priv(netdev);
> + u64_stats_init(&dev->stats.syncp);
> dev->port = port;
> dev->eth = eth;
> dev->nbq = nbq;
> @@ -3244,9 +3270,8 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
> if (!port)
> return -ENOMEM;
>
> - u64_stats_init(&port->stats.syncp);
> - spin_lock_init(&port->stats.lock);
> port->id = id;
> + spin_lock_init(&port->stats_lock);
> eth->ports[p] = port;
>
> err = airoha_metadata_dst_alloc(port);
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index 8f42973f9cf5..46b1c31939de 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -215,8 +215,6 @@ struct airoha_tx_irq_queue {
> };
>
> struct airoha_hw_stats {
> - /* protect concurrent hw_stats accesses */
> - spinlock_t lock;
> struct u64_stats_sync syncp;
>
> /* get_stats64 */
> @@ -554,6 +552,8 @@ struct airoha_gdm_dev {
>
> u32 flags;
> int nbq;
> +
> + struct airoha_hw_stats stats;
> };
>
> struct airoha_gdm_port {
> @@ -561,7 +561,8 @@ struct airoha_gdm_port {
> int id;
> int users;
>
> - struct airoha_hw_stats stats;
> + /* protect concurrent hw_stats accesses */
> + spinlock_t stats_lock;
>
> struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
> };
>
> ---
> base-commit: c8459ee2fef502d6ef6c063751c33d9ac7943eab
> change-id: 20260611-airoha-eth-multi-serdes-stats-df2dc16c2dd6
>
> Best regards,
> --
> Lorenzo Bianconi <lorenzo@kernel.org>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net-next] net: airoha: better handle MIBs for GDM ports with multiple devs attached
2026-06-11 10:43 [PATCH net-next] net: airoha: better handle MIBs for GDM ports with multiple devs attached Lorenzo Bianconi
2026-06-12 11:04 ` Lorenzo Bianconi
@ 2026-06-12 14:20 ` Lorenzo Bianconi
2026-06-12 17:08 ` Simon Horman
2 siblings, 0 replies; 4+ messages in thread
From: Lorenzo Bianconi @ 2026-06-12 14:20 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: linux-arm-kernel, linux-mediatek, netdev, Christian Marangi
[-- Attachment #1: Type: text/plain, Size: 9780 bytes --]
> In the context of a GDM port that can have multiple net_devices attached
> (GDM3 and GDM4), the HW counters (MIBs) are global for the GDM port.
> This cause duplicated stats reported to the kernel for the related
> net_device.
> The SoC supports a split MIB feature where each counter is tracked based
> on the relevant HW channel (NBQ) to account for this scenario and
> provide a way to select the related counter on accessing the MIB
> registers.
> Enable this feature for GDM3 and GDM4 and configure the relevant HW
> channel before updating the HW stats to report correct HW counter to the
> kernel for the related interface.
> Move the stats struct from port to dev since HW counter are now specific
> to the network device instead of the GDM port. Refactor
> airoha_update_hw_stats() to take airoha_eth and airoha_gdm_port
> parameters since the function operates on the entire port.
>
> Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 191 +++++++++++++++++--------------
> drivers/net/ethernet/airoha/airoha_eth.h | 7 +-
> 2 files changed, 112 insertions(+), 86 deletions(-)
>
[...]
> - airoha_fe_set(eth, REG_FE_GDM_MIB_CLEAR(port->id),
> +static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
> +{
> + struct airoha_gdm_port *port = dev->port;
> + int i;
> +
> + spin_lock(&port->stats_lock);
> +
> + for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
> + if (port->devs[i])
> + airoha_dev_get_hw_stats(port->devs[i]);
> + }
> +
> + /* Reset MIB counters */
> + airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
> FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
commenting on sashiko's report:
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260611-airoha-eth-multi-serdes-stats-v1-1-42442ae42064%40kernel.org
- With split MIB enabled on GDM3/GDM4, airoha_dev_get_hw_stats() programs
REG_FE_GDM_MIB_CFG with the device's nbq before reading per-channel
counters, so the FE_TX_MIB_ID/FE_RX_MIB_ID selector clearly gates the
counter reads.
After the loop in airoha_update_hw_stats(), MIB_CFG retains the last
iterated device's nbq, and a single airoha_fe_set() is issued against
REG_FE_GDM_MIB_CLEAR. REG_FE_GDM_MIB_CLEAR exposes only RX/TX bits with no
separate per-channel selector.
If, as is typical of split-MIB designs, the clear honors the same
FE_TX_MIB_ID/FE_RX_MIB_ID selector used for reads, will only the last
iterated device's HW counters be cleared each cycle?
Since airoha_dev_get_hw_stats() accumulates with += on each read, would
the un-cleared HW counters of the other devs keep being re-added to
dev->stats on every fetch, yielding inflated stats and eventual HW
counter overflow?
Would moving the MIB_CLEAR write inside the per-device loop, while that
device's nbq is still selected in MIB_CFG, address this if the clear is
indeed per-channel? Or can the datasheet confirm REG_FE_GDM_MIB_CLEAR is
strictly global and unaffected by split mode?
- This is the same report available in [0].
REG_FE_GDM_MIB_CLEAR is global and it clears all nbq counters for the port.
Regards,
Lorenzo
[0] https://sashiko.dev/#/patchset/20260611-airoha-eth-multi-serdes-stats-v1-1-42442ae42064%40kernel.org
>
> - u64_stats_update_end(&port->stats.syncp);
> - spin_unlock(&port->stats.lock);
> + spin_unlock(&port->stats_lock);
> }
>
> static int airoha_dev_open(struct net_device *netdev)
> @@ -2043,23 +2071,22 @@ static void airoha_dev_get_stats64(struct net_device *netdev,
> struct rtnl_link_stats64 *storage)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
> unsigned int start;
>
> airoha_update_hw_stats(dev);
> do {
> - start = u64_stats_fetch_begin(&port->stats.syncp);
> - storage->rx_packets = port->stats.rx_ok_pkts;
> - storage->tx_packets = port->stats.tx_ok_pkts;
> - storage->rx_bytes = port->stats.rx_ok_bytes;
> - storage->tx_bytes = port->stats.tx_ok_bytes;
> - storage->multicast = port->stats.rx_multicast;
> - storage->rx_errors = port->stats.rx_errors;
> - storage->rx_dropped = port->stats.rx_drops;
> - storage->tx_dropped = port->stats.tx_drops;
> - storage->rx_crc_errors = port->stats.rx_crc_error;
> - storage->rx_over_errors = port->stats.rx_over_errors;
> - } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> + start = u64_stats_fetch_begin(&dev->stats.syncp);
> + storage->rx_packets = dev->stats.rx_ok_pkts;
> + storage->tx_packets = dev->stats.tx_ok_pkts;
> + storage->rx_bytes = dev->stats.rx_ok_bytes;
> + storage->tx_bytes = dev->stats.tx_ok_bytes;
> + storage->multicast = dev->stats.rx_multicast;
> + storage->rx_errors = dev->stats.rx_errors;
> + storage->rx_dropped = dev->stats.rx_drops;
> + storage->tx_dropped = dev->stats.tx_drops;
> + storage->rx_crc_errors = dev->stats.rx_crc_error;
> + storage->rx_over_errors = dev->stats.rx_over_errors;
> + } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
> }
>
> static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
> @@ -2310,20 +2337,19 @@ static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
> struct ethtool_eth_mac_stats *stats)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
> unsigned int start;
>
> airoha_update_hw_stats(dev);
> do {
> - start = u64_stats_fetch_begin(&port->stats.syncp);
> - stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
> - stats->OctetsTransmittedOK = port->stats.tx_ok_bytes;
> - stats->MulticastFramesXmittedOK = port->stats.tx_multicast;
> - stats->BroadcastFramesXmittedOK = port->stats.tx_broadcast;
> - stats->FramesReceivedOK = port->stats.rx_ok_pkts;
> - stats->OctetsReceivedOK = port->stats.rx_ok_bytes;
> - stats->BroadcastFramesReceivedOK = port->stats.rx_broadcast;
> - } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> + start = u64_stats_fetch_begin(&dev->stats.syncp);
> + stats->FramesTransmittedOK = dev->stats.tx_ok_pkts;
> + stats->OctetsTransmittedOK = dev->stats.tx_ok_bytes;
> + stats->MulticastFramesXmittedOK = dev->stats.tx_multicast;
> + stats->BroadcastFramesXmittedOK = dev->stats.tx_broadcast;
> + stats->FramesReceivedOK = dev->stats.rx_ok_pkts;
> + stats->OctetsReceivedOK = dev->stats.rx_ok_bytes;
> + stats->BroadcastFramesReceivedOK = dev->stats.rx_broadcast;
> + } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
> }
>
> static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
> @@ -2343,8 +2369,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
> const struct ethtool_rmon_hist_range **ranges)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_gdm_port *port = dev->port;
> - struct airoha_hw_stats *hw_stats = &port->stats;
> + struct airoha_hw_stats *hw_stats = &dev->stats;
> unsigned int start;
>
> BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
> @@ -2357,7 +2382,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
> do {
> int i;
>
> - start = u64_stats_fetch_begin(&port->stats.syncp);
> + start = u64_stats_fetch_begin(&dev->stats.syncp);
> stats->fragments = hw_stats->rx_fragment;
> stats->jabbers = hw_stats->rx_jabber;
> for (i = 0; i < ARRAY_SIZE(airoha_ethtool_rmon_ranges) - 1;
> @@ -2365,7 +2390,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
> stats->hist[i] = hw_stats->rx_len[i];
> stats->hist_tx[i] = hw_stats->tx_len[i];
> }
> - } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> + } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
> }
>
> static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
> @@ -3205,6 +3230,7 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
>
> netdev->dev.of_node = of_node_get(np);
> dev = netdev_priv(netdev);
> + u64_stats_init(&dev->stats.syncp);
> dev->port = port;
> dev->eth = eth;
> dev->nbq = nbq;
> @@ -3244,9 +3270,8 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
> if (!port)
> return -ENOMEM;
>
> - u64_stats_init(&port->stats.syncp);
> - spin_lock_init(&port->stats.lock);
> port->id = id;
> + spin_lock_init(&port->stats_lock);
> eth->ports[p] = port;
>
> err = airoha_metadata_dst_alloc(port);
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index 8f42973f9cf5..46b1c31939de 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -215,8 +215,6 @@ struct airoha_tx_irq_queue {
> };
>
> struct airoha_hw_stats {
> - /* protect concurrent hw_stats accesses */
> - spinlock_t lock;
> struct u64_stats_sync syncp;
>
> /* get_stats64 */
> @@ -554,6 +552,8 @@ struct airoha_gdm_dev {
>
> u32 flags;
> int nbq;
> +
> + struct airoha_hw_stats stats;
> };
>
> struct airoha_gdm_port {
> @@ -561,7 +561,8 @@ struct airoha_gdm_port {
> int id;
> int users;
>
> - struct airoha_hw_stats stats;
> + /* protect concurrent hw_stats accesses */
> + spinlock_t stats_lock;
>
> struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
> };
>
> ---
> base-commit: c8459ee2fef502d6ef6c063751c33d9ac7943eab
> change-id: 20260611-airoha-eth-multi-serdes-stats-df2dc16c2dd6
>
> Best regards,
> --
> Lorenzo Bianconi <lorenzo@kernel.org>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net-next] net: airoha: better handle MIBs for GDM ports with multiple devs attached
2026-06-11 10:43 [PATCH net-next] net: airoha: better handle MIBs for GDM ports with multiple devs attached Lorenzo Bianconi
2026-06-12 11:04 ` Lorenzo Bianconi
2026-06-12 14:20 ` Lorenzo Bianconi
@ 2026-06-12 17:08 ` Simon Horman
2 siblings, 0 replies; 4+ messages in thread
From: Simon Horman @ 2026-06-12 17:08 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-arm-kernel, linux-mediatek, netdev,
Christian Marangi
On Thu, Jun 11, 2026 at 12:43:00PM +0200, Lorenzo Bianconi wrote:
> In the context of a GDM port that can have multiple net_devices attached
> (GDM3 and GDM4), the HW counters (MIBs) are global for the GDM port.
> This cause duplicated stats reported to the kernel for the related
> net_device.
> The SoC supports a split MIB feature where each counter is tracked based
> on the relevant HW channel (NBQ) to account for this scenario and
> provide a way to select the related counter on accessing the MIB
> registers.
> Enable this feature for GDM3 and GDM4 and configure the relevant HW
> channel before updating the HW stats to report correct HW counter to the
> kernel for the related interface.
> Move the stats struct from port to dev since HW counter are now specific
> to the network device instead of the GDM port. Refactor
> airoha_update_hw_stats() to take airoha_eth and airoha_gdm_port
> parameters since the function operates on the entire port.
>
> Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Reviewed-by: Simon Horman <horms@kernel.org>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-06-12 17:09 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-11 10:43 [PATCH net-next] net: airoha: better handle MIBs for GDM ports with multiple devs attached Lorenzo Bianconi
2026-06-12 11:04 ` Lorenzo Bianconi
2026-06-12 14:20 ` Lorenzo Bianconi
2026-06-12 17:08 ` Simon Horman
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.