* Re: [PATCH net] net: emac: mal: replace devm_request_irq with request_irq to fix probe error race
From: Andrew Lunn @ 2026-07-03 16:38 UTC (permalink / raw)
To: Rosen Penev
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list
In-Reply-To: <20260702235045.1326384-1-rosenp@gmail.com>
On Thu, Jul 02, 2026 at 04:50:45PM -0700, Rosen Penev wrote:
> devm_request_irq() is a managed resource: the IRQ is not freed until
> devres_release_all() runs after the probe function returns. In the
> probe error path, free_netdev(mal->dummy_dev) and dcr_unmap() execute
> while the IRQ is still live. If the shared IRQ fires during cleanup,
> the handler accesses unmapped DCR registers (crash) or the already-
> freed dummy_dev (use-after-free).
>
> Switch to plain request_irq() with per-IRQ error labels that tear down
> only the IRQs that were successfully registered, and add the matching
> free_irq() calls in mal_remove().
>
> Fixes: 14f59154ff0b ("net: ibm: emac: mal: use devm for request_irq")
> Assisted-by: opencode:big-pickle
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
You seemed to of sent the same patch within 24 hours. Please don't do
that.
https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
Send a self NACK to the broken version, wait 24 hours, and send v2.
Andrew
---
pw-bot: cr
^ permalink raw reply
* Re: [PATCH net-next 0/2] macvlan: RTNL-less macvlan_fill_info()
From: patchwork-bot+netdevbpf @ 2026-07-03 16:40 UTC (permalink / raw)
To: Eric Dumazet; +Cc: davem, kuba, pabeni, horms, kuniyu, netdev, eric.dumazet
In-Reply-To: <20260701082214.2974946-1-edumazet@google.com>
Hello:
This series was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Wed, 1 Jul 2026 08:22:12 +0000 you wrote:
> This series removes the RTNL lock dependency from macvlan_fill_info(),
> allowing it to run under RCU read lock.
>
> The first patch annotates data races on 'mode' and 'flags' fields which
> are accessed locklessly in the RX/TX paths.
>
> The second patch transitions macvlan_fill_info() to RCU, adding necessary
> annotations for other fields and handling concurrent updates to the MAC
> address list by computing the count dynamically.
>
> [...]
Here is the summary with links:
- [net-next,1/2] macvlan: annotate data-races around vlan->mode and vlan->flags
https://git.kernel.org/netdev/net-next/c/ed37710d6c67
- [net-next,2/2] macvlan: no longer rely on RTNL in macvlan_fill_info()
https://git.kernel.org/netdev/net-next/c/6d728e7e286b
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH net-next v7 1/2] dinghai: add ZTE network driver support
From: Paolo Abeni @ 2026-07-03 16:42 UTC (permalink / raw)
To: han.junyang
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms, linux-kernel,
netdev, ran.ming, han.chengfei, zhang.yanze
In-Reply-To: <20260630111300830lukBczfNSgWE5wt6qR95k@zte.com.cn>
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev/
---
dinghai: add ZTE network driver support
Add basic framework for ZTE DingHai ethernet PF driver, including
Kconfig/Makefile build support and PCIe device probe/remove skeleton.
> diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
> new file mode 100644
> index 0000000000000..b492fd2c261b8
> --- /dev/null
> +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
[ ... ]
> +static int dh_pf_pci_init(struct dh_core_dev *dev)
> +{
> + struct zxdh_pf_device *pf_dev = dev->priv;
> + int ret;
[ ... ]
> + pf_dev->pci_ioremap_addr[0] =
> + ioremap(pci_resource_start(dev->pdev, 0),
> + pci_resource_len(dev->pdev, 0));
[Severity: Medium]
Should this check if BAR 0 is actually an IORESOURCE_MEM before mapping it?
Calling ioremap() on an I/O port address or an unpopulated BAR could map
arbitrary physical memory.
Would it be safer to use pci_ioremap_bar() or pcim_iomap() which enforce
resource type and length checks?
> + if (!pf_dev->pci_ioremap_addr[0]) {
> + ret = -ENOMEM;
> + dev_err(dev->device, "dh pf pci ioremap failed\n");
> + goto err_pci_save_state;
> + }
[ ... ]
> +static void dh_pf_remove(struct pci_dev *pdev)
> +{
> + struct dh_core_dev *dh_dev = pci_get_drvdata(pdev);
> + struct devlink *devlink = priv_to_devlink(dh_dev);
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> +
> + devlink_unregister(devlink);
> + dh_pf_pci_close(dh_dev);
> + mutex_destroy(&pf_dev->irq_lock);
> + mutex_destroy(&dh_dev->lock);
> + dh_core_free_priv(dh_dev);
> + devlink_free(devlink);
> + pci_set_drvdata(pdev, NULL);
> +}
> +
> +static void dh_pf_shutdown(struct pci_dev *pdev)
> +{
> + struct dh_core_dev *dh_dev = pci_get_drvdata(pdev);
> + struct devlink *devlink = priv_to_devlink(dh_dev);
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> +
> + devlink_unregister(devlink);
> + dh_pf_pci_close(dh_dev);
> + mutex_destroy(&pf_dev->irq_lock);
> + mutex_destroy(&dh_dev->lock);
> + dh_core_free_priv(dh_dev);
> + devlink_free(devlink);
> + pci_set_drvdata(pdev, NULL);
> +}
[Severity: High]
Does this destructive teardown in the .shutdown callback create a race if
.remove is called afterwards?
If .remove is triggered (for example, during a hot-remove event during
shutdown), pci_get_drvdata() in dh_pf_remove() will return NULL.
The macro priv_to_devlink() will then convert NULL into a bad pointer,
causing a panic in devlink_unregister().
Should .shutdown only quiesce the hardware without destroying software
structures?
--
This is an AI-generated review.
^ permalink raw reply
* Re: [PATCH net-next v7 2/2] dinghai: add hardware register access and PCI capability scanning
From: Paolo Abeni @ 2026-07-03 16:42 UTC (permalink / raw)
To: han.junyang
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms, linux-kernel,
netdev, ran.ming, han.chengfei, zhang.yanze
In-Reply-To: <20260630111532964R8a8-5zlzQRMas-VWfCi2@zte.com.cn>
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev/
---
dinghai: add hardware register access and PCI capability scanning
Implement PCI configuration space access, BAR mapping, capability
scanning (common/notify/device), and hardware queue register
definitions for DingHai PF device.
> diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
> index b492fd2c261b8..fb80b0baca34d 100644
> --- a/drivers/net/ethernet/zte/dinghai/en_pf.c
> +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
[ ... ]
> @@ -104,6 +105,443 @@ void dh_pf_pci_close(struct dh_core_dev *dev)
[ ... ]
> +void __iomem *zxdh_pf_map_capability(struct dh_core_dev *dh_dev, int off,
> + size_t minlen, u32 align,
> + u32 start, u32 size,
> + size_t *len, resource_size_t *pa,
> + u32 *bar_off)
> +{
[ ... ]
> + if (length > size)
> + length = size;
> +
> + if (len)
> + *len = length;
> +
> + if (minlen + offset < minlen ||
> + minlen + offset > pci_resource_len(pdev, bar)) {
[Severity: High]
Does this code inflate the length returned in *len?
Because length is not capped by pci_resource_len(pdev, bar) - offset before
being assigned to *len, the returned length can be larger than the actually
mapped memory (which is capped by pci_iomap_range).
Later in zxdh_pf_map_vq_notify(), this inflated notify_len is used for bounds
checking, which could allow out-of-bounds MMIO accesses to pass the check.
[ ... ]
> +int zxdh_pf_device_cfg_init(struct dh_core_dev *dh_dev)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + struct pci_dev *pdev = dh_dev->pdev;
> + int device;
> +
> + /* Device capability is only mandatory for
> + * devices that have device-specific configuration.
> + */
> + device = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_DEVICE_CFG,
> + IORESOURCE_IO | IORESOURCE_MEM,
> + &pf_dev->modern_bars);
> +
> + /* we don't know how much we should map,
> + * but PAGE_SIZE is more than enough for all existing devices.
> + */
> + if (device) {
> + pf_dev->device = zxdh_pf_map_capability(dh_dev, device, 0,
> + ZXDH_PF_ALIGN4, 0, PAGE_SIZE,
> + &pf_dev->device_len, NULL,
> + &pf_dev->dev_cfg_bar_off);
> + if (!pf_dev->device) {
[Severity: High]
Could pf_dev->device remain NULL if the device capability isn't found?
If the capability is optional and not provided, pf_dev->device will be NULL.
Also, passing minlen = 0 allows undersized mappings.
Later, zxdh_get_mac() and zxdh_set_mac() unconditionally dereference
pf_dev->device, which could trigger a kernel panic due to a NULL pointer
dereference or out-of-bounds access.
[ ... ]
> +void __iomem *zxdh_pf_map_vq_notify(struct dh_core_dev *dh_dev,
> + u16 phy_index, u16 index,
> + resource_size_t *pa)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + u16 off;
> +
> + off = zxdh_pf_get_queue_notify_off(dh_dev, phy_index, index);
> +
> + if (pf_dev->notify_base) {
[ ... ]
> + } else {
> + return zxdh_pf_map_capability(dh_dev, pf_dev->notify_map_cap, 2, 2,
> + off * pf_dev->notify_offset_multiplier,
> + 2, NULL, pa, NULL);
> + }
> +}
[Severity: Medium]
Can the offset calculation overflow before being passed?
Since off is u16 and pf_dev->notify_offset_multiplier is u32,
off * pf_dev->notify_offset_multiplier evaluates as a 32-bit unsigned integer.
If the product exceeds 4GB, it wraps around before being passed to
zxdh_pf_map_capability(). A 64-bit cast is needed to prevent mapping incorrect
device registers.
[ ... ]
> +void zxdh_pf_get_vf_mac(struct dh_core_dev *dh_dev, u8 *mac, int vf_id)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + u32 DEV_MAC_L;
> + u16 DEV_MAC_H;
> +
> + if (pf_dev->pf_sriov_cap_base) {
> + DEV_MAC_L = ioread32(pf_dev->pf_sriov_cap_base +
> + (pf_dev->sriov_bar_size) * vf_id +
> + pf_dev->dev_cfg_bar_off);
> + mac[0] = DEV_MAC_L & 0xff;
> + mac[1] = (DEV_MAC_L >> 8) & 0xff;
> + mac[2] = (DEV_MAC_L >> 16) & 0xff;
> + mac[3] = (DEV_MAC_L >> 24) & 0xff;
> + DEV_MAC_H = ioread16(pf_dev->pf_sriov_cap_base +
> + (pf_dev->sriov_bar_size) * vf_id +
> + pf_dev->dev_cfg_bar_off +
> + ZXDH_DEV_MAC_HIGH_OFFSET);
> + mac[4] = DEV_MAC_H & 0xff;
> + mac[5] = (DEV_MAC_H >> 8) & 0xff;
> + }
> +}
[Severity: High]
Does this code silently return without initializing the buffer?
If pf_dev->pf_sriov_cap_base is NULL, the function exits without modifying the
output mac buffer or returning an error code.
The caller might proceed using the uninitialized contents of the array, which
could expose uninitialized kernel stack bytes.
[ ... ]
> +void zxdh_set_mac(struct dh_core_dev *dh_dev, u8 *mac)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + u32 DEV_MAC_L;
> + u16 DEV_MAC_H;
> +
> + DEV_MAC_L = mac[0] | (mac[1] << 8) | (mac[2] << 16) | (mac[3] << 24);
> + DEV_MAC_H = mac[4] | (mac[5] << 8);
> + iowrite32(DEV_MAC_L, pf_dev->device);
> + iowrite16(DEV_MAC_H, pf_dev->device + ZXDH_DEV_MAC_HIGH_OFFSET);
> +}
> +
> +void zxdh_get_mac(struct dh_core_dev *dh_dev, u8 *mac)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + u32 DEV_MAC_L;
> + u16 DEV_MAC_H;
> +
> + DEV_MAC_L = ioread32(pf_dev->device);
> + mac[0] = DEV_MAC_L & 0xff;
> + mac[1] = (DEV_MAC_L >> 8) & 0xff;
> + mac[2] = (DEV_MAC_L >> 16) & 0xff;
> + mac[3] = (DEV_MAC_L >> 24) & 0xff;
> + DEV_MAC_H = ioread16(pf_dev->device + ZXDH_DEV_MAC_HIGH_OFFSET);
> + mac[4] = DEV_MAC_H & 0xff;
> + mac[5] = (DEV_MAC_H >> 8) & 0xff;
> +}
[Severity: High]
Can these functions trigger a NULL pointer dereference?
In zxdh_pf_device_cfg_init(), pf_dev->device can be left as NULL if the device
capability is missing, or it could be undersized.
zxdh_set_mac() and zxdh_get_mac() unconditionally dereference pf_dev->device,
which could trigger a kernel panic.
--
This is an AI-generated review.
^ permalink raw reply
* Re: [PATCH 1/1] net: usb: aqc111: fix set_mac_address return value for bonding
From: Andrew Lunn @ 2026-07-03 16:46 UTC (permalink / raw)
To: Hanson Wang; +Cc: netdev, linux-usb, oneukum
In-Reply-To: <20260703073936.462231-1-hanson.wang@ugreen.com>
On Fri, Jul 03, 2026 at 03:39:36PM +0800, Hanson Wang wrote:
> aqc111_set_mac_addr() returns the result of aqc111_write_cmd() on
> success. That function wraps usb_control_msg(), which returns the
> number of bytes transferred (6 for ETH_ALEN) rather than zero.
>
> Bonding calls ndo_set_mac_address() when enslaving an interface and
> treats any non-zero return value as failure.
It is not just bonding which has problems:
int netif_set_mac_address(struct net_device *dev, struct sockaddr_storage *ss,
struct netlink_ext_ack *extack)
{
const struct net_device_ops *ops = dev->netdev_ops;
int err;
if (!ops->ndo_set_mac_address)
return -EOPNOTSUPP;
if (ss->ss_family != dev->type)
return -EINVAL;
if (!netif_device_present(dev))
return -ENODEV;
err = netif_pre_changeaddr_notify(dev, ss->__data, extack);
if (err)
return err;
if (memcmp(dev->dev_addr, ss->__data, dev->addr_len)) {
err = ops->ndo_set_mac_address(dev, ss);
if (err)
return err;
Could you take a quick look at all USB ethernet drivers, and see if
this bug exists in other drivers. If one driver has it wrong, it could
well be more have it wrong.
Andrew
^ permalink raw reply
* Re: [PATCH] net: qed: Fix spelling typo in qed_dcbx.c comment
From: Andrew Lunn @ 2026-07-03 16:49 UTC (permalink / raw)
To: Praveen Rajendran
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, netdev,
linux-kernel
In-Reply-To: <20260703143130.3685-1-praveenrajendran2009@gmail.com>
On Fri, Jul 03, 2026 at 08:01:30PM +0530, Praveen Rajendran wrote:
> Correct a minor spelling error inside a comment block of the QLogic
> Core module where "successfully" was misspelled as "successfuly".
>
> Signed-off-by: Praveen Rajendran <praveenrajendran2009@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Before submitting more patches to netdev, please read:
https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
Andrew
^ permalink raw reply
* Re: [PATCH net V2 0/3] net/mlx5: LAG bug fixes
From: patchwork-bot+netdevbpf @ 2026-07-03 16:50 UTC (permalink / raw)
To: Tariq Toukan
Cc: andrew+netdev, davem, edumazet, kuba, netdev, pabeni, edwards,
jacob.e.keller, kees, leon, linux-kernel, linux-rdma, msanalla,
mbloch, moshe, ohartoov, rongweil, saeedm, shayd, horms
In-Reply-To: <20260630112917.698313-1-tariqt@nvidia.com>
Hello:
This series was applied to netdev/net.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Tue, 30 Jun 2026 14:29:14 +0300 you wrote:
> Hi,
>
> Three bug fixes by Shay in the mlx5 LAG subsystem.
>
> Patch 1 fixes an off-by-one in the error rollback path of
> mlx5_lag_create_single_fdb_filter(): the loop started from the
> failed index i, potentially operating on uninitialized state or
> double-tearing-down an entry that had already self-rolled-back.
> The rollback should start from i - 1.
>
> [...]
Here is the summary with links:
- [net,V2,1/3] net/mlx5: LAG, Fix off-by-one in single-FDB error rollback
https://git.kernel.org/netdev/net/c/0f0e4ae6975c
- [net,V2,2/3] net/mlx5: LAG, MPESW, Fix missing complete() on devcom error
https://git.kernel.org/netdev/net/c/d4b85f9a668b
- [net,V2,3/3] net/mlx5e: TC, skip peer flow cleanup when LAG seq is unavailable
https://git.kernel.org/netdev/net/c/7bed4af0ced8
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH net-next] macsec: no longer rely on RTNL in macsec_fill_info()
From: patchwork-bot+netdevbpf @ 2026-07-03 16:50 UTC (permalink / raw)
To: Eric Dumazet
Cc: davem, kuba, pabeni, horms, kuniyu, netdev, eric.dumazet, sd,
andrew+netdev
In-Reply-To: <20260701094341.3218199-1-edumazet@google.com>
Hello:
This patch was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Wed, 1 Jul 2026 09:43:41 +0000 you wrote:
> Add READ_ONCE()/WRITE_ONCE() annotations on fields that can be
> changed concurrently in macsec_changelink() and macsec_update_offload():
>
> - secy->key_len
> - secy->xpn
> - tx_sc->encoding_sa
> - tx_sc->encrypt
> - secy->protect_frames
> - tx_sc->send_sci
> - tx_sc->end_station
> - tx_sc->scb
> - secy->replay_protect
> - secy->validate_frames
> - secy->replay_window
> - macsec->offload
>
> [...]
Here is the summary with links:
- [net-next] macsec: no longer rely on RTNL in macsec_fill_info()
https://git.kernel.org/netdev/net-next/c/5a5ebefdab9d
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [PATCH net-next v4 0/3] net: dsa: motorcomm: Add LED support
From: David Yang @ 2026-07-03 16:51 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
v3: https://lore.kernel.org/r/20260701155519.273212-1-mmyangfl@gmail.com
- fix null pointer dereference
- support polarity auto-configuration
v2: https://lore.kernel.org/r/20260629183137.541341-1-mmyangfl@gmail.com
- allocate LED structures only
- eliminate double locking
v1: https://lore.kernel.org/r/20260618202716.2166450-1-mmyangfl@gmail.com
- set up polarity correctly
- do not set up .brightness_get() to prevent dead lock
David Yang (3):
net: dsa: motorcomm: Move to subdirectory
net: dsa: motorcomm: Split SMI module
net: dsa: motorcomm: Add LED support
MAINTAINERS | 2 +-
drivers/net/dsa/Kconfig | 10 +-
drivers/net/dsa/Makefile | 2 +-
drivers/net/dsa/motorcomm/Kconfig | 17 +
drivers/net/dsa/motorcomm/Makefile | 5 +
.../net/dsa/{yt921x.c => motorcomm/chip.c} | 218 +------
.../net/dsa/{yt921x.h => motorcomm/chip.h} | 14 +-
drivers/net/dsa/motorcomm/leds.c | 607 ++++++++++++++++++
drivers/net/dsa/motorcomm/leds.h | 118 ++++
drivers/net/dsa/motorcomm/smi.c | 157 +++++
drivers/net/dsa/motorcomm/smi.h | 88 +++
11 files changed, 1018 insertions(+), 220 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/Kconfig
create mode 100644 drivers/net/dsa/motorcomm/Makefile
rename drivers/net/dsa/{yt921x.c => motorcomm/chip.c} (96%)
rename drivers/net/dsa/{yt921x.h => motorcomm/chip.h} (99%)
create mode 100644 drivers/net/dsa/motorcomm/leds.c
create mode 100644 drivers/net/dsa/motorcomm/leds.h
create mode 100644 drivers/net/dsa/motorcomm/smi.c
create mode 100644 drivers/net/dsa/motorcomm/smi.h
--
2.53.0
^ permalink raw reply
* [PATCH net-next v4 1/3] net: dsa: motorcomm: Move to subdirectory
From: David Yang @ 2026-07-03 16:51 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
In-Reply-To: <20260703165241.542195-1-mmyangfl@gmail.com>
yt921x is already the longest single-file DSA driver, so it's time to
split it into parts.
Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
MAINTAINERS | 2 +-
drivers/net/dsa/Kconfig | 10 ++--------
drivers/net/dsa/Makefile | 2 +-
drivers/net/dsa/motorcomm/Kconfig | 8 ++++++++
drivers/net/dsa/motorcomm/Makefile | 3 +++
drivers/net/dsa/{yt921x.c => motorcomm/chip.c} | 2 +-
drivers/net/dsa/{yt921x.h => motorcomm/chip.h} | 0
7 files changed, 16 insertions(+), 11 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/Kconfig
create mode 100644 drivers/net/dsa/motorcomm/Makefile
rename drivers/net/dsa/{yt921x.c => motorcomm/chip.c} (99%)
rename drivers/net/dsa/{yt921x.h => motorcomm/chip.h} (100%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 06df1171f4cf..b007f20b2763 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18039,7 +18039,7 @@ M: David Yang <mmyangfl@gmail.com>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
-F: drivers/net/dsa/yt921x.*
+F: drivers/net/dsa/motorcomm/
F: net/dsa/tag_yt921x.c
MOXA SMARTIO/INDUSTIO/INTELLIO SERIAL CARD
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 4ab567c5bbaf..98e9bbe47de7 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -72,6 +72,8 @@ config NET_DSA_MV88E6060
source "drivers/net/dsa/microchip/Kconfig"
+source "drivers/net/dsa/motorcomm/Kconfig"
+
source "drivers/net/dsa/mv88e6xxx/Kconfig"
source "drivers/net/dsa/mxl862xx/Kconfig"
@@ -158,12 +160,4 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM
This enables support for the Vitesse VSC7385, VSC7388, VSC7395
and VSC7398 SparX integrated ethernet switches, connected over
a CPU-attached address bus and work in memory-mapped I/O mode.
-
-config NET_DSA_YT921X
- tristate "Motorcomm YT9215 ethernet switch chip support"
- select NET_DSA_TAG_YT921X
- select NET_IEEE8021Q_HELPERS if DCB
- help
- This enables support for the Motorcomm YT9215 ethernet switch
- chip.
endmenu
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index d2975badffc0..138225baa4d5 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -14,11 +14,11 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
-obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
obj-y += b53/
obj-y += hirschmann/
obj-y += lantiq/
obj-y += microchip/
+obj-y += motorcomm/
obj-y += mv88e6xxx/
obj-y += mxl862xx/
obj-y += netc/
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
new file mode 100644
index 000000000000..1fddd386f866
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: ISC
+config NET_DSA_YT921X
+ tristate "Motorcomm YT9215 ethernet switch chip support"
+ select NET_DSA_TAG_YT921X
+ select NET_IEEE8021Q_HELPERS if DCB
+ help
+ This enables support for the Motorcomm YT9215 ethernet switch
+ chip.
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
new file mode 100644
index 000000000000..afd03be9fa35
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: ISC
+obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
+yt921x-objs := chip.o
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/motorcomm/chip.c
similarity index 99%
rename from drivers/net/dsa/yt921x.c
rename to drivers/net/dsa/motorcomm/chip.c
index 159b16606f6c..f070732845eb 100644
--- a/drivers/net/dsa/yt921x.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -26,7 +26,7 @@
#include <net/ieee8021q.h>
#include <net/pkt_cls.h>
-#include "yt921x.h"
+#include "chip.h"
struct yt921x_mib_desc {
unsigned int size;
diff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/motorcomm/chip.h
similarity index 100%
rename from drivers/net/dsa/yt921x.h
rename to drivers/net/dsa/motorcomm/chip.h
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v4 2/3] net: dsa: motorcomm: Split SMI module
From: David Yang @ 2026-07-03 16:51 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
In-Reply-To: <20260703165241.542195-1-mmyangfl@gmail.com>
SMI operations are going to be used across different modules.
Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/motorcomm/Makefile | 1 +
drivers/net/dsa/motorcomm/chip.c | 207 +----------------------------
drivers/net/dsa/motorcomm/smi.c | 157 ++++++++++++++++++++++
drivers/net/dsa/motorcomm/smi.h | 88 ++++++++++++
4 files changed, 247 insertions(+), 206 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/smi.c
create mode 100644 drivers/net/dsa/motorcomm/smi.h
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
index afd03be9fa35..6cea5313a444 100644
--- a/drivers/net/dsa/motorcomm/Makefile
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: ISC
obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
yt921x-objs := chip.o
+yt921x-objs += smi.o
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index f070732845eb..6dee25b6754a 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -13,7 +13,6 @@
#include <linux/if_bridge.h>
#include <linux/if_hsr.h>
#include <linux/if_vlan.h>
-#include <linux/iopoll.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -27,6 +26,7 @@
#include <net/pkt_cls.h>
#include "chip.h"
+#include "smi.h"
struct yt921x_mib_desc {
unsigned int size;
@@ -155,9 +155,6 @@ static const struct yt921x_info yt921x_infos[] = {
#define YT921X_VID_UNWARE 4095
-#define YT921X_POLL_SLEEP_US 10000
-#define YT921X_POLL_TIMEOUT_US 100000
-
/* The interval should be small enough to avoid overflow of 32bit MIBs.
*
* Until we can read MIBs from stats64 call directly (i.e. sleep
@@ -196,208 +193,6 @@ static u32 ethaddr_lo2_to_u32(const unsigned char *addr)
return (addr[4] << 8) | addr[5];
}
-static int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
-{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
-
- return priv->reg_ops->read(priv->reg_ctx, reg, valp);
-}
-
-static int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
-{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
-
- return priv->reg_ops->write(priv->reg_ctx, reg, val);
-}
-
-static int
-yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
-{
- u32 val;
- int res;
- int ret;
-
- ret = read_poll_timeout(yt921x_reg_read, res,
- res || (val & mask) == *valp,
- YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
- false, priv, reg, &val);
- if (ret)
- return ret;
- if (res)
- return res;
-
- *valp = val;
- return 0;
-}
-
-static int
-yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
-{
- int res;
- u32 v;
- u32 u;
-
- res = yt921x_reg_read(priv, reg, &v);
- if (res)
- return res;
-
- u = v;
- u &= ~mask;
- u |= val;
- if (u == v)
- return 0;
-
- return yt921x_reg_write(priv, reg, u);
-}
-
-static int yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, 0, mask);
-}
-
-static int yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, mask, 0);
-}
-
-static int
-yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
-{
- return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
-}
-
-/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
- * long register. More specifically, writes to parts of its words won't become
- * visible, until the last word is written.
- *
- * Here we require full read and write operations over these registers to
- * eliminate potential issues, although partial reads/writes are also possible.
- */
-
-static void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
-{
- *lo &= ~lower_32_bits(mask);
- *hi &= ~upper_32_bits(mask);
- *lo |= lower_32_bits(val);
- *hi |= upper_32_bits(val);
-}
-
-static int
-yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals, unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- u |= vals[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 2);
-}
-
-static int
-yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals)
-{
- return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
-}
-
-static int
-yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
-{
- return yt921x_regs_clear_bits(priv, reg, masks, 2);
-}
-
-static int
-yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 3);
-}
-
static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
{
struct yt921x_reg_mdio *mdio = context;
diff --git a/drivers/net/dsa/motorcomm/smi.c b/drivers/net/dsa/motorcomm/smi.c
new file mode 100644
index 000000000000..9054896e4cd1
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/iopoll.h>
+
+#include "chip.h"
+#include "smi.h"
+
+#define YT921X_POLL_SLEEP_US 10000
+#define YT921X_POLL_TIMEOUT_US 100000
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->read(priv->reg_ctx, reg, valp);
+}
+
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->write(priv->reg_ctx, reg, val);
+}
+
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
+{
+ u32 val;
+ int res;
+ int ret;
+
+ ret = read_poll_timeout(yt921x_reg_read, res,
+ res || (val & mask) == *valp,
+ YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
+ false, priv, reg, &val);
+ if (ret)
+ return ret;
+ if (res)
+ return res;
+
+ *valp = val;
+ return 0;
+}
+
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
+{
+ int res;
+ u32 v;
+ u32 u;
+
+ res = yt921x_reg_read(priv, reg, &v);
+ if (res)
+ return res;
+
+ u = v;
+ u &= ~mask;
+ u |= val;
+ if (u == v)
+ return 0;
+
+ return yt921x_reg_write(priv, reg, u);
+}
+
+int
+yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+int
+yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+int
+yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals, unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ if (WARN_ON_ONCE(num_regs > ARRAY_SIZE(vs)))
+ return -EINVAL;
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ u |= vals[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
+
+int
+yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ if (WARN_ON_ONCE(num_regs > ARRAY_SIZE(vs)))
+ return -EINVAL;
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
diff --git a/drivers/net/dsa/motorcomm/smi.h b/drivers/net/dsa/motorcomm/smi.h
new file mode 100644
index 000000000000..2e956065eb90
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_SMI_H
+#define _YT_SMI_H
+
+#include <linux/types.h>
+#include <linux/wordpart.h>
+
+struct yt921x_priv;
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp);
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val);
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp);
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask,
+ u32 val);
+
+static inline int
+yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, 0, mask);
+}
+
+static inline int
+yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, 0);
+}
+
+static inline int
+yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
+}
+
+/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
+ * long register. More specifically, writes to parts of its words won't become
+ * visible, until the last word is written.
+ *
+ * Here we require full read and write operations over these registers to
+ * eliminate potential issues, although partial reads/writes are also possible.
+ */
+
+static inline void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
+{
+ *lo &= ~lower_32_bits(mask);
+ *hi &= ~upper_32_bits(mask);
+ *lo |= lower_32_bits(val);
+ *hi |= upper_32_bits(val);
+}
+
+int yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
+ unsigned int num_regs);
+int yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
+ unsigned int num_regs);
+int yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals, unsigned int num_regs);
+int yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ unsigned int num_regs);
+
+static inline int
+yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 2);
+}
+
+static inline int
+yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals)
+{
+ return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
+}
+
+static inline int
+yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
+{
+ return yt921x_regs_clear_bits(priv, reg, masks, 2);
+}
+
+static inline int
+yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 3);
+}
+
+#endif
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v4 3/3] net: dsa: motorcomm: Add LED support
From: David Yang @ 2026-07-03 16:51 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
In-Reply-To: <20260703165241.542195-1-mmyangfl@gmail.com>
LEDs can be described in the device tree using the same format as qca8k.
Each port can configure up to 3 LEDs.
Currently, only parallel mode and strict 1:1 mapping are supported.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/motorcomm/Kconfig | 9 +
drivers/net/dsa/motorcomm/Makefile | 1 +
drivers/net/dsa/motorcomm/chip.c | 9 +-
drivers/net/dsa/motorcomm/chip.h | 14 +-
drivers/net/dsa/motorcomm/leds.c | 607 +++++++++++++++++++++++++++++
drivers/net/dsa/motorcomm/leds.h | 118 ++++++
6 files changed, 755 insertions(+), 3 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/leds.c
create mode 100644 drivers/net/dsa/motorcomm/leds.h
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
index 1fddd386f866..3438e0b14361 100644
--- a/drivers/net/dsa/motorcomm/Kconfig
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -6,3 +6,12 @@ config NET_DSA_YT921X
help
This enables support for the Motorcomm YT9215 ethernet switch
chip.
+
+config NET_DSA_YT921X_LEDS
+ bool "LED support for Motorcomm YT9215"
+ default y
+ depends on NET_DSA_YT921X
+ depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_YT921X
+ help
+ This enables support for controlling the LEDs attached to the
+ Motorcomm YT9215 switch chips.
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
index 6cea5313a444..5a63db0029ff 100644
--- a/drivers/net/dsa/motorcomm/Makefile
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: ISC
obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
yt921x-objs := chip.o
+yt921x-$(CONFIG_NET_DSA_YT921X_LEDS) += leds.o
yt921x-objs += smi.o
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 6dee25b6754a..a3512136383b 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -26,6 +26,7 @@
#include <net/pkt_cls.h>
#include "chip.h"
+#include "leds.h"
#include "smi.h"
struct yt921x_mib_desc {
@@ -151,8 +152,6 @@ static const struct yt921x_info yt921x_infos[] = {
{}
};
-#define YT921X_NAME "yt921x"
-
#define YT921X_VID_UNWARE 4095
/* The interval should be small enough to avoid overflow of 32bit MIBs.
@@ -4581,6 +4580,12 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
if (res)
return res;
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ res = yt921x_leds_setup(priv);
+ if (res)
+ dev_warn(dev, "Failed to setup LEDs: %d\n", res);
+#endif
+
return 0;
}
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index 555046526669..6570ae5902ad 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -850,8 +850,13 @@ enum yt921x_fdb_entry_status {
#define YT921X_ACL_NUM (YT921X_ACL_BLK_NUM * YT921X_ACL_ENT_PER_BLK)
#define YT921X_UDF_NUM 8
+#define YT921X_LED_GROUP_NUM 3
+
/* 8 internal + 2 external + 1 mcu */
-#define YT921X_PORT_NUM 11
+#define YT921X_PORT_NUM 11
+#define YT921X_PORT_MCU 10
+
+#define YT921X_NAME "yt921x"
#define yt921x_port_is_internal(port) ((port) < 8)
#define yt921x_port_is_external(port) (8 <= (port) && (port) < 9)
@@ -938,6 +943,13 @@ struct yt921x_port {
struct yt921x_mib mib;
u64 rx_frames;
u64 tx_frames;
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ unsigned char led_duty;
+ unsigned short led_cycle;
+
+ struct yt921x_led *leds[YT921X_LED_GROUP_NUM];
+#endif
};
struct yt921x_reg_ops {
diff --git a/drivers/net/dsa/motorcomm/leds.c b/drivers/net/dsa/motorcomm/leds.c
new file mode 100644
index 000000000000..b066a7e84fe6
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.c
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/uleds.h>
+
+#include "chip.h"
+#include "leds.h"
+#include "smi.h"
+
+#define to_yt921x_led(led_cdev) \
+ container_of_const((led_cdev), struct yt921x_led, cdev)
+#define to_yt921x_port(led) ((led)->port)
+#define to_yt921x_priv(pp) \
+ container_of_const((pp), struct yt921x_priv, ports[(pp)->index])
+#define to_device(priv) ((priv)->ds.dev)
+
+static u32 yt921x_led_regaddr(struct yt921x_priv *priv, int port, int group)
+{
+ switch (group) {
+ case 0:
+ default:
+ return YT921X_LED0_PORTn(port);
+ case 1:
+ return YT921X_LED1_PORTn(port);
+ case 2:
+ return YT921X_LED2_PORTn(port);
+ }
+}
+
+static int
+yt921x_led_force_get(struct yt921x_priv *priv, int port, int group, bool *onp)
+{
+ u32 val;
+ int res;
+
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ *onp = (val & YT921X_LED2_PORT_FORCEn_M(group)) ==
+ YT921X_LED2_PORT_FORCEn_ON(group);
+ return 0;
+}
+
+static int
+yt921x_led_force_set(struct yt921x_priv *priv, int port, int group, bool on)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ u32 ctrl;
+ u32 mask;
+
+ led->use_cycle = false;
+ led->use_duty = false;
+
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = on ? YT921X_LED2_PORT_FORCEn_ON(group) :
+ YT921X_LED2_PORT_FORCEn_OFF(group);
+ return yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), mask,
+ ctrl);
+}
+
+static int
+yt921x_led_blink_select(const struct yt921x_priv *priv, unsigned long on,
+ unsigned long off, unsigned short *cyclep,
+ unsigned char *dutyp)
+{
+ static const unsigned char dutys[] = {
+ YT921X_LED_DUTY(1, 6),
+ YT921X_LED_DUTY(1, 4),
+ YT921X_LED_DUTY(1, 3),
+ YT921X_LED_DUTY(1, 2),
+ };
+ unsigned int cycle_upper;
+ unsigned int cycle_req;
+ unsigned int duty_req;
+ unsigned int cycle;
+ unsigned int duty;
+
+ cycle = YT921X_LED_BLINK_MAX;
+ cycle_upper = cycle * 11585 / 8192 + 1; /* M_SQRT2 * cycle */
+ if (check_add_overflow(on, off, &cycle_req) || cycle_req >= cycle_upper)
+ return -EOPNOTSUPP;
+
+ for (; cycle > YT921X_LED_BLINK_MIN; cycle_upper >>= 1, cycle >>= 1)
+ if (cycle_req >= cycle_upper >> 1)
+ break;
+ *cyclep = cycle;
+
+ duty_req = DIV_ROUND_CLOSEST(YT921X_LED_DUTY_DENOM *
+ (on > off ? off : on), cycle_req);
+ for (unsigned int i = ARRAY_SIZE(dutys) - 1;; i--)
+ if (i == 0 || duty_req >= (dutys[i - 1] + dutys[i]) / 2) {
+ duty = dutys[i];
+ break;
+ }
+ if (on > off)
+ duty = YT921X_LED_DUTY_DENOM - duty;
+ *dutyp = duty;
+
+ return 0;
+}
+
+static int
+yt921x_led_blink_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long *onp, unsigned long *offp)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ unsigned short cycle;
+ unsigned char duty;
+ bool use_cycle;
+ u32 ctrl;
+ u32 mask;
+ u32 val;
+ int res;
+
+ if (!*onp && !*offp) {
+ cycle = YT921X_LED_BLINK_DEF;
+ duty = YT921X_LED_DUTY(1, 2);
+ for (unsigned int i = 0; i < YT921X_LED_GROUP_NUM; i++)
+ if (i != group && pp->leds[i] &&
+ pp->leds[i]->use_duty) {
+ duty = pp->led_duty;
+ break;
+ }
+
+ use_cycle = false;
+ } else {
+ bool change_cycle;
+ bool change_duty;
+
+ res = yt921x_led_blink_select(priv, *onp, *offp, &cycle, &duty);
+ if (res)
+ return res;
+
+ use_cycle = cycle < YT921X_LED_BLINK_DEF;
+ change_cycle = use_cycle && cycle != pp->led_cycle;
+ change_duty = duty != pp->led_duty;
+ if (change_cycle || change_duty)
+ for (unsigned int i = 0; i < YT921X_LED_GROUP_NUM;
+ i++) {
+ if (i == group || !pp->leds[i])
+ continue;
+ if ((change_cycle && pp->leds[i]->use_cycle) ||
+ (change_duty && pp->leds[i]->use_duty))
+ return -EOPNOTSUPP;
+ }
+ }
+
+ /* The chip seems to jam a while if changing duty directly */
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ ctrl = val & ~YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ if (val != ctrl) {
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+ }
+
+ mask = YT921X_LED1_PORT_BLINK_DUTY_M | YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ switch (duty >= YT921X_LED_DUTY(1, 2) ? duty :
+ YT921X_LED_DUTY_DENOM - duty) {
+ default:
+ duty = YT921X_LED_DUTY(1, 2);
+ fallthrough;
+ case YT921X_LED_DUTY(1, 2):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_1_2;
+ break;
+ case YT921X_LED_DUTY(2, 3):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_2_3;
+ break;
+ case YT921X_LED_DUTY(3, 4):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_3_4;
+ break;
+ case YT921X_LED_DUTY(5, 6):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_5_6;
+ break;
+ }
+ if (duty < YT921X_LED_DUTY(1, 2))
+ ctrl |= YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ if (use_cycle) {
+ mask |= YT921X_LED1_PORT_OTHER_BLINK_M;
+ ctrl |= YT921X_LED1_PORT_OTHER_BLINK(9 - __fls(cycle));
+ }
+ res = yt921x_reg_update_bits(priv, YT921X_LED1_PORTn(port), mask, ctrl);
+ if (res)
+ return res;
+
+ ctrl = val & ~(YT921X_LED2_PORT_FORCEn_M(group) |
+ YT921X_LED2_PORT_FORCE_BLINKn_M(group));
+ ctrl |= YT921X_LED2_PORT_FORCEn_BLINK(group);
+ if (use_cycle)
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn_OTHER(group);
+ else
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn(group, __fls(cycle) - 9);
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+
+ led->use_cycle = use_cycle;
+ if (use_cycle)
+ pp->led_cycle = cycle;
+ led->use_duty = true;
+ pp->led_duty = duty;
+
+ *onp = DIV_ROUND_CLOSEST(duty * cycle, YT921X_LED_DUTY_DENOM);
+ *offp = cycle - *onp;
+ return 0;
+}
+
+static const u32 yt921x_led_trigger_maps[__TRIGGER_NETDEV_MAX] = {
+ [TRIGGER_NETDEV_LINK] = YT921X_LEDx_PORT_ACT_DUPLEX_HALF |
+ YT921X_LEDx_PORT_ACT_DUPLEX_FULL,
+ [TRIGGER_NETDEV_LINK_10] = YT921X_LEDx_PORT_ACT_10M,
+ [TRIGGER_NETDEV_LINK_100] = YT921X_LEDx_PORT_ACT_100M,
+ [TRIGGER_NETDEV_LINK_1000] = YT921X_LEDx_PORT_ACT_1000M,
+ [TRIGGER_NETDEV_HALF_DUPLEX] = YT921X_LEDx_PORT_ACT_DUPLEX_HALF,
+ [TRIGGER_NETDEV_FULL_DUPLEX] = YT921X_LEDx_PORT_ACT_DUPLEX_FULL,
+ [TRIGGER_NETDEV_TX] = YT921X_LEDx_PORT_ACT_TX,
+ [TRIGGER_NETDEV_RX] = YT921X_LEDx_PORT_ACT_RX,
+};
+
+static bool
+yt921x_led_trigger_is_supported(const struct yt921x_priv *priv, int port,
+ int group, unsigned long flags)
+{
+ unsigned int i;
+
+ for_each_set_bit(i, &flags, __TRIGGER_NETDEV_MAX)
+ if (!yt921x_led_trigger_maps[i])
+ return false;
+
+ return true;
+}
+
+static int
+yt921x_led_trigger_get(struct yt921x_priv *priv, int port, int group,
+ unsigned long *flagsp)
+{
+ u32 addr;
+ u32 val;
+ int res;
+
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_read(priv, addr, &val);
+ if (res)
+ return res;
+
+ *flagsp = 0;
+ for (unsigned int i = 0; i < __TRIGGER_NETDEV_MAX; i++) {
+ u32 mask = yt921x_led_trigger_maps[i];
+
+ if (mask && (val & mask) == mask)
+ *flagsp |= BIT(i);
+ }
+
+ return 0;
+}
+
+static int
+yt921x_led_trigger_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long flags)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ unsigned int i;
+ u32 addr;
+ u32 ctrl;
+ u32 mask;
+ int res;
+
+ ctrl = 0;
+ for_each_set_bit(i, &flags, __TRIGGER_NETDEV_MAX) {
+ if (!yt921x_led_trigger_maps[i])
+ return -EOPNOTSUPP;
+
+ ctrl |= yt921x_led_trigger_maps[i];
+ }
+
+ led->use_cycle = false;
+ led->use_duty = false;
+
+ mask = !group ? YT921X_LED0_PORT_ACT_M : YT921X_LEDx_PORT_ACT_M;
+ if (group == 2) {
+ mask |= YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ }
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_update_bits(priv, addr, mask, ctrl);
+ if (res)
+ return res;
+
+ if (group != 2) {
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ res = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port),
+ mask, ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_cled_brightness_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_force_set(priv, pp->index, led->group, brightness);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_cled_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_blink_set(priv, pp->index, led->group, delay_on,
+ delay_off);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static struct device * __maybe_unused
+yt921x_cled_hw_control_get_device(struct led_classdev *led_cdev)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ struct dsa_port *dp;
+
+ dp = dsa_to_port(&priv->ds, pp->index);
+ if (!dp || !dp->user)
+ return NULL;
+ return &dp->user->dev;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_is_supported(struct led_classdev *led_cdev,
+ unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+
+ if (yt921x_led_trigger_is_supported(priv, pp->index, led->group, flags))
+ return 0;
+ return -EOPNOTSUPP;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_get(struct led_classdev *led_cdev, unsigned long *flagsp)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_get(priv, pp->index, led->group, flagsp);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_set(struct led_classdev *led_cdev, unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_set(priv, pp->index, led->group, flags);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_led_setup(struct yt921x_priv *priv, int port,
+ struct fwnode_handle *fwnode, int *polarityp)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct device *dev = to_device(priv);
+ struct led_init_data init_data;
+ struct led_classdev *led_cdev;
+ char name[LED_MAX_NAME_SIZE];
+ enum led_default_state state;
+ struct yt921x_led *led;
+ bool force_high;
+ bool force_low;
+ int polarity;
+ u32 group;
+ bool on;
+ int res;
+
+ if (port == YT921X_PORT_MCU) {
+ dev_err(dev, "No LEDs for port %d\n", port);
+ return -ENODEV;
+ }
+
+ res = fwnode_property_read_u32(fwnode, "reg", &group);
+ if (res)
+ return res;
+ if (group >= YT921X_LED_GROUP_NUM) {
+ dev_err(dev, "Invalid LED reg %u for port %d\n", group, port);
+ return -EINVAL;
+ }
+
+ force_high = fwnode_property_read_bool(fwnode, "active-high");
+ force_low = fwnode_property_read_bool(fwnode, "active-low");
+ if (force_high && force_low) {
+ dev_err(dev, "Duplicate polarities for LED %02d:%02u\n",
+ group, port);
+ return -EINVAL;
+ }
+ polarity = force_high ? 1 : force_low ? -1 : 0;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+ pp->leds[group] = led;
+
+ led->port = pp;
+ led->group = group;
+
+ state = led_init_default_state_get(fwnode);
+ mutex_lock(&priv->reg_lock);
+ switch (state) {
+ case LEDS_DEFSTATE_OFF:
+ case LEDS_DEFSTATE_ON:
+ on = state != LEDS_DEFSTATE_OFF;
+ res = yt921x_led_force_set(priv, port, group, on);
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ res = yt921x_led_force_get(priv, port, group, &on);
+ break;
+ }
+ mutex_unlock(&priv->reg_lock);
+ if (res)
+ goto err;
+
+ led_cdev = &led->cdev;
+ led_cdev->brightness = on;
+ led_cdev->max_brightness = 1;
+ led_cdev->flags = LED_RETAIN_AT_SHUTDOWN;
+ led_cdev->brightness_set_blocking = yt921x_cled_brightness_set_blocking;
+ led_cdev->blink_set = yt921x_cled_blink_set;
+#ifdef CONFIG_LEDS_TRIGGERS
+ led_cdev->hw_control_trigger = "netdev";
+ led_cdev->hw_control_get_device = yt921x_cled_hw_control_get_device;
+ led_cdev->hw_control_is_supported = yt921x_cled_hw_control_is_supported;
+ led_cdev->hw_control_get = yt921x_cled_hw_control_get;
+ led_cdev->hw_control_set = yt921x_cled_hw_control_set;
+#endif
+
+ snprintf(name, sizeof(name), YT921X_NAME "-%u:%02d:%02u",
+ priv->ds.index, port, group);
+ init_data = (typeof(init_data)){
+ .fwnode = fwnode,
+ .devicename = name,
+ .devname_mandatory = true,
+ };
+ res = devm_led_classdev_register_ext(dev, led_cdev, &init_data);
+ if (res)
+ goto err;
+
+ *polarityp = polarity;
+ return 0;
+
+err:
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+ return res;
+}
+
+static void yt921x_leds_teardown(struct yt921x_priv *priv)
+{
+ struct device *dev = to_device(priv);
+ u32 mask;
+ int res;
+
+ for (int port = 0; port < YT921X_PORT_NUM; port++) {
+ struct yt921x_port *pp = &priv->ports[port];
+
+ for (int group = 0; group < YT921X_LED_GROUP_NUM; group++) {
+ struct yt921x_led *led = pp->leds[group];
+
+ if (!led)
+ continue;
+
+ devm_led_classdev_unregister(dev, &led->cdev);
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+ }
+ }
+
+ mutex_lock(&priv->reg_lock);
+
+ res = yt921x_reg_write(priv, YT921X_LED_PAR_PORTS, 0);
+ if (res)
+ goto end;
+
+ mask = YT921X_LED_CTRL_PORT_NUM_M | YT921X_LED_CTRL_EN;
+ res = yt921x_reg_clear_bits(priv, YT921X_LED_CTRL, mask);
+
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to teardown LEDs: %d\n", res);
+}
+
+int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ struct dsa_switch *ds = &priv->ds;
+ struct dsa_port *dp;
+ u32 inv_ctrl = 0;
+ u32 inv_mask = 0;
+ u32 ctrl;
+ u32 mask;
+ int res;
+
+ dsa_switch_for_each_port(dp, ds) {
+ struct device_node *leds_np;
+ int port = dp->index;
+
+ if (!dp->dn)
+ continue;
+
+ leds_np = of_get_child_by_name(dp->dn, "leds");
+ if (!leds_np)
+ continue;
+
+ for_each_child_of_node_scoped(leds_np, led_np) {
+ int polarity;
+
+ res = yt921x_led_setup(priv, port,
+ of_fwnode_handle(led_np),
+ &polarity);
+ if (res) {
+ of_node_put(leds_np);
+ goto err;
+ }
+
+ if (polarity) {
+ inv_mask |= BIT(port);
+ if (polarity > 0)
+ inv_ctrl |= BIT(port);
+ }
+ }
+
+ of_node_put(leds_np);
+ }
+
+ mutex_lock(&priv->reg_lock);
+
+ mask = YT921X_LED_CTRL_MODE_M | YT921X_LED_CTRL_PORT_NUM_M |
+ YT921X_LED_CTRL_EN;
+ ctrl = YT921X_LED_CTRL_MODE_PARALLEL |
+ YT921X_LED_CTRL_PORT_NUM(YT921X_PORT_NUM - 1) |
+ YT921X_LED_CTRL_EN;
+ res = yt921x_reg_update_bits(priv, YT921X_LED_CTRL, mask, ctrl);
+ if (res)
+ goto end;
+
+ /* Inversion is internal - force on will give low logic.
+ * In the rest of the file, treat LEDs as if active-low.
+ */
+ if (inv_mask)
+ res = yt921x_reg_update_bits(priv, YT921X_LED_PAR_INV, inv_mask,
+ inv_ctrl);
+
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ goto err;
+
+ return 0;
+
+err:
+ yt921x_leds_teardown(priv);
+ return res;
+}
diff --git a/drivers/net/dsa/motorcomm/leds.h b/drivers/net/dsa/motorcomm/leds.h
new file mode 100644
index 000000000000..bbd207e09a8d
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_LEDS_H
+#define _YT_LEDS_H
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/kconfig.h>
+#include <linux/leds.h>
+
+#define YT921X_LED_CTRL 0xd0000
+#define YT921X_LED_CTRL_EN BIT(21)
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK_M GENMASK(20, 19) /* cycle = 512 * x ms */
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK(x) FIELD_PREP(YT921X_LED_CTRL_LOOPDETECT_BLINK_M, (x))
+#define YT921X_LED_CTRL_PORT_NUM_M GENMASK(16, 13)
+#define YT921X_LED_CTRL_PORT_NUM(x) FIELD_PREP(YT921X_LED_CTRL_PORT_NUM_M, (x))
+#define YT921X_LED_CTRL_MODE_M GENMASK(1, 0)
+#define YT921X_LED_CTRL_MODE(x) FIELD_PREP(YT921X_LED_CTRL_MODE_M, (x))
+#define YT921X_LED_CTRL_MODE_PARALLEL YT921X_LED_CTRL_MODE(0)
+#define YT921X_LED_CTRL_MODE_SERIAL YT921X_LED_CTRL_MODE(2)
+#define YT921X_LED0_PORTn(port) (0xd0004 + 4 * (port))
+#define YT921X_LED0_PORT_ACT_M GENMASK(17, 0)
+#define YT921X_LED0_PORT_ACT_LINK_TRY_DIS BIT(17)
+#define YT921X_LED0_PORT_ACT_COLLISION_BLINK_INDI BIT(16)
+#define YT921X_LED1_PORTn(port) (0xd0040 + 4 * (port))
+#define YT921X_LED1_PORT_OTHER_BLINK_M GENMASK(31, 30) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_OTHER_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_OTHER_BLINK_M, (x))
+#define YT921X_LED1_PORT_EEE_BLINK_M GENMASK(29, 28) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_EEE_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_EEE_BLINK_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_COMP BIT(27)
+#define YT921X_LED1_PORT_BLINK_DUTY_M GENMASK(26, 25)
+#define YT921X_LED1_PORT_BLINK_DUTY(x) FIELD_PREP(YT921X_LED1_PORT_BLINK_DUTY_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_1_2 YT921X_LED1_PORT_BLINK_DUTY(0)
+#define YT921X_LED1_PORT_BLINK_DUTY_2_3 YT921X_LED1_PORT_BLINK_DUTY(1)
+#define YT921X_LED1_PORT_BLINK_DUTY_3_4 YT921X_LED1_PORT_BLINK_DUTY(2)
+#define YT921X_LED1_PORT_BLINK_DUTY_5_6 YT921X_LED1_PORT_BLINK_DUTY(3)
+#define YT921X_LED2_PORTn(port) (0xd0080 + 4 * (port))
+#define YT921X_LED2_PORT_FORCEn_M(grp) GENMASK(4 * (grp) + 19, 4 * (grp) + 18)
+#define YT921X_LED2_PORT_FORCEn(grp, x) ((x) << (4 * (grp) + 18))
+#define YT921X_LED2_PORT_FORCEn_DONTCARE(grp) YT921X_LED2_PORT_FORCEn(grp, 0)
+#define YT921X_LED2_PORT_FORCEn_BLINK(grp) YT921X_LED2_PORT_FORCEn(grp, 1)
+#define YT921X_LED2_PORT_FORCEn_ON(grp) YT921X_LED2_PORT_FORCEn(grp, 2)
+#define YT921X_LED2_PORT_FORCEn_OFF(grp) YT921X_LED2_PORT_FORCEn(grp, 3)
+#define YT921X_LED2_PORT_FORCE_BLINKn_M(grp) GENMASK(4 * (grp) + 17, 4 * (grp) + 16) /* cycle = 512 << x ms */
+#define YT921X_LED2_PORT_FORCE_BLINKn(grp, x) ((x) << (4 * (grp) + 16))
+#define YT921X_LED2_PORT_FORCE_BLINKn_OTHER(grp) YT921X_LED2_PORT_FORCE_BLINKn(grp, 3)
+#define YT921X_LEDx_PORT_ACT_M GENMASK(15, 0)
+#define YT921X_LEDx_PORT_ACT_EEE_BLINK BIT(15)
+#define YT921X_LEDx_PORT_ACT_LOOPDETECT_BLINK BIT(14)
+#define YT921X_LEDx_PORT_ACT_ACTIVE_BLINK BIT(13)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_FULL BIT(12)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_HALF BIT(11)
+#define YT921X_LEDx_PORT_ACT_TX_BLINK BIT(10)
+#define YT921X_LEDx_PORT_ACT_RX_BLINK BIT(9)
+#define YT921X_LEDx_PORT_ACT_TX BIT(8)
+#define YT921X_LEDx_PORT_ACT_RX BIT(7)
+#define YT921X_LEDx_PORT_ACT_1000M BIT(6)
+#define YT921X_LEDx_PORT_ACT_100M BIT(5)
+#define YT921X_LEDx_PORT_ACT_10M BIT(4)
+#define YT921X_LEDx_PORT_ACT_COLLISION_BLINK BIT(3)
+#define YT921X_LEDx_PORT_ACT_1000M_BLINK BIT(2)
+#define YT921X_LEDx_PORT_ACT_100M_BLINK BIT(1)
+#define YT921X_LEDx_PORT_ACT_10M_BLINK BIT(0)
+#define YT921X_LED_SER_CTRL 0xd0100
+#define YT921X_LED_SER_CTRL_UNK GENMASK(25, 24) /* delay? */
+#define YT921X_LED_SER_CTRL_ACTIVE_LOW BIT(4)
+#define YT921X_LED_SER_CTRL_GRP_NUM_M GENMASK(1, 0) /* #grp - 1 */
+#define YT921X_LED_SER_CTRL_GRP_NUM(x) FIELD_PREP(YT921X_LED_SER_CTRL_GRP_NUM_M, (x))
+#define YT921X_LED_SER_MAPnm(grp, port) (0xd0104 + 8 * (2 - (grp)) + 4 * ((port) / 5))
+#define YT921X_LED_SER_MAP_DSTn_PORT_M(port) GENMASK(6 * ((port) % 5) + 5, 6 * ((port) % 5) + 2)
+#define YT921X_LED_SER_MAP_DSTn_PORT(port, x) ((x) << (6 * ((port) % 5) + 2))
+#define YT921X_LED_SER_MAP_DSTn_LED_M(port) GENMASK(6 * ((port) % 5) + 1, 6 * ((port) % 5))
+#define YT921X_LED_SER_MAP_DSTn_LED(port, x) ((x) << (6 * ((port) % 5)))
+#define YT921X_LED_PAR_PORTS 0xd01c4
+#define YT921X_LED_PAR_INV 0xd01c8
+#define YT921X_LED_PAR_INV_INVnm(grp, port) BIT(10 * (grp) + (port))
+#define YT921X_LED_PAR_MAPn(port) (0xd01d0 + 4 * (port))
+#define YT921X_LED_PAR_MAP_DSTn_PORT_M(grp) GENMASK(6 * (grp) + 5, 6 * (grp) + 2)
+#define YT921X_LED_PAR_MAP_DSTn_PORT(grp, x) ((x) << (6 * (grp) + 2))
+#define YT921X_LED_PAR_MAP_DSTn_LED_M(grp) GENMASK(6 * (grp) + 1, 6 * (grp))
+#define YT921X_LED_PAR_MAP_DSTn_LED(grp, x) ((x) << (6 * (grp)))
+
+#define YT921X_LED_BLINK_MIN 64
+#define YT921X_LED_BLINK_DEF 512
+#define YT921X_LED_BLINK_MAX 2048
+
+/* 2 * lcm(2, 3, 4, 6) */
+#define YT921X_LED_DUTY_DENOM 24
+#define YT921X_LED_DUTY(nom, denom) (YT921X_LED_DUTY_DENOM * (nom) / (denom))
+
+struct yt921x_priv;
+
+struct yt921x_led {
+ struct led_classdev cdev;
+ struct yt921x_port *port;
+ unsigned char group;
+
+ bool use_cycle:1;
+ bool use_duty:1;
+};
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+
+int yt921x_leds_setup(struct yt921x_priv *priv);
+
+#else
+
+static inline int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ return 0;
+}
+
+#endif
+
+#endif
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net] selftests/tc-testing: Add tests that force multiq and taprio to enqueue to child's gso_skb
From: Victor Nogueira @ 2026-07-03 16:57 UTC (permalink / raw)
To: Paolo Abeni
Cc: netdev, hexlabsecurity, pctammela, davem, edumazet, kuba, jhs,
jiri
In-Reply-To: <e3f99de6-cf19-451f-8175-50fa30012f7f@redhat.com>
On Fri, Jul 3, 2026 at 1:37 PM Paolo Abeni <pabeni@redhat.com> wrote:
>
> On 6/30/26 5:36 PM, Victor Nogueira wrote:
> > Add test cases to reproduce scenarios fixed recently [1] where
> > multiqueue and taprio forced their children into enqueueing an skb to
> > gso_skb (during peek), but failed to dequeue from gso_skb because they
> > called the child's dequeue callback directly. This causes a desync in the
> > child's qlen/backlog and results in an eventual null-ptr-deref (with a
> > qfq or dualpi2 child).
> >
> > Test cases are the following:
> >
> > - Force multiq to dequeue from its child's gso_skb with qfq leaf (fb6c)
> > - Force multiq to dequeue from its child's gso_skb with dualpi2 leaf (1922)
> > - Force taprio to dequeue from its child's gso_skb with qfq leaf (476f)
> > - Force taprio to dequeue from its child's gso_skb with dualpi2 leaf (0235)
> >
> > [1] https://lore.kernel.org/netdev/20260625-b4-disp-31bcb279-v1-0-85c40b83c529@proton.me/
> >
> > Signed-off-by: Victor Nogueira <victor@mojatatu.com>
>
> Since the fix has been propagated to net-next, this is net-next
> material, right? no need to repost, just ack/nack, thanks!
Sorry, I didn't realise the fix had propagated.
Yes, this should go to net-next then.
cheers,
Victor
^ permalink raw reply
* [PATCH ipsec 1/8] xfrm: state: exact mark/mask match for SPI-keyed control-plane SA lookups
From: Antony Antony @ 2026-07-03 17:07 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
Add __xfrm_state_lookup_exact(), an identity-match like
__xfrm_state_lookup().
Wire it into every SPI-keyed control-plane path: DELSA/GETSA, UPDSA,
GETAE/NEWAE, EXPIRE, MIGRATE_STATE.
xfrm_state_add()'s duplicate-detect keeps the wildcard
__xfrm_state_locate() - unrelated, unchanged.
Fixes: 3d6acfa7641f ("xfrm: SA lookups with mark")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
include/net/xfrm.h | 3 ++
net/xfrm/xfrm_state.c | 80 ++++++++++++++++++++++++++++++++++++++++++++-------
net/xfrm/xfrm_user.c | 29 ++++++++++---------
3 files changed, 88 insertions(+), 24 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 519a0156a05c..f6ed590cb2ff 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1746,6 +1746,9 @@ struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,
const xfrm_address_t *saddr,
u8 proto,
unsigned short family);
+struct xfrm_state *xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr, __be32 spi,
+ u8 proto, unsigned short family);
#ifdef CONFIG_XFRM_SUB_POLICY
void xfrm_tmpl_sort(struct xfrm_tmpl **dst, struct xfrm_tmpl **src, int n,
unsigned short family);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index c58cd024e3c6..df761ce1c290 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1172,11 +1172,22 @@ static struct xfrm_state *__xfrm_state_lookup_all(const struct xfrm_hash_state_p
return NULL;
}
-static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,
- u32 mark,
- const xfrm_address_t *daddr,
- __be32 spi, u8 proto,
- unsigned short family)
+/* exact=false: data-plane wildcard match against x's mask. exact=true:
+ * control-plane identity match, mark and mask must both match exactly.
+ */
+static bool xfrm_state_mark_matches(const struct xfrm_state *x, u32 mark, u32 mask, bool exact)
+{
+ if (exact)
+ return x->mark.v == mark && x->mark.m == mask;
+ return (mark & x->mark.m) == x->mark.v;
+}
+
+static struct xfrm_state *
+__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,
+ u32 mark, u32 mask, bool exact,
+ const xfrm_address_t *daddr,
+ __be32 spi, u8 proto,
+ unsigned short family)
{
unsigned int h = __xfrm_spi_hash(daddr, spi, proto, family, state_ptrs->hmask);
struct xfrm_state *x;
@@ -1188,7 +1199,7 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs
!xfrm_addr_equal(&x->id.daddr, daddr, family))
continue;
- if ((mark & x->mark.m) != x->mark.v)
+ if (!xfrm_state_mark_matches(x, mark, mask, exact))
continue;
if (!xfrm_state_hold_rcu(x))
continue;
@@ -1198,6 +1209,17 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs
return NULL;
}
+static struct xfrm_state *
+__xfrm_state_lookup_exact(const struct xfrm_hash_state_ptrs *state_ptrs,
+ const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr,
+ __be32 spi, u8 proto,
+ unsigned short family)
+{
+ return __xfrm_state_lookup(state_ptrs, mark->v, mark->m, true,
+ daddr, spi, proto, family);
+}
+
struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
const xfrm_address_t *daddr,
__be32 spi, u8 proto,
@@ -1228,7 +1250,7 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
xfrm_hash_ptrs_get(net, &state_ptrs);
- x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family);
+ x = __xfrm_state_lookup(&state_ptrs, mark, 0, false, daddr, spi, proto, family);
if (x) {
spin_lock(&net->xfrm.xfrm_state_lock);
if (x->km.state != XFRM_STATE_VALID) {
@@ -1288,7 +1310,7 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
xfrm_hash_ptrs_get(net, &state_ptrs);
if (use_spi)
- return __xfrm_state_lookup(&state_ptrs, mark, &x->id.daddr,
+ return __xfrm_state_lookup(&state_ptrs, mark, 0, false, &x->id.daddr,
x->id.spi, x->id.proto, family);
else
return __xfrm_state_lookup_byaddr(&state_ptrs, mark,
@@ -1297,6 +1319,27 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
x->id.proto, family);
}
+/* Used by xfrm_state_update() only; xfrm_state_add()'s dup check keeps
+ * using the wildcard __xfrm_state_locate() above.
+ */
+static inline struct xfrm_state *
+__xfrm_state_locate_exact(struct xfrm_state *x, int use_spi, int family)
+{
+ struct xfrm_hash_state_ptrs state_ptrs;
+ struct net *net = xs_net(x);
+
+ xfrm_hash_ptrs_get(net, &state_ptrs);
+
+ if (use_spi)
+ return __xfrm_state_lookup_exact(&state_ptrs, &x->mark, &x->id.daddr,
+ x->id.spi, x->id.proto, family);
+ else
+ return __xfrm_state_lookup_byaddr(&state_ptrs, x->mark.v & x->mark.m,
+ &x->id.daddr,
+ &x->props.saddr,
+ x->id.proto, family);
+}
+
static void xfrm_hash_grow_check(struct net *net, int have_hash_collision)
{
if (have_hash_collision &&
@@ -2229,7 +2272,7 @@ int xfrm_state_update(struct xfrm_state *x)
to_put = NULL;
spin_lock_bh(&net->xfrm.xfrm_state_lock);
- x1 = __xfrm_state_locate(x, use_spi, x->props.family);
+ x1 = __xfrm_state_locate_exact(x, use_spi, x->props.family);
err = -ESRCH;
if (!x1)
@@ -2374,7 +2417,7 @@ xfrm_state_lookup(struct net *net, u32 mark, const xfrm_address_t *daddr, __be32
rcu_read_lock();
xfrm_hash_ptrs_get(net, &state_ptrs);
- x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family);
+ x = __xfrm_state_lookup(&state_ptrs, mark, 0, false, daddr, spi, proto, family);
rcu_read_unlock();
return x;
}
@@ -2398,6 +2441,23 @@ xfrm_state_lookup_byaddr(struct net *net, u32 mark,
}
EXPORT_SYMBOL(xfrm_state_lookup_byaddr);
+struct xfrm_state *
+xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr, __be32 spi,
+ u8 proto, unsigned short family)
+{
+ struct xfrm_hash_state_ptrs state_ptrs;
+ struct xfrm_state *x;
+
+ rcu_read_lock();
+ xfrm_hash_ptrs_get(net, &state_ptrs);
+
+ x = __xfrm_state_lookup_exact(&state_ptrs, mark, daddr, spi, proto, family);
+ rcu_read_unlock();
+ return x;
+}
+EXPORT_SYMBOL(xfrm_state_lookup_exact);
+
struct xfrm_state *
xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, u8 mode, u32 reqid,
u32 if_id, u32 pcpu_num, u8 proto, const xfrm_address_t *daddr,
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 6384795ee6b2..b56fca666b89 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1089,11 +1089,12 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
struct xfrm_state *x = NULL;
struct xfrm_mark m;
int err;
- u32 mark = xfrm_mark_get(attrs, &m);
+
+ xfrm_mark_get(attrs, &m);
if (xfrm_id_proto_match(p->proto, IPSEC_PROTO_ANY)) {
err = -ESRCH;
- x = xfrm_state_lookup(net, mark, &p->daddr, p->spi, p->proto, p->family);
+ x = xfrm_state_lookup_exact(net, &m, &p->daddr, p->spi, p->proto, p->family);
} else {
xfrm_address_t *saddr = NULL;
@@ -1104,7 +1105,7 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
}
err = -ESRCH;
- x = xfrm_state_lookup_byaddr(net, mark,
+ x = xfrm_state_lookup_byaddr(net, m.v & m.m,
&p->daddr, saddr,
p->proto, p->family);
}
@@ -2788,14 +2789,13 @@ static int xfrm_get_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
struct sk_buff *r_skb;
int err;
struct km_event c;
- u32 mark;
struct xfrm_mark m;
struct xfrm_aevent_id *p = nlmsg_data(nlh);
struct xfrm_usersa_id *id = &p->sa_id;
- mark = xfrm_mark_get(attrs, &m);
+ xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &id->daddr, id->spi, id->proto, id->family);
+ x = xfrm_state_lookup_exact(net, &m, &id->daddr, id->spi, id->proto, id->family);
if (x == NULL)
return -ESRCH;
@@ -2836,7 +2836,6 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
struct xfrm_state *x;
struct km_event c;
int err = -EINVAL;
- u32 mark = 0;
struct xfrm_mark m;
struct xfrm_aevent_id *p = nlmsg_data(nlh);
struct nlattr *rp = attrs[XFRMA_REPLAY_VAL];
@@ -2856,9 +2855,10 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
return err;
}
- mark = xfrm_mark_get(attrs, &m);
+ xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &p->sa_id.daddr, p->sa_id.spi, p->sa_id.proto, p->sa_id.family);
+ x = xfrm_state_lookup_exact(net, &m, &p->sa_id.daddr, p->sa_id.spi,
+ p->sa_id.proto, p->sa_id.family);
if (x == NULL)
return -ESRCH;
@@ -2992,9 +2992,10 @@ static int xfrm_add_sa_expire(struct sk_buff *skb, struct nlmsghdr *nlh,
struct xfrm_user_expire *ue = nlmsg_data(nlh);
struct xfrm_usersa_info *p = &ue->state;
struct xfrm_mark m;
- u32 mark = xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &p->id.daddr, p->id.spi, p->id.proto, p->family);
+ xfrm_mark_get(attrs, &m);
+
+ x = xfrm_state_lookup_exact(net, &m, &p->id.daddr, p->id.spi, p->id.proto, p->family);
err = -ENOENT;
if (x == NULL)
@@ -3361,9 +3362,9 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
copy_from_user_migrate_state(&m, um);
- x = xfrm_state_lookup(net, m.old_mark.v & m.old_mark.m,
- &um->id.daddr, um->id.spi,
- um->id.proto, um->id.family);
+ x = xfrm_state_lookup_exact(net, &m.old_mark,
+ &um->id.daddr, um->id.spi,
+ um->id.proto, um->id.family);
if (!x) {
NL_SET_ERR_MSG(extack, "Can not find state");
return -ESRCH;
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 2/8] xfrm: state: exact mark/mask match for by-address control-plane SA lookups
From: Antony Antony @ 2026-07-03 17:07 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
Add mark/mask/exact parameters directly to __xfrm_state_lookup_byaddr(),
reusing the SPI-keyed lookup's comparison.
Use __xfrm_state_locate_exact()'s by-address branch UPDSA
and xfrm_user_state_lookup()'s by-address branch
(DELSA/GETSA for non-SPI-keyed lookups).
Fixes: 3d6acfa7641f ("xfrm: SA lookups with mark")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
include/net/xfrm.h | 2 +-
net/ipv6/xfrm6_input.c | 2 +-
net/xfrm/xfrm_state.c | 21 +++++++++++----------
net/xfrm/xfrm_user.c | 2 +-
4 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index f6ed590cb2ff..ebe514376254 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1741,7 +1741,7 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
const xfrm_address_t *daddr,
__be32 spi, u8 proto,
unsigned short family);
-struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,
+struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,
const xfrm_address_t *daddr,
const xfrm_address_t *saddr,
u8 proto,
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index 89d0443b5307..2dd347fece52 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -272,7 +272,7 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
break;
}
- x = xfrm_state_lookup_byaddr(net, skb->mark, dst, src, proto, AF_INET6);
+ x = xfrm_state_lookup_byaddr(net, skb->mark, 0, false, dst, src, proto, AF_INET6);
if (!x)
continue;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index df761ce1c290..d78cfe481f75 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1274,11 +1274,12 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
}
EXPORT_SYMBOL(xfrm_input_state_lookup);
-static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_state_ptrs *state_ptrs,
- u32 mark,
- const xfrm_address_t *daddr,
- const xfrm_address_t *saddr,
- u8 proto, unsigned short family)
+static struct xfrm_state *
+__xfrm_state_lookup_byaddr(const struct xfrm_hash_state_ptrs *state_ptrs,
+ u32 mark, u32 mask, bool exact,
+ const xfrm_address_t *daddr,
+ const xfrm_address_t *saddr,
+ u8 proto, unsigned short family)
{
unsigned int h = __xfrm_src_hash(daddr, saddr, family, state_ptrs->hmask);
struct xfrm_state *x;
@@ -1290,7 +1291,7 @@ static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_stat
!xfrm_addr_equal(&x->props.saddr, saddr, family))
continue;
- if ((mark & x->mark.m) != x->mark.v)
+ if (!xfrm_state_mark_matches(x, mark, mask, exact))
continue;
if (!xfrm_state_hold_rcu(x))
continue;
@@ -1313,7 +1314,7 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
return __xfrm_state_lookup(&state_ptrs, mark, 0, false, &x->id.daddr,
x->id.spi, x->id.proto, family);
else
- return __xfrm_state_lookup_byaddr(&state_ptrs, mark,
+ return __xfrm_state_lookup_byaddr(&state_ptrs, mark, 0, false,
&x->id.daddr,
&x->props.saddr,
x->id.proto, family);
@@ -1334,7 +1335,7 @@ __xfrm_state_locate_exact(struct xfrm_state *x, int use_spi, int family)
return __xfrm_state_lookup_exact(&state_ptrs, &x->mark, &x->id.daddr,
x->id.spi, x->id.proto, family);
else
- return __xfrm_state_lookup_byaddr(&state_ptrs, x->mark.v & x->mark.m,
+ return __xfrm_state_lookup_byaddr(&state_ptrs, x->mark.v, x->mark.m, true,
&x->id.daddr,
&x->props.saddr,
x->id.proto, family);
@@ -2424,7 +2425,7 @@ xfrm_state_lookup(struct net *net, u32 mark, const xfrm_address_t *daddr, __be32
EXPORT_SYMBOL(xfrm_state_lookup);
struct xfrm_state *
-xfrm_state_lookup_byaddr(struct net *net, u32 mark,
+xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,
const xfrm_address_t *daddr, const xfrm_address_t *saddr,
u8 proto, unsigned short family)
{
@@ -2435,7 +2436,7 @@ xfrm_state_lookup_byaddr(struct net *net, u32 mark,
xfrm_hash_ptrs_get(net, &state_ptrs);
- x = __xfrm_state_lookup_byaddr(&state_ptrs, mark, daddr, saddr, proto, family);
+ x = __xfrm_state_lookup_byaddr(&state_ptrs, mark, mask, exact, daddr, saddr, proto, family);
rcu_read_unlock();
return x;
}
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index b56fca666b89..11ec3b14a42f 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1105,7 +1105,7 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
}
err = -ESRCH;
- x = xfrm_state_lookup_byaddr(net, m.v & m.m,
+ x = xfrm_state_lookup_byaddr(net, m.v, m.m, true,
&p->daddr, saddr,
p->proto, p->family);
}
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 3/8] selftests: net: xfrm_state: add mark shadowing tests for state lookups
From: Antony Antony @ 2026-07-03 17:07 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
Add SPI and by-address wildcard-mark shadowing tests.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
tools/testing/selftests/net/xfrm_state.sh | 130 +++++++++++++++++++++++++++++-
1 file changed, 129 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/xfrm_state.sh b/tools/testing/selftests/net/xfrm_state.sh
index f6c54a6496d7..f202073726a9 100755
--- a/tools/testing/selftests/net/xfrm_state.sh
+++ b/tools/testing/selftests/net/xfrm_state.sh
@@ -42,7 +42,11 @@ tests="
mtu_ipv4_r2 IPv4 MTU exceeded from ESP router r2
mtu_ipv6_r2 IPv6 MTU exceeded from ESP router r2
mtu_ipv4_r3 IPv4 MTU exceeded from router r3
- mtu_ipv6_r3 IPv6 MTU exceeded from router r3"
+ mtu_ipv6_r3 IPv6 MTU exceeded from router r3
+ mark_wildcard_shadow mark: wildcard SA in by-spi state get lookup
+ mark_wildcard_delete mark: wildcard SA in by-spi state delete
+ mark_wildcard_get_addr mark: wildcard SA in by-address get lookup
+ mark_wildcard_delete_addr mark: wildcard SA in by-address delete"
prefix4="10.1"
prefix6="fc00"
@@ -101,6 +105,10 @@ run_test() {
mtu_ipv6_r2) test_mtu_ipv6_r2 ;;
mtu_ipv4_r3) test_mtu_ipv4_r3 ;;
mtu_ipv6_r3) test_mtu_ipv6_r3 ;;
+ mark_wildcard_shadow) test_mark_wildcard_shadow ;;
+ mark_wildcard_delete) test_mark_wildcard_delete ;;
+ mark_wildcard_get_addr) test_mark_wildcard_get_addr ;;
+ mark_wildcard_delete_addr) test_mark_wildcard_delete_addr ;;
esac
ret=$?
@@ -167,6 +175,8 @@ setup_namespaces() {
[ -n "${NS_S2}" ] && ns_s2=(ip netns exec "${NS_S2}") && ns_active="${ns_active} $NS_S2"
[ -n "${NS_R3}" ] && ns_r3=(ip netns exec "${NS_R3}") && ns_active="${ns_active} $NS_R3"
[ -n "${NS_B}" ] && ns_active="${ns_active} $NS_B"
+
+ return 0
}
addr_add() {
@@ -295,6 +305,18 @@ setup_ns_set_v6x() {
set_xfrm_params
}
+setup_ns_set_simple() {
+ # Single namespace, no veths/routes.
+ ns_set="a"
+ imax=1
+ src="10.1.1.1"
+ dst="10.1.1.2"
+ src_net="10.1.0.0/24"
+ dst_net="10.2.0.0/24"
+
+ set_xfrm_params
+}
+
setup_network() {
# Create veths and add addresses
local -a ns_cmd
@@ -403,6 +425,7 @@ setup() {
ns_set_v4x) setup_ns_set_v4x ;;
ns_set_v6) setup_ns_set_v6 ;;
ns_set_v6x) setup_ns_set_v6x ;;
+ ns_set_simple) setup_ns_set_simple ;;
namespaces) setup_namespaces ;;
network) setup_network ;;
xfrm) setup_xfrm ;;
@@ -548,6 +571,111 @@ test_mtu_ipv6_r3() {
return "${rc}"
}
+# SA_decoy (mark 0/0, added second) shadows SA_target (mark 1/1) on a
+# wildcard mark lookup. No traffic sent; these only exercise the SAD.
+
+test_mark_wildcard_shadow() {
+ setup ns_set_simple namespaces || return "$ksft_skip"
+ local result=0
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src}" dst "${dst}" proto esp spi 0x1000 \
+ reqid 100 mode tunnel \
+ aead 'rfc4106(gcm(aes))' 0x1111111111111111111111111111111111111111 96 \
+ mark 1 mask 1
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src}" dst "${dst}" proto esp spi 0x1000 \
+ reqid 100 mode tunnel \
+ aead 'rfc4106(gcm(aes))' 0x2222222222222222222222222222222222222222 96 \
+ mark 0 mask 0
+
+ run_cmd_err "${ns_a[@]}" ip xfrm state get \
+ dst "${dst}" proto esp spi 0x1000 \
+ mark 1 mask 1
+
+ # Expected: SA_target (mark 0x1/0x1). Actual (bug): SA_decoy (mark 0/0).
+ echo "$out" | grep -q "mark 0x1/0x1" || result=1
+
+ return "${result}"
+}
+
+test_mark_wildcard_delete() {
+ setup ns_set_simple namespaces || return "$ksft_skip"
+ local result=0
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src}" dst "${dst}" proto esp spi 0x1000 \
+ reqid 100 mode tunnel \
+ aead 'rfc4106(gcm(aes))' 0x1111111111111111111111111111111111111111 96 \
+ mark 1 mask 1
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src}" dst "${dst}" proto esp spi 0x1000 \
+ reqid 100 mode tunnel \
+ aead 'rfc4106(gcm(aes))' 0x2222222222222222222222222222222222222222 96 \
+ mark 0 mask 0
+
+ run_cmd "${ns_a[@]}" ip xfrm state delete \
+ dst "${dst}" proto esp spi 0x1000 \
+ mark 1 mask 1
+
+ run_cmd_err "${ns_a[@]}" ip xfrm state show
+ echo "$out" | grep -q "mark 0x1/0x1" && result=1
+
+ return "${result}"
+}
+
+# by-address counterpart: proto route2/hao (IPv6 mobility) have no SPI,
+# so xfrm_user_state_lookup() resolves them by address instead.
+
+test_mark_wildcard_get_addr() {
+ setup ns_set_simple namespaces || return "$ksft_skip"
+ local result=0
+ local src6="fc00:9::1"
+ local dst6="fc00:9::2"
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::3 \
+ mark 1 mask 1
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::4 \
+ mark 0 mask 0
+
+ run_cmd_err "${ns_a[@]}" ip xfrm state get \
+ src "${src6}" dst "${dst6}" proto route2 \
+ mark 1 mask 1
+
+ echo "$out" | grep -q "mark 0x1/0x1" || result=1
+
+ return "${result}"
+}
+
+test_mark_wildcard_delete_addr() {
+ setup ns_set_simple namespaces || return "$ksft_skip"
+ local result=0
+ local src6="fc00:9::1"
+ local dst6="fc00:9::2"
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::3 \
+ mark 1 mask 1
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::4 \
+ mark 0 mask 0
+
+ run_cmd "${ns_a[@]}" ip xfrm state delete \
+ src "${src6}" dst "${dst6}" proto route2 \
+ mark 1 mask 1
+
+ run_cmd_err "${ns_a[@]}" ip xfrm state show
+ echo "$out" | grep -q "mark 0x1/0x1" && result=1
+
+ return "${result}"
+}
+
################################################################################
#
usage() {
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 4/8] xfrm: fix use-after-free of migrated state in xfrm_do_migrate_state()
From: Antony Antony @ 2026-07-03 17:08 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc, Sashiko
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
A concurrent delete can free the migrated state before
xfrm_do_migrate_state() finishes using it.
Fixes: a9d155ea9b44 ("xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
net/xfrm/xfrm_user.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 11ec3b14a42f..29cbdc836cfc 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3468,6 +3468,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
__xfrm_state_delete(x);
spin_unlock_bh(&x->lock);
+ xfrm_state_hold(xc);
err = xfrm_state_migrate_install(x, xc, &m, extack);
if (err < 0) {
/*
@@ -3475,6 +3476,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
* free under xfrm_cfg_mutex. Both SAs are gone if it does;
* restoring x would risk SN/IV reuse.
*/
+ xfrm_state_put(xc);
goto out;
}
@@ -3493,6 +3495,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
err = 0;
}
+ xfrm_state_put(xc);
out:
xfrm_state_put(x);
return err;
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 5/8] xfrm: fix hw offload state leak on xfrm_do_migrate_state() error path
From: Antony Antony @ 2026-07-03 17:08 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc, Sashiko
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
In the error path, the cloned state is dropped without removing its
hardware offload, leaking the offloaded SA entry.
Fixes: a9d155ea9b44 ("xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
net/xfrm/xfrm_user.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 29cbdc836cfc..87ef198993db 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3500,6 +3500,8 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
xfrm_state_put(x);
return err;
out_xc:
+ if (m.xuo)
+ xfrm_dev_state_delete(xc);
xc->km.state = XFRM_STATE_DEAD;
xfrm_state_put(xc);
xfrm_state_put(x);
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 6/8] xfrm: include mark in MIGRATE_STATE SA collision check
From: Antony Antony @ 2026-07-03 17:08 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc, Sashiko
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
The SA lookup tuple is (daddr, spi, proto, family, mark), but the
EEXIST pre-check and the xfrm_state_insert() vs xfrm_state_add()
decision only considered daddr and family, ignoring mark.
A migration that only changes the mark inserts a duplicate SA tuple
into the hash tables.
Before:
root@west:~# ip xfrm state add src 10.1.1.1 dst 10.1.1.2 proto esp \
spi 0x1000 reqid 100 mode tunnel aead "rfc4106(gcm(aes))" \
0x1111111111111111111111111111111111111111 96 \
mark 0x1 mask 0xff
root@west:~# ip xfrm state add src 10.1.1.1 dst 10.1.1.2 proto esp \
spi 0x1000 reqid 100 mode tunnel aead "rfc4106(gcm(aes))" \
0x1111111111111111111111111111111111111111 96 \
mark 0x2 mask 0xff
root@west:~# ip xfrm state migrate dst 10.1.1.2 proto esp spi 0x1000 \
mark 0x1 mask 0xff \
new-dst 10.1.1.2 new-src 10.1.1.1 new-reqid 100 \
new-mark 0x2 mask 0xff
ip x s
src 10.1.1.1 dst 10.1.1.2
proto esp spi 0x00001000 reqid 100 mode tunnel
replay-window 0
mark 0x2/0xff
aead rfc4106(gcm(aes)) 0x1111111111111111111111111111111111111111 96
anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
src 10.1.1.1 dst 10.1.1.2
proto esp spi 0x00001000 reqid 100 mode tunnel
replay-window 0
mark 0x2/0xff
aead rfc4106(gcm(aes)) 0x1111111111111111111111111111111111111111 96
anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
sel src 0.0.0.0/0 dst 0.0.0.0/0
Notice two states with same mark 0x2/0xff.
After:
Error: New SA tuple already occupied.
Fixes: a9d155ea9b44 ("xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
net/xfrm/xfrm_state.c | 8 +++++---
net/xfrm/xfrm_user.c | 15 ++++++++++-----
2 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index d78cfe481f75..bffe985e42ea 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2221,10 +2221,12 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
struct netlink_ext_ack *extack)
{
if (m->new_family == m->old_family &&
- xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
+ xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family) &&
+ xc->mark.v == x->mark.v && xc->mark.m == x->mark.m) {
/*
- * Care is needed when the destination address of the state is
- * to be updated as it is a part of triplet.
+ * Care is needed when the destination address or mark of the
+ * state is to be updated, as they are part of the lookup
+ * triplet.
*/
xfrm_state_insert(xc);
} else {
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 87ef198993db..a2317e6e6802 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3435,18 +3435,23 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
x->nat_keepalive_interval);
if (m.new_family != um->id.family ||
- !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family)) {
+ !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family) ||
+ (m.new_mark && (m.new_mark->v != x->mark.v ||
+ m.new_mark->m != x->mark.m))) {
u32 new_mark_key = m.new_mark ? m.new_mark->v & m.new_mark->m :
- m.old_mark.v & m.old_mark.m;
+ x->mark.v & x->mark.m;
struct xfrm_state *x_new;
x_new = xfrm_state_lookup(net, new_mark_key, &m.new_daddr,
um->id.spi, um->id.proto, m.new_family);
if (x_new) {
xfrm_state_put(x_new);
- NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
- err = -EEXIST;
- goto out;
+ if (x_new != x) {
+ NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
+ err = -EEXIST;
+ goto out;
+ }
+ /* self-match via wide mark mask; not a collision */
}
}
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 7/8] xfrm: pass extack through to xfrm_init_replay() from xfrm_init_state()
From: Antony Antony @ 2026-07-03 17:08 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
xfrm_init_state() takes an extack parameter but doesn't pass it to
xfrm_init_replay(), so replay-window errors can't be reported via
extack.
Fixes: 231a1744dc43 ("xfrm: add extack to xfrm_init_state")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
net/xfrm/xfrm_state.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index bffe985e42ea..0b3dc7789dd7 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -3369,7 +3369,7 @@ int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
if (err)
return err;
- err = xfrm_init_replay(x, NULL);
+ err = xfrm_init_replay(x, extack);
if (err)
return err;
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 8/8] docs: xfrm: include mark in XFRM_MSG_MIGRATE_STATE EEXIST tuple
From: Antony Antony @ 2026-07-03 17:09 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
Document mark as part of the EEXIST tuple and update the SA lookup
description to match.
Fixes: c13c0cc6f52e ("xfrm: add documentation for XFRM_MSG_MIGRATE_STATE")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
Documentation/networking/xfrm/xfrm_migrate_state.rst | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/Documentation/networking/xfrm/xfrm_migrate_state.rst b/Documentation/networking/xfrm/xfrm_migrate_state.rst
index 9d53cb22b007..905e3f0c4ee0 100644
--- a/Documentation/networking/xfrm/xfrm_migrate_state.rst
+++ b/Documentation/networking/xfrm/xfrm_migrate_state.rst
@@ -27,15 +27,18 @@ SA Identification
=================
The struct is defined in ``include/uapi/linux/xfrm.h``. The SA is looked
-up using ``xfrm_state_lookup()`` with ``id.spi``,
-``id.daddr``, ``id.proto``, ``id.family``, and
-``old_mark.v & old_mark.m`` as the mark key::
+up using ``xfrm_state_lookup_exact()`` with ``id.spi``, ``id.daddr``,
+``id.proto``, ``id.family``, and an exact match against ``old_mark.v``
+and ``old_mark.m``. Unlike the data path, which uses a masked
+comparison, this requires the SA's mark and mask to equal ``old_mark``
+exactly, so a broad-mask SA is never matched when a more specific one
+was intended. If no such SA exists, ``-ESRCH`` is returned.::
struct xfrm_user_migrate_state {
struct xfrm_usersa_id id; /* spi, daddr, proto, family */
xfrm_address_t new_daddr;
xfrm_address_t new_saddr;
- struct xfrm_mark old_mark; /* SA lookup: key = v & m */
+ struct xfrm_mark old_mark; /* SA lookup key (exact v/m match) */
struct xfrm_selector new_sel; /* new selector (see Flags) */
__u32 new_reqid;
__u32 flags; /* XFRM_MIGRATE_STATE_* */
@@ -72,8 +75,8 @@ inherits the value from the existing SA (omit-to-inherit).
- Description
* - ``XFRMA_MARK``
- Mark on the migrated SA (``struct xfrm_mark``). Absent inherits
- ``old_mark``. To use no mark on the new SA, send ``XFRMA_MARK``
- with ``{0, 0}``.
+ the mark of the existing SA. To use no mark on the new SA, send
+ ``XFRMA_MARK`` with ``{0, 0}``.
* - ``XFRMA_ENCAP``
- UDP encapsulation template; only ``UDP_ENCAP_ESPINUDP`` is supported.
Set ``encap_type=0`` to remove encap.
@@ -259,8 +262,9 @@ Attributes in the notification
Error Handling
==============
-If the target SA tuple (new daddr, SPI, proto, new family) is already
-occupied, the operation returns ``-EEXIST`` before the migration begins.
+If the target SA tuple (new daddr, SPI, proto, new family, mark) is
+already occupied, the operation returns ``-EEXIST`` before the migration
+begins.
The old SA remains intact and the operation is safe to retry after
resolving the conflict.
--
2.47.3
^ permalink raw reply related
* Re: [PATCH net-next v3 04/15] net: macb: unify queue index variable naming convention and types
From: Théo Lebrun @ 2026-07-03 17:10 UTC (permalink / raw)
To: Conor Dooley
Cc: Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
netdev, linux-kernel, Nicolas Ferre, Claudiu Beznea,
Paolo Valerio, Nicolai Buchwitz, Vladimir Kondratiev,
Gregory CLEMENT, Benoît Monin, Tawfik Bayouk,
Thomas Petazzoni, Maxime Chevallier
In-Reply-To: <20260703-monogram-unable-2eaeaf79676f@spud>
Hello Conor,
On Fri Jul 3, 2026 at 1:34 PM CEST, Conor Dooley wrote:
> On Wed, Jul 01, 2026 at 05:59:07PM +0200, Théo Lebrun wrote:
>> Variables are named q or queue_index. Types are int, unsigned int, u32
>> and u16. Use `unsigned int q` everywhere.
>>
>> Skip over taprio functions. They use `u8 queue_id` which fits with the
>> `struct macb_queue_enst_config` field. Using `queue_id` everywhere
>> would be too verbose.
>
> I'm not sure that I agree about the verbosity, and "q" isn't a letter I
> would naturally associate with indexing, in the way ijk etc are. Perhaps
> in netdev it is a natural choice however?
The question wasn't much if q made sense in netdev land, more about what
the MACB code used and to unify the naming convention.
Before After
struct macb_queue * q 1 0
int q 1 0
u32 queue_index 1 0
u16 queue_index 2 0
unsigned int q 4 12
struct macb_queue * queue 29 25
`q` that could be an index (5 times) or a `struct macb_queue *`
(29 times) was the most annoying to me. As `queue` was often the
pointer, I used that.
`queue_id` or `queue_index` for the stack felt too descriptive for no
valid reason. If it was green-field code I'd name it `qid` but that
name doesn't appear anywhere in MACB so I avoided introducing new
jargon.
If you have a name proposal I will probably agree with you and go that
route!
# command to extract counts
ctags -o - --kinds-c='{local}{member}{parameter}' \
--fields='{typeref}' drivers/net/ethernet/cadence/* | \
awk -F"\t" '$1=="q" || $1 ~ /queue/ {print $NF, $1}' | \
sort | uniq -c | sort -n
Thanks,
--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH 0/18] pull request (net-next): ipsec-next 2026-06-12
From: Antony Antony @ 2026-07-03 17:20 UTC (permalink / raw)
To: Yan Yan
Cc: Antony Antony, Jakub Kicinski, Steffen Klassert, Nathan Harold,
Antony Antony, David Miller, Herbert Xu, netdev, Tobias Brunner,
Sabrina Dubroca
In-Reply-To: <CADHa2dB9e=O_thTEPo3Bwtj9qRxE0UJd_MBriC8qqrPFB+x9rw@mail.gmail.com>
Hi Yan,
thanks for testing. Would you please test on a new series?
On Thu, Jun 25, 2026 at 06:58:47PM -0700, Yan Yan wrote:
> Hi Antony,
>
> I ran the following test cases and two—4.2 and 6—are worth disucssing:
>
> 1. Mark Inheritance: Creates one SA and migrates it without
> specifying a new mark. [PASSED]
> 2. Mark Update: Creates one SA with a specific mark and migrates it
> to a new specific mark. [PASSED]
> 3. State Update Isolation: Creates two SAs differing only by mark and
> verifies that migrating one does not affect the other. [PASSED]
> 4. Mark Collision:
> 4.1 [Specific Mark] Creates two SAs differing only by mark: SA
> (mark_1) and an SA (mark_2). Verifies migrating SA (mark_1) to mark_2
> fails with EEXIST and both SAs intact. [PASSED]
> 4.2 [Wildcard Mark] Creates two SAs differing only by mark: SA
> (mark_1) and an SA (mark_none). Migrate the mark_none to mark_1.
> Expecting failure with EEXIST and both SAs intact. [FAILED: SA
> (mark_none) ends up being deleted]
> 5. Mark Mismatch:
> 5.1 Creates only a wildcard SA and verifying migration using a
> specific mark fails with ESRCH; SA intact [PASSED]
> 5.2 Creates only one SA (mark_1) and verifying migration using a
> mark_2 fails with ESRCH; SA intact [PASSED]
> 6. Mark Shadowing Failure:: Creates two SAs differing only by mark:
> the first SA (mark_1) and the second SA (mark None). Migrating the SA
> (mark_1) fails with ESRCH, two SAs remain untouched [PASSED]
>
> --------------------------------
> My questions are as follows:
>
> Regarding 4.2 I believe is a kernel bug. During the pre-migration
> collision check, the lookup for the mark_1 matches the wildcard SA
> itself first, causing the kernel to miss the collision with the
> existing specific SA with mark_1
>
> At https://github.com/antonyantony/linux/blob/migrate-state-fixes-v0/net/xfrm/xfrm_user.c#L3450
>
> x_new = xfrm_state_lookup(net, new_mark_key, &m.new_daddr, ----> BUG:
> We are looking for the existence of SA (mark_1) but SA (mark_none) is
> returned.
> um->id.spi, um->id.proto, m.new_family);
> if (x_new) {
> xfrm_state_put(x_new);
> if (x_new != x) { ----> BUG: We end up having x_new == x
> NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
> err = -EEXIST;
> goto out;
> }
this should be fixed no. Would please try again?
> /* self-match via wide mark mask; not a collision */
>
> Would it be possible to traverse the entire list or create a lookup
> method for an exact match?
>
> Regarding 6. I wrote the tests as per the kernel implementation but
> I'm not sure it is intended. Intuitively, this migration should
> succeed by finding and updating the specific SA (mark_1) instead of
> failing. Is this failure considered an acceptable limit, or should we
> aim to support exact-match lookup here?
While trying to fix MIGRATE_STATE I fell into a rabbit hole:)
I realized many methods have this issue. Just never noticed, as it would be
a misconfiguration, and would likely cause operational issues rather than
crashes.
However, given that xfrm accepts multiple identical SAs differing only in mark,
a proper fix is to add __xfrm_state_locate_exact() for both spi and byaddr
lookups. This should be used in all calls from userspace.
I just send a new series.
https://patchwork.kernel.org/project/netdevbpf/patch/migrate-state-fixes-v0-1-a69e8637ba3b@secunet.com/
-antony
^ permalink raw reply
* Re: [PATCH net-next v4 3/3] net: dsa: motorcomm: Add LED support
From: Andrew Lunn @ 2026-07-03 17:40 UTC (permalink / raw)
To: David Yang
Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-kernel
In-Reply-To: <20260703165241.542195-4-mmyangfl@gmail.com>
On Sat, Jul 04, 2026 at 12:51:47AM +0800, David Yang wrote:
> LEDs can be described in the device tree using the same format as qca8k.
> Each port can configure up to 3 LEDs.
>
> Currently, only parallel mode and strict 1:1 mapping are supported.
>
> Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply
* Re: [PATCH v2 nf-next] ipvs: Move defense_work and est_reload_work to system_dfl_long_wq
From: Julian Anastasov @ 2026-07-03 17:49 UTC (permalink / raw)
To: Ismael Luceno
Cc: linux-kernel, Marco Crivellari, Tejun Heo, Simon Horman,
Pablo Neira Ayuso, Florian Westphal, Phil Sutter, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, open list:IPVS,
open list:IPVS, open list:NETFILTER, open list:NETFILTER
In-Reply-To: <20260702101100.24256-2-iluceno@suse.de>
Hello,
On Thu, 2 Jul 2026, Ismael Luceno wrote:
> Under synflood conditions binding these handlers to system_long_wq may
> pin them to a saturated CPU.
>
> We've observed improved throughtput on a DPDK/VPP application with this
> change, which we attribute to the reduced context switching.
>
> Neither handler has per-CPU data dependencies nor cache locality
> requirements that would prevent this change.
>
> Signed-off-by: Ismael Luceno <iluceno@suse.de>
Looks good to me, thanks!
Acked-by: Julian Anastasov <ja@ssi.bg>
> ---
> CC: Marco Crivellari <marco.crivellari@suse.com>
> CC: Tejun Heo <tj@kernel.org>
>
> Changes since v1:
> * Rebased on nf-next
> * Reworded commit message
>
> net/netfilter/ipvs/ip_vs_ctl.c | 6 +++---
> net/netfilter/ipvs/ip_vs_est.c | 2 +-
> 2 files changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
> index bcf40b8c41cf..d7e669efab4d 100644
> --- a/net/netfilter/ipvs/ip_vs_ctl.c
> +++ b/net/netfilter/ipvs/ip_vs_ctl.c
> @@ -235,7 +235,7 @@ #define DEFENSE_TIMER_PERIOD 1*HZ
> update_defense_level(ipvs);
> if (atomic_read(&ipvs->dropentry))
> ip_vs_random_dropentry(ipvs);
> - queue_delayed_work(system_long_wq, &ipvs->defense_work,
> + queue_delayed_work(system_dfl_long_wq, &ipvs->defense_work,
> DEFENSE_TIMER_PERIOD);
> }
> #endif
> @@ -290,7 +290,7 @@ #define DEFENSE_TIMER_PERIOD 1*HZ
> atomic_set(&ipvs->est_genid_done, genid);
>
> if (repeat)
> - queue_delayed_work(system_long_wq, &ipvs->est_reload_work,
> + queue_delayed_work(system_dfl_long_wq, &ipvs->est_reload_work,
> delay);
>
> unlock:
> @@ -5126,7 +5126,7 @@ do_ip_vs_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
> goto err;
>
> /* Schedule defense work */
> - queue_delayed_work(system_long_wq, &ipvs->defense_work,
> + queue_delayed_work(system_dfl_long_wq, &ipvs->defense_work,
> DEFENSE_TIMER_PERIOD);
>
> return 0;
> diff --git a/net/netfilter/ipvs/ip_vs_est.c b/net/netfilter/ipvs/ip_vs_est.c
> index ab09f5182951..78964aa861e9 100644
> --- a/net/netfilter/ipvs/ip_vs_est.c
> +++ b/net/netfilter/ipvs/ip_vs_est.c
> @@ -243,7 +243,7 @@ #define pr_fmt(fmt) "IPVS: " fmt
> /* Bump the kthread configuration genid if stopping is requested */
> if (restart)
> atomic_inc(&ipvs->est_genid);
> - queue_delayed_work(system_long_wq, &ipvs->est_reload_work, 0);
> + queue_delayed_work(system_dfl_long_wq, &ipvs->est_reload_work, 0);
> }
>
> /* Start kthread task with current configuration */
Regards
--
Julian Anastasov <ja@ssi.bg>
^ 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