* Re: [PATCH bpf-next v3 06/15] bpf: Add prog_list_init_item(), prog_list_replace_item(), and prog_list_id()
From: Emil Tsalapatis @ 2026-07-13 21:56 UTC (permalink / raw)
To: Amery Hung, bpf
Cc: netdev, alexei.starovoitov, andrii, daniel, eddyz87, memxor,
martin.lau, shakeel.butt, roman.gushchin, kuniyu, kerneljasonxing,
kernel-team
In-Reply-To: <20260706171918.317102-7-ameryhung@gmail.com>
On Mon Jul 6, 2026 at 1:19 PM EDT, Amery Hung wrote:
> From: Martin KaFai Lau <martin.lau@kernel.org>
>
> Add three helpers to abstract operations on a bpf_prog_list entry.
>
> Right now, bpf_prog_array_item is initialized from prog_list_prog(pl),
> which returns either pl->prog or pl->link->link.prog. This will not work
> when struct_ops is attached to a cgroup because the attachment is backed
> by a struct_ops map instead of a BPF prog.
>
> The same applies to __cgroup_bpf_query(). Instead of always copying a
> prog id to userspace, struct_ops cgroup attachment will need to copy the
> struct_ops map id.
>
> Refactor bpf_prog_array_item initialization into prog_list_init_item()
> and prog_list_replace_item(), and refactor id lookup into prog_list_id().
> These helpers will be extended to support pl->link->map in a later patch.
>
> This is a no-op change.
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Followup on prog_list_prog() since I hadn't realized it was moved to the
helpers in this patchset: Maybe we can just make it not return NULL
since it's impossible instead of adding error handling to minimize
churn.
>
> Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
> Signed-off-by: Amery Hung <ameryhung@gmail.com>
> ---
> kernel/bpf/cgroup.c | 26 +++++++++++++++++++-------
> 1 file changed, 19 insertions(+), 7 deletions(-)
>
> diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
> index b100c04cb9c8..b43f0bff184c 100644
> --- a/kernel/bpf/cgroup.c
> +++ b/kernel/bpf/cgroup.c
> @@ -399,6 +399,22 @@ static struct bpf_prog *prog_list_prog(struct bpf_prog_list *pl)
> return NULL;
> }
>
> +static void prog_list_init_item(struct bpf_prog_list *pl, struct bpf_prog_array_item *item)
> +{
> + item->prog = prog_list_prog(pl);
> + bpf_cgroup_storages_assign(item->cgroup_storage, pl->storage);
> +}
> +
> +static void prog_list_replace_item(struct bpf_prog_list *pl, struct bpf_prog_array_item *item)
> +{
> + WRITE_ONCE(item->prog, pl->link->link.prog);
> +}
> +
> +static u32 prog_list_id(struct bpf_prog_list *pl)
> +{
> + return prog_list_prog(pl)->aux->id;
> +}
> +
> /* count number of elements in the list.
> * it's slow but the list cannot be long
> */
> @@ -492,9 +508,7 @@ static int compute_effective_progs(struct cgroup *cgrp,
> item = &progs->items[fstart];
> fstart++;
> }
> - item->prog = prog_list_prog(pl);
> - bpf_cgroup_storages_assign(item->cgroup_storage,
> - pl->storage);
> + prog_list_init_item(pl, item);
> cnt++;
> }
>
> @@ -1015,7 +1029,7 @@ static void replace_effective_prog(struct cgroup *cgrp,
> desc->bpf.effective[atype],
> lockdep_is_held(&cgroup_mutex));
> item = &progs->items[pos];
> - WRITE_ONCE(item->prog, pl->link->link.prog);
> + prog_list_replace_item(pl, item);
> }
> }
>
> @@ -1318,15 +1332,13 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
> } else {
> struct hlist_head *progs;
> struct bpf_prog_list *pl;
> - struct bpf_prog *prog;
> u32 id;
>
> progs = &cgrp->bpf.progs[atype];
> cnt = min_t(int, prog_list_length(progs, NULL), total_cnt);
> i = 0;
> hlist_for_each_entry(pl, progs, node) {
> - prog = prog_list_prog(pl);
> - id = prog->aux->id;
> + id = prog_list_id(pl);
> if (copy_to_user(prog_ids + i, &id, sizeof(id)))
> return -EFAULT;
> if (++i == cnt)
^ permalink raw reply
* Re: [PATCH bpf-next v3 08/15] bpf: Add a few bpf_cgroup_array_* helper functions
From: Emil Tsalapatis @ 2026-07-13 21:57 UTC (permalink / raw)
To: Amery Hung, bpf
Cc: netdev, alexei.starovoitov, andrii, daniel, eddyz87, memxor,
martin.lau, shakeel.butt, roman.gushchin, kuniyu, kerneljasonxing,
kernel-team
In-Reply-To: <20260706171918.317102-9-ameryhung@gmail.com>
On Mon Jul 6, 2026 at 1:19 PM EDT, Amery Hung wrote:
> From: Martin KaFai Lau <martin.lau@kernel.org>
>
> In the upcoming patch, the array can store a struct_ops map.
> The array could have a cfi_stubs acting as a dummy instead of
> the dummy_bpf_prog. The array logic will need to skip the cfi_stubs
> also in order to support storing struct_ops map in the array.
>
> bpf_cgroup_array_length(), bpf_cgroup_array_copy_to_user(), and
> bpf_cgroup_array_delete_safe_at() are added as a preparation work
> to allow skipping the cfi_stubs in the upcoming patch. This patch
> only skips the dummy_bpf_prog which is the same as the existing behavior.
> The current bpf_prog_array_*() callers are changed to call the new
> bpf_cgroup_array_*(). This is a no-op change.
>
> Unlike bpf_prog_array_copy_to_user(), bpf_cgroup_array_copy_to_user()
> does not need a temporary buffer. The cgroup caller already holds
> cgroup_mutex and dereferences the effective array with
> rcu_dereference_protected(), so it does not copy to userspace
> from an RCU read-side critical section. Details in commit 0911287ce32b.
>
> Another addition is the bpf_cgroup_array_free(). This prepares
> the array to have a different rcu gp for the struct_ops use case,
> for example, a struct_ops could have mix of sleepable ops and
> non-sleepable ops. In this patch, bpf_cgroup_array_free() only
> goes through the regular rcu gp. This is a no-op change also.
>
> bpf_prog_dummy() is also added to return the global dummy_bpf_prog.
>
> bpf_cgroup_array_dummy() is added to decide the sentinel based on atype.
> It now always returns bpf_prog_dummy(). In the upcoming patch,
> it can return a cfi_stubs if the atype belongs to a struct_ops.
>
> Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
> Signed-off-by: Amery Hung <ameryhung@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
One style issue below.
> ---
> include/linux/bpf.h | 1 +
> kernel/bpf/cgroup.c | 79 +++++++++++++++++++++++++++++++++++++++------
> kernel/bpf/core.c | 5 +++
> 3 files changed, 76 insertions(+), 9 deletions(-)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 047ffc029666..e371a4733135 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -2561,6 +2561,7 @@ int bpf_prog_array_copy(struct bpf_prog_array *old_array,
> struct bpf_prog *include_prog,
> u64 bpf_cookie,
> struct bpf_prog_array **new_array);
> +struct bpf_prog *bpf_prog_dummy(void);
>
> struct bpf_run_ctx {};
>
> diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
> index 7abbe12e108f..081d81de1816 100644
> --- a/kernel/bpf/cgroup.c
> +++ b/kernel/bpf/cgroup.c
> @@ -319,6 +319,67 @@ static void bpf_cgroup_link_auto_detach(struct bpf_cgroup_link *link)
> link->cgroup = NULL;
> }
>
> +static void bpf_cgroup_array_free(struct bpf_prog_array *array)
> +{
> + if (!array || array == &bpf_empty_prog_array)
> + return;
> + kfree_rcu(array, rcu);
> +}
> +
> +static void *bpf_cgroup_array_dummy(enum cgroup_bpf_attach_type atype)
> +{
> + return bpf_prog_dummy();
> +}
> +
> +static int bpf_cgroup_array_length(struct bpf_prog_array *array,
> + enum cgroup_bpf_attach_type atype)
> +{
> + struct bpf_prog_array_item *item;
> + int cnt = 0;
> +
> + for (item = array->items; item->prog; item++)
> + if (item->prog != bpf_cgroup_array_dummy(atype))
> + cnt++;
The outer parenthesis needs braces since its body is an if-statement.
> +
> + return cnt;
> +}
> +
> +static int bpf_cgroup_array_copy_to_user(struct bpf_prog_array *array,
> + __u32 __user *prog_ids, int cnt,
> + enum cgroup_bpf_attach_type atype)
> +{
> + struct bpf_prog_array_item *item;
> + int i = 0;
> + u32 id;
> +
> + for (item = array->items; item->prog && i < cnt; item++) {
> + if (item->prog == bpf_cgroup_array_dummy(atype))
> + continue;
> + id = item->prog->aux->id;
> + if (copy_to_user(prog_ids + i, &id, sizeof(id)))
> + return -EFAULT;
> + i++;
> + }
> + return item->prog ? -ENOSPC : 0;
> +}
> +
> +static int bpf_cgroup_array_delete_safe_at(struct bpf_prog_array *array,
> + int index, enum cgroup_bpf_attach_type atype)
> +{
> + struct bpf_prog_array_item *item;
> +
> + for (item = array->items; item->prog; item++) {
> + if (item->prog == bpf_cgroup_array_dummy(atype))
> + continue;
> + if (!index) {
> + WRITE_ONCE(item->prog, bpf_cgroup_array_dummy(atype));
> + return 0;
> + }
> + index--;
> + }
> + return -ENOENT;
> +}
> +
> /**
> * cgroup_bpf_release() - put references of all bpf programs and
> * release all cgroup bpf data
> @@ -356,7 +417,7 @@ static void cgroup_bpf_release(struct work_struct *work)
> old_array = rcu_dereference_protected(
> cgrp->bpf.effective[atype],
> lockdep_is_held(&cgroup_mutex));
> - bpf_prog_array_free(old_array);
> + bpf_cgroup_array_free(old_array);
> }
>
> list_for_each_entry_safe(storage, stmp, storages, list_cg) {
> @@ -530,7 +591,7 @@ static void activate_effective_progs(struct cgroup *cgrp,
> /* free prog array after grace period, since __cgroup_bpf_run_*()
> * might be still walking the array
> */
> - bpf_prog_array_free(old_array);
> + bpf_cgroup_array_free(old_array);
> }
>
> /**
> @@ -570,7 +631,7 @@ static int cgroup_bpf_inherit(struct cgroup *cgrp)
> return 0;
> cleanup:
> for (i = 0; i < NR; i++)
> - bpf_prog_array_free(arrays[i]);
> + bpf_cgroup_array_free(arrays[i]);
>
> for (p = cgroup_parent(cgrp); p; p = cgroup_parent(p))
> cgroup_bpf_put(p);
> @@ -625,7 +686,7 @@ static int update_effective_progs(struct cgroup *cgrp,
>
> if (percpu_ref_is_zero(&desc->bpf.refcnt)) {
> if (unlikely(desc->bpf.inactive)) {
> - bpf_prog_array_free(desc->bpf.inactive);
> + bpf_cgroup_array_free(desc->bpf.inactive);
> desc->bpf.inactive = NULL;
> }
> continue;
> @@ -644,7 +705,7 @@ static int update_effective_progs(struct cgroup *cgrp,
> css_for_each_descendant_pre(css, &cgrp->self) {
> struct cgroup *desc = container_of(css, struct cgroup, self);
>
> - bpf_prog_array_free(desc->bpf.inactive);
> + bpf_cgroup_array_free(desc->bpf.inactive);
> desc->bpf.inactive = NULL;
> }
>
> @@ -1166,7 +1227,7 @@ static void purge_effective_progs(struct cgroup *cgrp, struct bpf_prog_list *pl,
> lockdep_is_held(&cgroup_mutex));
>
> /* Remove the program from the array */
> - WARN_ONCE(bpf_prog_array_delete_safe_at(progs, pos),
> + WARN_ONCE(bpf_cgroup_array_delete_safe_at(progs, pos, atype),
> "Failed to purge a prog from array at index %d", pos);
> }
> }
> @@ -1296,7 +1357,7 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
> if (effective_query) {
> effective = rcu_dereference_protected(cgrp->bpf.effective[atype],
> lockdep_is_held(&cgroup_mutex));
> - total_cnt += bpf_prog_array_length(effective);
> + total_cnt += bpf_cgroup_array_length(effective, atype);
> } else {
> total_cnt += prog_list_length(&cgrp->bpf.progs[atype], NULL);
> }
> @@ -1326,8 +1387,8 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
> if (effective_query) {
> effective = rcu_dereference_protected(cgrp->bpf.effective[atype],
> lockdep_is_held(&cgroup_mutex));
> - cnt = min_t(int, bpf_prog_array_length(effective), total_cnt);
> - ret = bpf_prog_array_copy_to_user(effective, prog_ids, cnt);
> + cnt = min_t(int, bpf_cgroup_array_length(effective, atype), total_cnt);
> + ret = bpf_cgroup_array_copy_to_user(effective, prog_ids, cnt, atype);
> } else {
> struct hlist_head *progs;
> struct bpf_prog_list *pl;
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index 649cce41e13f..1837bb7bb4e9 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -2740,6 +2740,11 @@ void bpf_prog_array_free_sleepable(struct bpf_prog_array *progs)
> call_rcu_tasks_trace(&progs->rcu, __bpf_prog_array_free_sleepable_cb);
> }
>
> +struct bpf_prog *bpf_prog_dummy(void)
> +{
> + return &dummy_bpf_prog.prog;
> +}
> +
> int bpf_prog_array_length(struct bpf_prog_array *array)
> {
> struct bpf_prog_array_item *item;
^ permalink raw reply
* Re: [PATCH bpf-next v3 07/15] bpf: Move LSM trampoline unlink into bpf_cgroup_link_auto_detach()
From: Emil Tsalapatis @ 2026-07-13 21:57 UTC (permalink / raw)
To: Amery Hung, bpf
Cc: netdev, alexei.starovoitov, andrii, daniel, eddyz87, memxor,
martin.lau, shakeel.butt, roman.gushchin, kuniyu, kerneljasonxing,
kernel-team
In-Reply-To: <20260706171918.317102-8-ameryhung@gmail.com>
On Mon Jul 6, 2026 at 1:19 PM EDT, Amery Hung wrote:
> From: Martin KaFai Lau <martin.lau@kernel.org>
>
> Move the LSM trampoline unlink into bpf_cgroup_link_auto_detach().
> The purpose is to consolidate the auto_detach cleanup logic.
>
> It prepares for the upcoming struct_ops cgroup attachment patch where
> bpf_cgroup_link_auto_detach() will need to handle the struct_ops case
> (link->map != NULL).
>
> This is a no-op change.
>
> Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
> Signed-off-by: Amery Hung <ameryhung@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---
> kernel/bpf/cgroup.c | 7 +++----
> 1 file changed, 3 insertions(+), 4 deletions(-)
>
> diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
> index b43f0bff184c..7abbe12e108f 100644
> --- a/kernel/bpf/cgroup.c
> +++ b/kernel/bpf/cgroup.c
> @@ -313,6 +313,8 @@ static void bpf_cgroup_storages_link(struct bpf_cgroup_storage *storages[],
> */
> static void bpf_cgroup_link_auto_detach(struct bpf_cgroup_link *link)
> {
> + if (link->link.prog->expected_attach_type == BPF_LSM_CGROUP)
> + bpf_trampoline_unlink_cgroup_shim(link->link.prog);
> cgroup_put(link->cgroup);
> link->cgroup = NULL;
> }
> @@ -346,11 +348,8 @@ static void cgroup_bpf_release(struct work_struct *work)
> bpf_trampoline_unlink_cgroup_shim(pl->prog);
> bpf_prog_put(pl->prog);
> }
> - if (pl->link) {
> - if (pl->link->link.prog->expected_attach_type == BPF_LSM_CGROUP)
> - bpf_trampoline_unlink_cgroup_shim(pl->link->link.prog);
> + if (pl->link)
> bpf_cgroup_link_auto_detach(pl->link);
> - }
> kfree(pl);
> static_branch_dec(&cgroup_bpf_enabled_key[atype]);
> }
^ permalink raw reply
* [PATCH net] nfc: microread: validate CARD_FOUND event length before parsing targets
From: Doruk Tan Ozturk @ 2026-07-13 21:59 UTC (permalink / raw)
To: David Heidelberg; +Cc: oe-linux-nfc, netdev, linux-kernel, stable
microread_target_discovered() parses a device-supplied MREAD_CARD_FOUND
event into a struct nfc_target, reading fixed offsets and -- for the
ISO-A and ISO-A-3 gates -- a variable-length NFCID1 straight out of the
event skb. The only length check is nfcid1_len vs sizeof(targets->nfcid1);
skb->len itself is never validated, so a short event makes every gate
case read out of bounds past the skb:
- ISO-A / ISO-A-3: fixed ATQA/SAK/LEN reads plus a memcpy of an
attacker-controlled nfcid1_len bytes from the NFCID1 offset;
- ISO-B / NFC-T1 / NFC-T3: a fixed 4- or 8-byte NFCID1 memcpy from a
fixed offset.
The copied nfcid1 is exported to user space via nfc_targets_found(), so
the over-read is an information leak (and a possible oops on an unmapped
page).
Reject events too short for the fields each gate case reads.
Found by 0sec (https://0sec.ai).
Fixes: cfad1ba87150 ("NFC: Initial support for Inside Secure microread")
Cc: stable@vger.kernel.org
Assisted-by: 0sec
Signed-off-by: Doruk Tan Ozturk <doruk@0sec.ai>
---
drivers/nfc/microread/microread.c | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/drivers/nfc/microread/microread.c b/drivers/nfc/microread/microread.c
index 4149c5d735bd..9a7a4fd6796b 100644
--- a/drivers/nfc/microread/microread.c
+++ b/drivers/nfc/microread/microread.c
@@ -483,13 +483,19 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate,
switch (gate) {
case MICROREAD_GATE_ID_MREAD_ISO_A:
+ if (skb->len < MICROREAD_EMCF_A_UID) {
+ r = -EINVAL;
+ goto exit_free;
+ }
+
targets->supported_protocols =
nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A_SAK]);
targets->sens_res =
be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A_ATQA]);
targets->sel_res = skb->data[MICROREAD_EMCF_A_SAK];
targets->nfcid1_len = skb->data[MICROREAD_EMCF_A_LEN];
- if (targets->nfcid1_len > sizeof(targets->nfcid1)) {
+ if (targets->nfcid1_len > sizeof(targets->nfcid1) ||
+ skb->len - MICROREAD_EMCF_A_UID < targets->nfcid1_len) {
r = -EINVAL;
goto exit_free;
}
@@ -497,13 +503,19 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate,
targets->nfcid1_len);
break;
case MICROREAD_GATE_ID_MREAD_ISO_A_3:
+ if (skb->len < MICROREAD_EMCF_A3_UID) {
+ r = -EINVAL;
+ goto exit_free;
+ }
+
targets->supported_protocols =
nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A3_SAK]);
targets->sens_res =
be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A3_ATQA]);
targets->sel_res = skb->data[MICROREAD_EMCF_A3_SAK];
targets->nfcid1_len = skb->data[MICROREAD_EMCF_A3_LEN];
- if (targets->nfcid1_len > sizeof(targets->nfcid1)) {
+ if (targets->nfcid1_len > sizeof(targets->nfcid1) ||
+ skb->len - MICROREAD_EMCF_A3_UID < targets->nfcid1_len) {
r = -EINVAL;
goto exit_free;
}
@@ -511,11 +523,21 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate,
targets->nfcid1_len);
break;
case MICROREAD_GATE_ID_MREAD_ISO_B:
+ if (skb->len < MICROREAD_EMCF_B_UID + 4) {
+ r = -EINVAL;
+ goto exit_free;
+ }
+
targets->supported_protocols = NFC_PROTO_ISO14443_B_MASK;
memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_B_UID], 4);
targets->nfcid1_len = 4;
break;
case MICROREAD_GATE_ID_MREAD_NFC_T1:
+ if (skb->len < MICROREAD_EMCF_T1_UID + 4) {
+ r = -EINVAL;
+ goto exit_free;
+ }
+
targets->supported_protocols = NFC_PROTO_JEWEL_MASK;
targets->sens_res =
le16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_T1_ATQA]);
@@ -523,6 +545,11 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate,
targets->nfcid1_len = 4;
break;
case MICROREAD_GATE_ID_MREAD_NFC_T3:
+ if (skb->len < MICROREAD_EMCF_T3_UID + 8) {
+ r = -EINVAL;
+ goto exit_free;
+ }
+
targets->supported_protocols = NFC_PROTO_FELICA_MASK;
memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_T3_UID], 8);
targets->nfcid1_len = 8;
--
2.43.0
^ permalink raw reply related
* [PATCH net] nexthop: initialize extack in nh_res_bucket_migrate()
From: Xiang Mei (Microsoft) @ 2026-07-13 22:15 UTC (permalink / raw)
To: David Ahern, Ido Schimmel
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Petr Machata, netdev, linux-kernel,
AutonomousCodeSecurity, tgopinath, kys, Xiang Mei (Microsoft)
nh_res_bucket_migrate() passes an uninitialized netlink_ext_ack to
call_nexthop_res_bucket_notifiers(). When
nh_notifier_res_bucket_info_init() fails (e.g. the kzalloc returns
-ENOMEM), the error is propagated back before any notifier sets
extack._msg, and the error path formats the stale pointer with
pr_err_ratelimited("%s\n", extack._msg). With CONFIG_INIT_STACK_NONE
this dereferences uninitialized stack memory:
Oops: general protection fault, probably for non-canonical address ...
KASAN: maybe wild-memory-access in range [...]
RIP: 0010:string (lib/vsprintf.c:730)
vsnprintf (lib/vsprintf.c:2945)
_printk (kernel/printk/printk.c:2504)
nh_res_bucket_migrate (net/ipv4/nexthop.c:1816)
nh_res_table_upkeep (net/ipv4/nexthop.c:1866)
rtm_new_nexthop (net/ipv4/nexthop.c:3323)
rtnetlink_rcv_msg (net/core/rtnetlink.c:7076)
netlink_sendmsg (net/netlink/af_netlink.c:1900)
Kernel panic - not syncing: Fatal exception
Zero-initialize extack so _msg is NULL on error paths that never set it.
Fixes: 7c37c7e00411 ("nexthop: Implement notifiers for resilient nexthop groups")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
---
net/ipv4/nexthop.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c
index 6205bd57aa85..44fe75004cac 100644
--- a/net/ipv4/nexthop.c
+++ b/net/ipv4/nexthop.c
@@ -1788,8 +1788,8 @@ static bool nh_res_bucket_migrate(struct nh_res_table *res_table,
bool notify_nl, bool force)
{
struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index];
+ struct netlink_ext_ack extack = {};
struct nh_grp_entry *new_nhge;
- struct netlink_ext_ack extack;
int err;
new_nhge = list_first_entry_or_null(&res_table->uw_nh_entries,
--
2.43.0
^ permalink raw reply related
* [PATCH 0/2] Remove pcrypt
From: Eric Biggers @ 2026-07-13 22:32 UTC (permalink / raw)
To: linux-crypto, Herbert Xu
Cc: netdev, linux-kernel, Steffen Klassert, Thomas Huth, Eric Biggers
This series removes the obsolete 'pcrypt' module, along with the
serialized job support in padata which was used only by pcrypt.
Changed in v2:
- Added patch that removes the serialized job support from padata.
Eric Biggers (2):
crypto: pcrypt - Remove pcrypt
padata: Remove serialized job support
Documentation/core-api/padata.rst | 145 +---
MAINTAINERS | 7 -
arch/loongarch/configs/loongson32_defconfig | 1 -
arch/loongarch/configs/loongson64_defconfig | 1 -
arch/s390/configs/debug_defconfig | 1 -
arch/s390/configs/defconfig | 1 -
crypto/Kconfig | 10 -
crypto/Makefile | 1 -
crypto/pcrypt.c | 394 ---------
include/crypto/pcrypt.h | 39 -
include/linux/padata.h | 145 +---
kernel/padata.c | 902 +-------------------
tools/crypto/tcrypt/tcrypt_speed_compare.py | 7 +-
13 files changed, 18 insertions(+), 1636 deletions(-)
delete mode 100644 crypto/pcrypt.c
delete mode 100644 include/crypto/pcrypt.h
base-commit: 0f26556c5eeea62cc934fa8938b148aa5844a6b6
--
2.55.0
^ permalink raw reply
* [PATCH 1/2] crypto: pcrypt - Remove pcrypt
From: Eric Biggers @ 2026-07-13 22:32 UTC (permalink / raw)
To: linux-crypto, Herbert Xu
Cc: netdev, linux-kernel, Steffen Klassert, Thomas Huth, Eric Biggers
In-Reply-To: <20260713223234.24812-1-ebiggers@kernel.org>
pcrypt was originally intended to improve IPsec performance. However,
it's no longer useful for that. Reports from the rare cases that anyone
has actually tried to use it over the years indicate that it actually
reduces IPsec performance, e.g.:
* https://github.com/libreswan/libreswan/wiki/Internals:-Cryptographic-Acceleration#obsoleted-ipsec-accelerations
* https://users.strongswan.narkive.com/liqTaTq8/strongswan-problem-with-pcrypt
* https://unix.stackexchange.com/questions/594336/ipsec-multithreading-via-pcrypt-worse-than-single-thread
It's also undocumented and quite difficult to actually use. Its design
is also broken, in that any unprivileged program can enable pcrypt
systemwide at any time (by instantiating it using AF_ALG).
Meanwhile, pcrypt has been a regular source of bugs, including at least
four that have received CVEs.
Let's just remove it. No one seems to care about it anymore other than
people looking for vulnerabilities.
Cc: Steffen Klassert <steffen.klassert@secunet.com>
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
MAINTAINERS | 7 -
arch/loongarch/configs/loongson32_defconfig | 1 -
arch/loongarch/configs/loongson64_defconfig | 1 -
arch/s390/configs/debug_defconfig | 1 -
arch/s390/configs/defconfig | 1 -
crypto/Kconfig | 10 -
crypto/Makefile | 1 -
crypto/pcrypt.c | 394 --------------------
include/crypto/pcrypt.h | 39 --
tools/crypto/tcrypt/tcrypt_speed_compare.py | 7 +-
10 files changed, 2 insertions(+), 460 deletions(-)
delete mode 100644 crypto/pcrypt.c
delete mode 100644 include/crypto/pcrypt.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 806bd2d80d15..260b3bdc7614 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21085,13 +21085,6 @@ L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/ethernet/amd/pcnet32.c
-PCRYPT PARALLEL CRYPTO ENGINE
-M: Steffen Klassert <steffen.klassert@secunet.com>
-L: linux-crypto@vger.kernel.org
-S: Maintained
-F: crypto/pcrypt.c
-F: include/crypto/pcrypt.h
-
PDS DSC VIRTIO DATA PATH ACCELERATOR
R: Brett Creeley <brett.creeley@amd.com>
F: drivers/vdpa/pds/
diff --git a/arch/loongarch/configs/loongson32_defconfig b/arch/loongarch/configs/loongson32_defconfig
index 7c8f01513ed2..cf97f4493573 100644
--- a/arch/loongarch/configs/loongson32_defconfig
+++ b/arch/loongarch/configs/loongson32_defconfig
@@ -1063,7 +1063,6 @@ CONFIG_SECURITY_YAMA=y
CONFIG_DEFAULT_SECURITY_DAC=y
CONFIG_CRYPTO_USER=m
CONFIG_CRYPTO_SELFTESTS=y
-CONFIG_CRYPTO_PCRYPT=m
CONFIG_CRYPTO_CRYPTD=m
CONFIG_CRYPTO_ANUBIS=m
CONFIG_CRYPTO_BLOWFISH=m
diff --git a/arch/loongarch/configs/loongson64_defconfig b/arch/loongarch/configs/loongson64_defconfig
index 8e3906d3bd70..d0ece7920f21 100644
--- a/arch/loongarch/configs/loongson64_defconfig
+++ b/arch/loongarch/configs/loongson64_defconfig
@@ -1096,7 +1096,6 @@ CONFIG_SECURITY_YAMA=y
CONFIG_DEFAULT_SECURITY_DAC=y
CONFIG_CRYPTO_USER=m
CONFIG_CRYPTO_SELFTESTS=y
-CONFIG_CRYPTO_PCRYPT=m
CONFIG_CRYPTO_CRYPTD=m
CONFIG_CRYPTO_ANUBIS=m
CONFIG_CRYPTO_BLOWFISH=m
diff --git a/arch/s390/configs/debug_defconfig b/arch/s390/configs/debug_defconfig
index 54637be87fb7..15f51cb924db 100644
--- a/arch/s390/configs/debug_defconfig
+++ b/arch/s390/configs/debug_defconfig
@@ -765,7 +765,6 @@ CONFIG_CRYPTO_USER=m
CONFIG_CRYPTO_SELFTESTS=y
CONFIG_CRYPTO_SELFTESTS_FULL=y
CONFIG_CRYPTO_NULL=y
-CONFIG_CRYPTO_PCRYPT=m
CONFIG_CRYPTO_CRYPTD=m
CONFIG_CRYPTO_BENCHMARK=m
CONFIG_CRYPTO_DH=m
diff --git a/arch/s390/configs/defconfig b/arch/s390/configs/defconfig
index 5f5114a253cf..88257ff3c2c6 100644
--- a/arch/s390/configs/defconfig
+++ b/arch/s390/configs/defconfig
@@ -749,7 +749,6 @@ CONFIG_CRYPTO_FIPS=y
CONFIG_CRYPTO_USER=m
CONFIG_CRYPTO_SELFTESTS=y
CONFIG_CRYPTO_NULL=y
-CONFIG_CRYPTO_PCRYPT=m
CONFIG_CRYPTO_CRYPTD=m
CONFIG_CRYPTO_BENCHMARK=m
CONFIG_CRYPTO_DH=m
diff --git a/crypto/Kconfig b/crypto/Kconfig
index f1e372195273..228a7ac9f063 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -201,16 +201,6 @@ config CRYPTO_NULL
help
These are 'Null' algorithms, used by IPsec, which do nothing.
-config CRYPTO_PCRYPT
- tristate "Parallel crypto engine"
- depends on SMP
- select PADATA
- select CRYPTO_MANAGER
- select CRYPTO_AEAD
- help
- This converts an arbitrary crypto algorithm into a parallel
- algorithm that executes in kernel threads.
-
config CRYPTO_CRYPTD
tristate "Software async crypto daemon"
select CRYPTO_AEAD
diff --git a/crypto/Makefile b/crypto/Makefile
index 8386d55a9755..2e487c946e63 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -120,7 +120,6 @@ CFLAGS_aegis128-neon-inner.o += $(aegis128-cflags-y)
aegis128-$(CONFIG_CRYPTO_AEGIS128_SIMD) += aegis128-neon.o aegis128-neon-inner.o
endif
-obj-$(CONFIG_CRYPTO_PCRYPT) += pcrypt.o
obj-$(CONFIG_CRYPTO_CRYPTD) += cryptd.o
obj-$(CONFIG_CRYPTO_DES) += des_generic.o
obj-$(CONFIG_CRYPTO_BLOWFISH) += blowfish_generic.o
diff --git a/crypto/pcrypt.c b/crypto/pcrypt.c
deleted file mode 100644
index 9f372442981e..000000000000
--- a/crypto/pcrypt.c
+++ /dev/null
@@ -1,394 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * pcrypt - Parallel crypto wrapper.
- *
- * Copyright (C) 2009 secunet Security Networks AG
- * Copyright (C) 2009 Steffen Klassert <steffen.klassert@secunet.com>
- */
-
-#include <crypto/algapi.h>
-#include <crypto/internal/aead.h>
-#include <linux/atomic.h>
-#include <linux/err.h>
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/kobject.h>
-#include <linux/cpu.h>
-#include <crypto/pcrypt.h>
-
-static struct padata_instance *pencrypt;
-static struct padata_instance *pdecrypt;
-static struct kset *pcrypt_kset;
-
-struct pcrypt_instance_ctx {
- struct crypto_aead_spawn spawn;
- struct padata_shell *psenc;
- struct padata_shell *psdec;
- atomic_t tfm_count;
-};
-
-struct pcrypt_aead_ctx {
- struct crypto_aead *child;
- unsigned int cb_cpu;
-};
-
-static inline struct pcrypt_instance_ctx *pcrypt_tfm_ictx(
- struct crypto_aead *tfm)
-{
- return aead_instance_ctx(aead_alg_instance(tfm));
-}
-
-static int pcrypt_aead_setkey(struct crypto_aead *parent,
- const u8 *key, unsigned int keylen)
-{
- struct pcrypt_aead_ctx *ctx = crypto_aead_ctx(parent);
-
- return crypto_aead_setkey(ctx->child, key, keylen);
-}
-
-static int pcrypt_aead_setauthsize(struct crypto_aead *parent,
- unsigned int authsize)
-{
- struct pcrypt_aead_ctx *ctx = crypto_aead_ctx(parent);
-
- return crypto_aead_setauthsize(ctx->child, authsize);
-}
-
-static void pcrypt_aead_serial(struct padata_priv *padata)
-{
- struct pcrypt_request *preq = pcrypt_padata_request(padata);
- struct aead_request *req = pcrypt_request_ctx(preq);
-
- aead_request_complete(req->base.data, padata->info);
-}
-
-static void pcrypt_aead_done(void *data, int err)
-{
- struct aead_request *req = data;
- struct pcrypt_request *preq = aead_request_ctx(req);
- struct padata_priv *padata = pcrypt_request_padata(preq);
-
- if (err == -EINPROGRESS)
- return;
-
- padata->info = err;
-
- padata_do_serial(padata);
-}
-
-static void pcrypt_aead_enc(struct padata_priv *padata)
-{
- struct pcrypt_request *preq = pcrypt_padata_request(padata);
- struct aead_request *req = pcrypt_request_ctx(preq);
- int ret;
-
- ret = crypto_aead_encrypt(req);
-
- if (ret == -EINPROGRESS || ret == -EBUSY)
- return;
-
- padata->info = ret;
- padata_do_serial(padata);
-}
-
-static int pcrypt_aead_encrypt(struct aead_request *req)
-{
- int err;
- struct pcrypt_request *preq = aead_request_ctx(req);
- struct aead_request *creq = pcrypt_request_ctx(preq);
- struct padata_priv *padata = pcrypt_request_padata(preq);
- struct crypto_aead *aead = crypto_aead_reqtfm(req);
- struct pcrypt_aead_ctx *ctx = crypto_aead_ctx(aead);
- u32 flags = aead_request_flags(req);
- struct pcrypt_instance_ctx *ictx;
-
- ictx = pcrypt_tfm_ictx(aead);
-
- memset(padata, 0, sizeof(struct padata_priv));
-
- padata->parallel = pcrypt_aead_enc;
- padata->serial = pcrypt_aead_serial;
-
- aead_request_set_tfm(creq, ctx->child);
- aead_request_set_callback(creq, flags & ~CRYPTO_TFM_REQ_MAY_SLEEP,
- pcrypt_aead_done, req);
- aead_request_set_crypt(creq, req->src, req->dst,
- req->cryptlen, req->iv);
- aead_request_set_ad(creq, req->assoclen);
-
- err = padata_do_parallel(ictx->psenc, padata, &ctx->cb_cpu);
- if (!err)
- return -EINPROGRESS;
- if (err == -EBUSY) {
- /* try non-parallel mode */
- aead_request_set_callback(creq, flags, req->base.complete,
- req->base.data);
- return crypto_aead_encrypt(creq);
- }
-
- return err;
-}
-
-static void pcrypt_aead_dec(struct padata_priv *padata)
-{
- struct pcrypt_request *preq = pcrypt_padata_request(padata);
- struct aead_request *req = pcrypt_request_ctx(preq);
- int ret;
-
- ret = crypto_aead_decrypt(req);
-
- if (ret == -EINPROGRESS || ret == -EBUSY)
- return;
-
- padata->info = ret;
- padata_do_serial(padata);
-}
-
-static int pcrypt_aead_decrypt(struct aead_request *req)
-{
- int err;
- struct pcrypt_request *preq = aead_request_ctx(req);
- struct aead_request *creq = pcrypt_request_ctx(preq);
- struct padata_priv *padata = pcrypt_request_padata(preq);
- struct crypto_aead *aead = crypto_aead_reqtfm(req);
- struct pcrypt_aead_ctx *ctx = crypto_aead_ctx(aead);
- u32 flags = aead_request_flags(req);
- struct pcrypt_instance_ctx *ictx;
-
- ictx = pcrypt_tfm_ictx(aead);
-
- memset(padata, 0, sizeof(struct padata_priv));
-
- padata->parallel = pcrypt_aead_dec;
- padata->serial = pcrypt_aead_serial;
-
- aead_request_set_tfm(creq, ctx->child);
- aead_request_set_callback(creq, flags & ~CRYPTO_TFM_REQ_MAY_SLEEP,
- pcrypt_aead_done, req);
- aead_request_set_crypt(creq, req->src, req->dst,
- req->cryptlen, req->iv);
- aead_request_set_ad(creq, req->assoclen);
-
- err = padata_do_parallel(ictx->psdec, padata, &ctx->cb_cpu);
- if (!err)
- return -EINPROGRESS;
- if (err == -EBUSY) {
- /* try non-parallel mode */
- aead_request_set_callback(creq, flags, req->base.complete,
- req->base.data);
- return crypto_aead_decrypt(creq);
- }
-
- return err;
-}
-
-static int pcrypt_aead_init_tfm(struct crypto_aead *tfm)
-{
- int cpu_index;
- struct aead_instance *inst = aead_alg_instance(tfm);
- struct pcrypt_instance_ctx *ictx = aead_instance_ctx(inst);
- struct pcrypt_aead_ctx *ctx = crypto_aead_ctx(tfm);
- struct crypto_aead *cipher;
-
- cpu_index = (unsigned int)atomic_inc_return(&ictx->tfm_count) %
- cpumask_weight(cpu_online_mask);
-
- ctx->cb_cpu = cpumask_nth(cpu_index, cpu_online_mask);
- cipher = crypto_spawn_aead(&ictx->spawn);
-
- if (IS_ERR(cipher))
- return PTR_ERR(cipher);
-
- ctx->child = cipher;
- crypto_aead_set_reqsize(tfm, sizeof(struct pcrypt_request) +
- sizeof(struct aead_request) +
- crypto_aead_reqsize(cipher));
-
- return 0;
-}
-
-static void pcrypt_aead_exit_tfm(struct crypto_aead *tfm)
-{
- struct pcrypt_aead_ctx *ctx = crypto_aead_ctx(tfm);
-
- crypto_free_aead(ctx->child);
-}
-
-static void pcrypt_free(struct aead_instance *inst)
-{
- struct pcrypt_instance_ctx *ctx = aead_instance_ctx(inst);
-
- crypto_drop_aead(&ctx->spawn);
- padata_free_shell(ctx->psdec);
- padata_free_shell(ctx->psenc);
- kfree(inst);
-}
-
-static int pcrypt_init_instance(struct crypto_instance *inst,
- struct crypto_alg *alg)
-{
- if (snprintf(inst->alg.cra_driver_name, CRYPTO_MAX_ALG_NAME,
- "pcrypt(%s)", alg->cra_driver_name) >= CRYPTO_MAX_ALG_NAME)
- return -ENAMETOOLONG;
-
- memcpy(inst->alg.cra_name, alg->cra_name, CRYPTO_MAX_ALG_NAME);
-
- inst->alg.cra_priority = alg->cra_priority + 100;
- inst->alg.cra_blocksize = alg->cra_blocksize;
- inst->alg.cra_alignmask = alg->cra_alignmask;
-
- return 0;
-}
-
-static int pcrypt_create_aead(struct crypto_template *tmpl, struct rtattr **tb,
- struct crypto_attr_type *algt)
-{
- struct pcrypt_instance_ctx *ctx;
- struct aead_instance *inst;
- struct aead_alg *alg;
- u32 mask = crypto_algt_inherited_mask(algt);
- int err;
-
- inst = kzalloc(sizeof(*inst) + sizeof(*ctx), GFP_KERNEL);
- if (!inst)
- return -ENOMEM;
-
- err = -ENOMEM;
-
- ctx = aead_instance_ctx(inst);
- ctx->psenc = padata_alloc_shell(pencrypt);
- if (!ctx->psenc)
- goto err_free_inst;
-
- ctx->psdec = padata_alloc_shell(pdecrypt);
- if (!ctx->psdec)
- goto err_free_inst;
-
- err = crypto_grab_aead(&ctx->spawn, aead_crypto_instance(inst),
- crypto_attr_alg_name(tb[1]), 0, mask);
- if (err)
- goto err_free_inst;
-
- alg = crypto_spawn_aead_alg(&ctx->spawn);
- err = pcrypt_init_instance(aead_crypto_instance(inst), &alg->base);
- if (err)
- goto err_free_inst;
-
- inst->alg.base.cra_flags |= CRYPTO_ALG_ASYNC;
-
- inst->alg.ivsize = crypto_aead_alg_ivsize(alg);
- inst->alg.maxauthsize = crypto_aead_alg_maxauthsize(alg);
-
- inst->alg.base.cra_ctxsize = sizeof(struct pcrypt_aead_ctx);
-
- inst->alg.init = pcrypt_aead_init_tfm;
- inst->alg.exit = pcrypt_aead_exit_tfm;
-
- inst->alg.setkey = pcrypt_aead_setkey;
- inst->alg.setauthsize = pcrypt_aead_setauthsize;
- inst->alg.encrypt = pcrypt_aead_encrypt;
- inst->alg.decrypt = pcrypt_aead_decrypt;
-
- inst->free = pcrypt_free;
-
- err = aead_register_instance(tmpl, inst);
- if (err) {
-err_free_inst:
- pcrypt_free(inst);
- }
- return err;
-}
-
-static int pcrypt_create(struct crypto_template *tmpl, struct rtattr **tb)
-{
- struct crypto_attr_type *algt;
-
- algt = crypto_get_attr_type(tb);
- if (IS_ERR(algt))
- return PTR_ERR(algt);
-
- switch (algt->type & algt->mask & CRYPTO_ALG_TYPE_MASK) {
- case CRYPTO_ALG_TYPE_AEAD:
- return pcrypt_create_aead(tmpl, tb, algt);
- }
-
- return -EINVAL;
-}
-
-static int pcrypt_sysfs_add(struct padata_instance *pinst, const char *name)
-{
- int ret;
-
- pinst->kobj.kset = pcrypt_kset;
- ret = kobject_add(&pinst->kobj, NULL, "%s", name);
- if (!ret)
- kobject_uevent(&pinst->kobj, KOBJ_ADD);
-
- return ret;
-}
-
-static int pcrypt_init_padata(struct padata_instance **pinst, const char *name)
-{
- int ret = -ENOMEM;
-
- *pinst = padata_alloc(name);
- if (!*pinst)
- return ret;
-
- ret = pcrypt_sysfs_add(*pinst, name);
- if (ret)
- padata_free(*pinst);
-
- return ret;
-}
-
-static struct crypto_template pcrypt_tmpl = {
- .name = "pcrypt",
- .create = pcrypt_create,
- .module = THIS_MODULE,
-};
-
-static int __init pcrypt_init(void)
-{
- int err = -ENOMEM;
-
- pcrypt_kset = kset_create_and_add("pcrypt", NULL, kernel_kobj);
- if (!pcrypt_kset)
- goto err;
-
- err = pcrypt_init_padata(&pencrypt, "pencrypt");
- if (err)
- goto err_unreg_kset;
-
- err = pcrypt_init_padata(&pdecrypt, "pdecrypt");
- if (err)
- goto err_deinit_pencrypt;
-
- return crypto_register_template(&pcrypt_tmpl);
-
-err_deinit_pencrypt:
- padata_free(pencrypt);
-err_unreg_kset:
- kset_unregister(pcrypt_kset);
-err:
- return err;
-}
-
-static void __exit pcrypt_exit(void)
-{
- crypto_unregister_template(&pcrypt_tmpl);
-
- padata_free(pencrypt);
- padata_free(pdecrypt);
-
- kset_unregister(pcrypt_kset);
-}
-
-module_init(pcrypt_init);
-module_exit(pcrypt_exit);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Steffen Klassert <steffen.klassert@secunet.com>");
-MODULE_DESCRIPTION("Parallel crypto wrapper");
-MODULE_ALIAS_CRYPTO("pcrypt");
diff --git a/include/crypto/pcrypt.h b/include/crypto/pcrypt.h
deleted file mode 100644
index 234d7cf3cf5e..000000000000
--- a/include/crypto/pcrypt.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * pcrypt - Parallel crypto engine.
- *
- * Copyright (C) 2009 secunet Security Networks AG
- * Copyright (C) 2009 Steffen Klassert <steffen.klassert@secunet.com>
- */
-
-#ifndef _CRYPTO_PCRYPT_H
-#define _CRYPTO_PCRYPT_H
-
-#include <linux/container_of.h>
-#include <linux/crypto.h>
-#include <linux/padata.h>
-
-struct pcrypt_request {
- struct padata_priv padata;
- void *data;
- void *__ctx[] CRYPTO_MINALIGN_ATTR;
-};
-
-static inline void *pcrypt_request_ctx(struct pcrypt_request *req)
-{
- return req->__ctx;
-}
-
-static inline
-struct padata_priv *pcrypt_request_padata(struct pcrypt_request *req)
-{
- return &req->padata;
-}
-
-static inline
-struct pcrypt_request *pcrypt_padata_request(struct padata_priv *padata)
-{
- return container_of(padata, struct pcrypt_request, padata);
-}
-
-#endif
diff --git a/tools/crypto/tcrypt/tcrypt_speed_compare.py b/tools/crypto/tcrypt/tcrypt_speed_compare.py
index f3f5783cdc06..0bf38c073dbc 100755
--- a/tools/crypto/tcrypt/tcrypt_speed_compare.py
+++ b/tools/crypto/tcrypt/tcrypt_speed_compare.py
@@ -28,19 +28,16 @@ num_mb=8
mode=211
# base speed test
-lsmod | grep pcrypt && modprobe -r pcrypt
dmesg -C
-modprobe tcrypt alg="pcrypt(rfc4106(gcm(aes)))" type=3
+modprobe tcrypt alg="rfc4106(gcm(aes))" type=3
modprobe tcrypt mode=${mode} sec=${sec} num_mb=${num_mb}
dmesg > ${seq_num}_base_dmesg.log
# new speed test
-lsmod | grep pcrypt && modprobe -r pcrypt
dmesg -C
-modprobe tcrypt alg="pcrypt(rfc4106(gcm(aes)))" type=3
+modprobe tcrypt alg="rfc4106(gcm(aes))" type=3
modprobe tcrypt mode=${mode} sec=${sec} num_mb=${num_mb}
dmesg > ${seq_num}_new_dmesg.log
-lsmod | grep pcrypt && modprobe -r pcrypt
tools/crypto/tcrypt/tcrypt_speed_compare.py \
${seq_num}_base_dmesg.log \
--
2.55.0
^ permalink raw reply related
* [PATCH 2/2] padata: Remove serialized job support
From: Eric Biggers @ 2026-07-13 22:32 UTC (permalink / raw)
To: linux-crypto, Herbert Xu
Cc: netdev, linux-kernel, Steffen Klassert, Thomas Huth, Eric Biggers
In-Reply-To: <20260713223234.24812-1-ebiggers@kernel.org>
Now that pcrypt has been removed, also remove all the code in padata
whose only user was pcrypt.
Cc: Steffen Klassert <steffen.klassert@secunet.com>
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
Documentation/core-api/padata.rst | 145 +----
include/linux/padata.h | 145 +----
kernel/padata.c | 902 +-----------------------------
3 files changed, 16 insertions(+), 1176 deletions(-)
diff --git a/Documentation/core-api/padata.rst b/Documentation/core-api/padata.rst
index 05b73c6c105f..d7dc7e33054d 100644
--- a/Documentation/core-api/padata.rst
+++ b/Documentation/core-api/padata.rst
@@ -7,152 +7,11 @@ The padata parallel execution mechanism
:Date: May 2020
Padata is a mechanism by which the kernel can farm jobs out to be done in
-parallel on multiple CPUs while optionally retaining their ordering.
+parallel on multiple CPUs while coordinating between threads.
-It was originally developed for IPsec, which needs to perform encryption and
-decryption on large numbers of packets without reordering those packets. This
-is currently the sole consumer of padata's serialized job support.
-
-Padata also supports multithreaded jobs, splitting up the job evenly while load
+Padata supports multithreaded jobs, splitting up the job evenly while load
balancing and coordinating between threads.
-Running Serialized Jobs
-=======================
-
-Initializing
-------------
-
-The first step in using padata to run serialized jobs is to set up a
-padata_instance structure for overall control of how jobs are to be run::
-
- #include <linux/padata.h>
-
- struct padata_instance *padata_alloc(const char *name);
-
-'name' simply identifies the instance.
-
-Then, complete padata initialization by allocating a padata_shell::
-
- struct padata_shell *padata_alloc_shell(struct padata_instance *pinst);
-
-A padata_shell is used to submit a job to padata and allows a series of such
-jobs to be serialized independently. A padata_instance may have one or more
-padata_shells associated with it, each allowing a separate series of jobs.
-
-Modifying cpumasks
-------------------
-
-The CPUs used to run jobs can be changed in two ways, programmatically with
-padata_set_cpumask() or via sysfs. The former is defined::
-
- int padata_set_cpumask(struct padata_instance *pinst, int cpumask_type,
- cpumask_var_t cpumask);
-
-Here cpumask_type is one of PADATA_CPU_PARALLEL or PADATA_CPU_SERIAL, where a
-parallel cpumask describes which processors will be used to execute jobs
-submitted to this instance in parallel and a serial cpumask defines which
-processors are allowed to be used as the serialization callback processor.
-cpumask specifies the new cpumask to use.
-
-There may be sysfs files for an instance's cpumasks. For example, pcrypt's
-live in /sys/kernel/pcrypt/<instance-name>. Within an instance's directory
-there are two files, parallel_cpumask and serial_cpumask, and either cpumask
-may be changed by echoing a bitmask into the file, for example::
-
- echo f > /sys/kernel/pcrypt/pencrypt/parallel_cpumask
-
-Reading one of these files shows the user-supplied cpumask, which may be
-different from the 'usable' cpumask.
-
-Padata maintains two pairs of cpumasks internally, the user-supplied cpumasks
-and the 'usable' cpumasks. (Each pair consists of a parallel and a serial
-cpumask.) The user-supplied cpumasks default to all possible CPUs on instance
-allocation and may be changed as above. The usable cpumasks are always a
-subset of the user-supplied cpumasks and contain only the online CPUs in the
-user-supplied masks; these are the cpumasks padata actually uses. So it is
-legal to supply a cpumask to padata that contains offline CPUs. Once an
-offline CPU in the user-supplied cpumask comes online, padata is going to use
-it.
-
-Changing the CPU masks are expensive operations, so it should not be done with
-great frequency.
-
-Running A Job
--------------
-
-Actually submitting work to the padata instance requires the creation of a
-padata_priv structure, which represents one job::
-
- struct padata_priv {
- /* Other stuff here... */
- void (*parallel)(struct padata_priv *padata);
- void (*serial)(struct padata_priv *padata);
- };
-
-This structure will almost certainly be embedded within some larger
-structure specific to the work to be done. Most of its fields are private to
-padata, but the structure should be zeroed at initialisation time, and the
-parallel() and serial() functions should be provided. Those functions will
-be called in the process of getting the work done as we will see
-momentarily.
-
-The submission of the job is done with::
-
- int padata_do_parallel(struct padata_shell *ps,
- struct padata_priv *padata, int *cb_cpu);
-
-The ps and padata structures must be set up as described above; cb_cpu
-points to the preferred CPU to be used for the final callback when the job is
-done; it must be in the current instance's CPU mask (if not the cb_cpu pointer
-is updated to point to the CPU actually chosen). The return value from
-padata_do_parallel() is zero on success, indicating that the job is in
-progress. -EBUSY means that somebody, somewhere else is messing with the
-instance's CPU mask, while -EINVAL is a complaint about cb_cpu not being in the
-serial cpumask, no online CPUs in the parallel or serial cpumasks, or a stopped
-instance.
-
-Each job submitted to padata_do_parallel() will, in turn, be passed to
-exactly one call to the above-mentioned parallel() function, on one CPU, so
-true parallelism is achieved by submitting multiple jobs. parallel() runs with
-software interrupts disabled and thus cannot sleep. The parallel()
-function gets the padata_priv structure pointer as its lone parameter;
-information about the actual work to be done is probably obtained by using
-container_of() to find the enclosing structure.
-
-Note that parallel() has no return value; the padata subsystem assumes that
-parallel() will take responsibility for the job from this point. The job
-need not be completed during this call, but, if parallel() leaves work
-outstanding, it should be prepared to be called again with a new job before
-the previous one completes.
-
-Serializing Jobs
-----------------
-
-When a job does complete, parallel() (or whatever function actually finishes
-the work) should inform padata of the fact with a call to::
-
- void padata_do_serial(struct padata_priv *padata);
-
-At some point in the future, padata_do_serial() will trigger a call to the
-serial() function in the padata_priv structure. That call will happen on
-the CPU requested in the initial call to padata_do_parallel(); it, too, is
-run with local software interrupts disabled.
-Note that this call may be deferred for a while since the padata code takes
-pains to ensure that jobs are completed in the order in which they were
-submitted.
-
-Destroying
-----------
-
-Cleaning up a padata instance predictably involves calling the two free
-functions that correspond to the allocation in reverse::
-
- void padata_free_shell(struct padata_shell *ps);
- void padata_free(struct padata_instance *pinst);
-
-It is the user's responsibility to ensure all outstanding jobs are complete
-before any of the above are called.
-
Running Multithreaded Jobs
==========================
diff --git a/include/linux/padata.h b/include/linux/padata.h
index b6232bea6edf..ea45ea680cb7 100644
--- a/include/linux/padata.h
+++ b/include/linux/padata.h
@@ -12,112 +12,8 @@
#ifndef PADATA_H
#define PADATA_H
-#include <linux/refcount.h>
-#include <linux/compiler_types.h>
-#include <linux/workqueue.h>
-#include <linux/spinlock.h>
-#include <linux/list.h>
-#include <linux/kobject.h>
-
-#define PADATA_CPU_SERIAL 0x01
-#define PADATA_CPU_PARALLEL 0x02
-
-/**
- * struct padata_priv - Represents one job
- *
- * @list: List entry, to attach to the padata lists.
- * @pd: Pointer to the internal control structure.
- * @cb_cpu: Callback cpu for serializatioon.
- * @seq_nr: Sequence number of the parallelized data object.
- * @info: Used to pass information from the parallel to the serial function.
- * @parallel: Parallel execution function.
- * @serial: Serial complete function.
- */
-struct padata_priv {
- struct list_head list;
- struct parallel_data *pd;
- int cb_cpu;
- unsigned int seq_nr;
- int info;
- void (*parallel)(struct padata_priv *padata);
- void (*serial)(struct padata_priv *padata);
-};
-
-/**
- * struct padata_list - one per work type per CPU
- *
- * @list: List head.
- * @lock: List lock.
- */
-struct padata_list {
- struct list_head list;
- spinlock_t lock;
-};
-
-/**
-* struct padata_serial_queue - The percpu padata serial queue
-*
-* @serial: List to wait for serialization after reordering.
-* @work: work struct for serialization.
-* @pd: Backpointer to the internal control structure.
-*/
-struct padata_serial_queue {
- struct padata_list serial;
- struct work_struct work;
- struct parallel_data *pd;
-};
-
-/**
- * struct padata_cpumask - The cpumasks for the parallel/serial workers
- *
- * @pcpu: cpumask for the parallel workers.
- * @cbcpu: cpumask for the serial (callback) workers.
- */
-struct padata_cpumask {
- cpumask_var_t pcpu;
- cpumask_var_t cbcpu;
-};
-
-/**
- * struct parallel_data - Internal control structure, covers everything
- * that depends on the cpumask in use.
- *
- * @ps: padata_shell object.
- * @reorder_list: percpu reorder lists
- * @squeue: percpu padata queues used for serialuzation.
- * @refcnt: Number of objects holding a reference on this parallel_data.
- * @seq_nr: Sequence number of the parallelized data object.
- * @processed: Number of already processed objects.
- * @cpu: Next CPU to be processed.
- * @cpumask: The cpumasks in use for parallel and serial workers.
- */
-struct parallel_data {
- struct padata_shell *ps;
- struct padata_list __percpu *reorder_list;
- struct padata_serial_queue __percpu *squeue;
- refcount_t refcnt;
- unsigned int seq_nr;
- unsigned int processed;
- int cpu;
- struct padata_cpumask cpumask;
-};
-
-/**
- * struct padata_shell - Wrapper around struct parallel_data, its
- * purpose is to allow the underlying control structure to be replaced
- * on the fly using RCU.
- *
- * @pinst: padat instance.
- * @pd: Actual parallel_data structure which may be substituted on the fly.
- * @opd: Pointer to old pd to be freed by padata_replace.
- * @list: List entry in padata_instance list.
- */
-struct padata_shell {
- struct padata_instance *pinst;
- struct parallel_data __rcu *pd;
- struct parallel_data *opd;
- struct list_head list;
-};
+#include <linux/init.h>
+#include <linux/types.h>
/**
* struct padata_mt_job - represents one multithreaded job
@@ -146,46 +42,9 @@ struct padata_mt_job {
bool numa_aware;
};
-/**
- * struct padata_instance - The overall control structure.
- *
- * @cpuhp_node: Linkage for CPU hotplug callbacks.
- * @parallel_wq: The workqueue used for parallel work.
- * @serial_wq: The workqueue used for serial work.
- * @pslist: List of padata_shell objects attached to this instance.
- * @cpumask: User supplied cpumasks for parallel and serial works.
- * @validate_cpumask: Internal cpumask used to validate @cpumask during hotplug.
- * @kobj: padata instance kernel object.
- * @lock: padata instance lock.
- * @flags: padata flags.
- */
-struct padata_instance {
- struct hlist_node cpuhp_node;
- struct workqueue_struct *parallel_wq;
- struct workqueue_struct *serial_wq;
- struct list_head pslist;
- struct padata_cpumask cpumask;
- cpumask_var_t validate_cpumask;
- struct kobject kobj;
- struct mutex lock;
- u8 flags;
-#define PADATA_INIT 1
-#define PADATA_RESET 2
-#define PADATA_INVALID 4
-};
-
#ifdef CONFIG_PADATA
extern void __init padata_init(void);
-extern struct padata_instance *padata_alloc(const char *name);
-extern void padata_free(struct padata_instance *pinst);
-extern struct padata_shell *padata_alloc_shell(struct padata_instance *pinst);
-extern void padata_free_shell(struct padata_shell *ps);
-extern int padata_do_parallel(struct padata_shell *ps,
- struct padata_priv *padata, int *cb_cpu);
-extern void padata_do_serial(struct padata_priv *padata);
extern void __init padata_do_multithreaded(struct padata_mt_job *job);
-extern int padata_set_cpumask(struct padata_instance *pinst, int cpumask_type,
- cpumask_var_t cpumask);
#else
static inline void __init padata_init(void) {}
static inline void __init padata_do_multithreaded(struct padata_mt_job *job)
diff --git a/kernel/padata.c b/kernel/padata.c
index 0d3ea1b68b1f..6eb130d31024 100644
--- a/kernel/padata.c
+++ b/kernel/padata.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * padata.c - generic interface to process data streams in parallel
+ * padata.c - generic interface to run multithreaded jobs
*
* See Documentation/core-api/padata.rst for more information.
*
@@ -11,17 +11,17 @@
* Author: Daniel Jordan <daniel.m.jordan@oracle.com>
*/
+#include <linux/atomic.h>
#include <linux/completion.h>
-#include <linux/export.h>
#include <linux/cpumask.h>
-#include <linux/err.h>
-#include <linux/cpu.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/nodemask.h>
#include <linux/padata.h>
-#include <linux/mutex.h>
-#include <linux/sched.h>
#include <linux/slab.h>
-#include <linux/sysfs.h>
-#include <linux/rcupdate.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
#define PADATA_WORK_ONSTACK 1 /* Work's memory is on stack */
@@ -44,36 +44,8 @@ struct padata_mt_job_state {
unsigned long chunk_size;
};
-static void padata_free_pd(struct parallel_data *pd);
static void __init padata_mt_helper(struct work_struct *work);
-static inline void padata_get_pd(struct parallel_data *pd)
-{
- refcount_inc(&pd->refcnt);
-}
-
-static inline void padata_put_pd_cnt(struct parallel_data *pd, int cnt)
-{
- if (refcount_sub_and_test(cnt, &pd->refcnt))
- padata_free_pd(pd);
-}
-
-static inline void padata_put_pd(struct parallel_data *pd)
-{
- padata_put_pd_cnt(pd, 1);
-}
-
-static int padata_cpu_hash(struct parallel_data *pd, unsigned int seq_nr)
-{
- /*
- * Hash the sequence numbers to the cpus by taking
- * seq_nr mod. number of cpus in use.
- */
- int cpu_index = seq_nr % cpumask_weight(pd->cpumask.pcpu);
-
- return cpumask_nth(cpu_index, pd->cpumask.pcpu);
-}
-
static struct padata_work *padata_work_alloc(void)
{
struct padata_work *pw;
@@ -147,260 +119,6 @@ static void __init padata_works_free(struct list_head *works)
spin_unlock_bh(&padata_works_lock);
}
-static void padata_parallel_worker(struct work_struct *parallel_work)
-{
- struct padata_work *pw = container_of(parallel_work, struct padata_work,
- pw_work);
- struct padata_priv *padata = pw->pw_data;
-
- local_bh_disable();
- padata->parallel(padata);
- spin_lock(&padata_works_lock);
- padata_work_free(pw);
- spin_unlock(&padata_works_lock);
- local_bh_enable();
-}
-
-/**
- * padata_do_parallel - padata parallelization function
- *
- * @ps: padatashell
- * @padata: object to be parallelized
- * @cb_cpu: pointer to the CPU that the serialization callback function should
- * run on. If it's not in the serial cpumask of @pinst
- * (i.e. cpumask.cbcpu), this function selects a fallback CPU and if
- * none found, returns -EINVAL.
- *
- * The parallelization callback function will run with BHs off.
- * Note: Every object which is parallelized by padata_do_parallel
- * must be seen by padata_do_serial.
- *
- * Return: 0 on success or else negative error code.
- */
-int padata_do_parallel(struct padata_shell *ps,
- struct padata_priv *padata, int *cb_cpu)
-{
- struct padata_instance *pinst = ps->pinst;
- struct parallel_data *pd;
- struct padata_work *pw;
- int cpu_index, err;
-
- rcu_read_lock_bh();
-
- pd = rcu_dereference_bh(ps->pd);
-
- err = -EINVAL;
- if (!(pinst->flags & PADATA_INIT) || pinst->flags & PADATA_INVALID)
- goto out;
-
- if (!cpumask_test_cpu(*cb_cpu, pd->cpumask.cbcpu)) {
- if (cpumask_empty(pd->cpumask.cbcpu))
- goto out;
-
- /* Select an alternate fallback CPU and notify the caller. */
- cpu_index = *cb_cpu % cpumask_weight(pd->cpumask.cbcpu);
- *cb_cpu = cpumask_nth(cpu_index, pd->cpumask.cbcpu);
- }
-
- err = -EBUSY;
- if ((pinst->flags & PADATA_RESET))
- goto out;
-
- padata_get_pd(pd);
- padata->pd = pd;
- padata->cb_cpu = *cb_cpu;
-
- spin_lock(&padata_works_lock);
- padata->seq_nr = ++pd->seq_nr;
- pw = padata_work_alloc();
- spin_unlock(&padata_works_lock);
-
- if (!pw) {
- /* Maximum works limit exceeded, run in the current task. */
- padata->parallel(padata);
- }
-
- rcu_read_unlock_bh();
-
- if (pw) {
- padata_work_init(pw, padata_parallel_worker, padata, 0);
- queue_work(pinst->parallel_wq, &pw->pw_work);
- }
-
- return 0;
-out:
- rcu_read_unlock_bh();
-
- return err;
-}
-EXPORT_SYMBOL(padata_do_parallel);
-
-/*
- * padata_find_next - Find the next object that needs serialization.
- *
- * Return:
- * * A pointer to the control struct of the next object that needs
- * serialization, if present in one of the percpu reorder queues.
- * * NULL, if the next object that needs serialization will
- * be parallel processed by another cpu and is not yet present in
- * the cpu's reorder queue.
- */
-static struct padata_priv *padata_find_next(struct parallel_data *pd, int cpu,
- unsigned int processed)
-{
- struct padata_priv *padata;
- struct padata_list *reorder;
-
- reorder = per_cpu_ptr(pd->reorder_list, cpu);
-
- spin_lock(&reorder->lock);
- if (list_empty(&reorder->list))
- goto notfound;
-
- padata = list_entry(reorder->list.next, struct padata_priv, list);
-
- /*
- * Checks the rare case where two or more parallel jobs have hashed to
- * the same CPU and one of the later ones finishes first.
- */
- if (padata->seq_nr != processed)
- goto notfound;
-
- list_del_init(&padata->list);
- spin_unlock(&reorder->lock);
- return padata;
-
-notfound:
- pd->processed = processed;
- pd->cpu = cpu;
- spin_unlock(&reorder->lock);
- return NULL;
-}
-
-static void padata_reorder(struct padata_priv *padata)
-{
- struct parallel_data *pd = padata->pd;
- struct padata_instance *pinst = pd->ps->pinst;
- unsigned int processed;
- int cpu;
-
- processed = pd->processed;
- cpu = pd->cpu;
-
- do {
- struct padata_serial_queue *squeue;
- int cb_cpu;
-
- processed++;
- /* When sequence wraps around, reset to the first CPU. */
- if (unlikely(processed == 0))
- cpu = cpumask_first(pd->cpumask.pcpu);
- else
- cpu = cpumask_next_wrap(cpu, pd->cpumask.pcpu);
-
- cb_cpu = padata->cb_cpu;
- squeue = per_cpu_ptr(pd->squeue, cb_cpu);
-
- spin_lock(&squeue->serial.lock);
- list_add_tail(&padata->list, &squeue->serial.list);
- queue_work_on(cb_cpu, pinst->serial_wq, &squeue->work);
-
- /*
- * If the next object that needs serialization is parallel
- * processed by another cpu and is still on it's way to the
- * cpu's reorder queue, end the loop.
- */
- padata = padata_find_next(pd, cpu, processed);
- spin_unlock(&squeue->serial.lock);
- } while (padata);
-}
-
-static void padata_serial_worker(struct work_struct *serial_work)
-{
- struct padata_serial_queue *squeue;
- struct parallel_data *pd;
- LIST_HEAD(local_list);
- int cnt;
-
- local_bh_disable();
- squeue = container_of(serial_work, struct padata_serial_queue, work);
- pd = squeue->pd;
-
- spin_lock(&squeue->serial.lock);
- list_replace_init(&squeue->serial.list, &local_list);
- spin_unlock(&squeue->serial.lock);
-
- cnt = 0;
-
- while (!list_empty(&local_list)) {
- struct padata_priv *padata;
-
- padata = list_entry(local_list.next,
- struct padata_priv, list);
-
- list_del_init(&padata->list);
-
- padata->serial(padata);
- cnt++;
- }
- local_bh_enable();
-
- padata_put_pd_cnt(pd, cnt);
-}
-
-/**
- * padata_do_serial - padata serialization function
- *
- * @padata: object to be serialized.
- *
- * padata_do_serial must be called for every parallelized object.
- * The serialization callback function will run with BHs off.
- */
-void padata_do_serial(struct padata_priv *padata)
-{
- struct parallel_data *pd = padata->pd;
- int hashed_cpu = padata_cpu_hash(pd, padata->seq_nr);
- struct padata_list *reorder = per_cpu_ptr(pd->reorder_list, hashed_cpu);
- struct padata_priv *cur;
- struct list_head *pos;
- bool gotit = true;
-
- spin_lock(&reorder->lock);
- /* Sort in ascending order of sequence number. */
- list_for_each_prev(pos, &reorder->list) {
- cur = list_entry(pos, struct padata_priv, list);
- /* Compare by difference to consider integer wrap around */
- if ((signed int)(cur->seq_nr - padata->seq_nr) < 0)
- break;
- }
- if (padata->seq_nr != pd->processed) {
- gotit = false;
- list_add(&padata->list, pos);
- }
- spin_unlock(&reorder->lock);
-
- if (gotit)
- padata_reorder(padata);
-}
-EXPORT_SYMBOL(padata_do_serial);
-
-static int padata_setup_cpumasks(struct padata_instance *pinst)
-{
- struct workqueue_attrs *attrs;
- int err;
-
- attrs = alloc_workqueue_attrs();
- if (!attrs)
- return -ENOMEM;
-
- /* Restrict parallel_wq workers to pd->cpumask.pcpu. */
- cpumask_copy(attrs->cpumask, pinst->cpumask.pcpu);
- err = apply_workqueue_attrs(pinst->parallel_wq, attrs);
- free_workqueue_attrs(attrs);
-
- return err;
-}
-
static void __init padata_mt_helper(struct work_struct *w)
{
struct padata_work *pw = container_of(w, struct padata_work, pw_work);
@@ -506,613 +224,17 @@ void __init padata_do_multithreaded(struct padata_mt_job *job)
padata_works_free(&works);
}
-/* Initialize all percpu queues used by serial workers */
-static void padata_init_squeues(struct parallel_data *pd)
-{
- int cpu;
- struct padata_serial_queue *squeue;
-
- for_each_cpu(cpu, pd->cpumask.cbcpu) {
- squeue = per_cpu_ptr(pd->squeue, cpu);
- squeue->pd = pd;
- INIT_LIST_HEAD(&squeue->serial.list);
- spin_lock_init(&squeue->serial.lock);
- INIT_WORK(&squeue->work, padata_serial_worker);
- }
-}
-
-/* Initialize per-CPU reorder lists */
-static void padata_init_reorder_list(struct parallel_data *pd)
-{
- int cpu;
- struct padata_list *list;
-
- for_each_cpu(cpu, pd->cpumask.pcpu) {
- list = per_cpu_ptr(pd->reorder_list, cpu);
- INIT_LIST_HEAD(&list->list);
- spin_lock_init(&list->lock);
- }
-}
-
-/* Allocate and initialize the internal cpumask dependend resources. */
-static struct parallel_data *padata_alloc_pd(struct padata_shell *ps,
- int offlining_cpu)
-{
- struct padata_instance *pinst = ps->pinst;
- struct parallel_data *pd;
-
- pd = kzalloc_obj(struct parallel_data);
- if (!pd)
- goto err;
-
- pd->reorder_list = alloc_percpu(struct padata_list);
- if (!pd->reorder_list)
- goto err_free_pd;
-
- pd->squeue = alloc_percpu(struct padata_serial_queue);
- if (!pd->squeue)
- goto err_free_reorder_list;
-
- pd->ps = ps;
-
- if (!alloc_cpumask_var(&pd->cpumask.pcpu, GFP_KERNEL))
- goto err_free_squeue;
- if (!alloc_cpumask_var(&pd->cpumask.cbcpu, GFP_KERNEL))
- goto err_free_pcpu;
-
- cpumask_and(pd->cpumask.pcpu, pinst->cpumask.pcpu, cpu_online_mask);
- cpumask_and(pd->cpumask.cbcpu, pinst->cpumask.cbcpu, cpu_online_mask);
- if (offlining_cpu >= 0) {
- __cpumask_clear_cpu(offlining_cpu, pd->cpumask.pcpu);
- __cpumask_clear_cpu(offlining_cpu, pd->cpumask.cbcpu);
- }
-
- padata_init_reorder_list(pd);
- padata_init_squeues(pd);
- pd->seq_nr = -1;
- refcount_set(&pd->refcnt, 1);
- pd->cpu = cpumask_first(pd->cpumask.pcpu);
-
- return pd;
-
-err_free_pcpu:
- free_cpumask_var(pd->cpumask.pcpu);
-err_free_squeue:
- free_percpu(pd->squeue);
-err_free_reorder_list:
- free_percpu(pd->reorder_list);
-err_free_pd:
- kfree(pd);
-err:
- return NULL;
-}
-
-static void padata_free_pd(struct parallel_data *pd)
-{
- free_cpumask_var(pd->cpumask.pcpu);
- free_cpumask_var(pd->cpumask.cbcpu);
- free_percpu(pd->reorder_list);
- free_percpu(pd->squeue);
- kfree(pd);
-}
-
-static void __padata_start(struct padata_instance *pinst)
-{
- pinst->flags |= PADATA_INIT;
-}
-
-static void __padata_stop(struct padata_instance *pinst)
-{
- if (!(pinst->flags & PADATA_INIT))
- return;
-
- pinst->flags &= ~PADATA_INIT;
-
- synchronize_rcu();
-}
-
-/* Replace the internal control structure with a new one. */
-static int padata_replace_one(struct padata_shell *ps, int offlining_cpu)
-{
- struct parallel_data *pd_new;
-
- pd_new = padata_alloc_pd(ps, offlining_cpu);
- if (!pd_new)
- return -ENOMEM;
-
- ps->opd = rcu_dereference_protected(ps->pd, 1);
- rcu_assign_pointer(ps->pd, pd_new);
-
- return 0;
-}
-
-static int padata_replace(struct padata_instance *pinst, int offlining_cpu)
-{
- struct padata_shell *ps;
- int err = 0;
-
- pinst->flags |= PADATA_RESET;
-
- list_for_each_entry(ps, &pinst->pslist, list) {
- err = padata_replace_one(ps, offlining_cpu);
- if (err)
- break;
- }
-
- synchronize_rcu();
-
- list_for_each_entry_continue_reverse(ps, &pinst->pslist, list)
- padata_put_pd(ps->opd);
-
- pinst->flags &= ~PADATA_RESET;
-
- return err;
-}
-
-/* If cpumask contains no active cpu, we mark the instance as invalid. */
-static bool padata_validate_cpumask(struct padata_instance *pinst,
- const struct cpumask *cpumask,
- int offlining_cpu)
-{
- cpumask_copy(pinst->validate_cpumask, cpu_online_mask);
-
- /*
- * @offlining_cpu is still in cpu_online_mask, so remove it here for
- * validation. Using a sub-CPUHP_TEARDOWN_CPU hotplug state where
- * @offlining_cpu wouldn't be in the online mask doesn't work because
- * padata_cpu_offline() can fail but such a state doesn't allow failure.
- */
- if (offlining_cpu >= 0)
- __cpumask_clear_cpu(offlining_cpu, pinst->validate_cpumask);
-
- if (!cpumask_intersects(cpumask, pinst->validate_cpumask)) {
- pinst->flags |= PADATA_INVALID;
- return false;
- }
-
- pinst->flags &= ~PADATA_INVALID;
- return true;
-}
-
-static int __padata_set_cpumasks(struct padata_instance *pinst,
- cpumask_var_t pcpumask,
- cpumask_var_t cbcpumask)
-{
- int valid;
- int err;
-
- valid = padata_validate_cpumask(pinst, pcpumask, -1);
- if (!valid) {
- __padata_stop(pinst);
- goto out_replace;
- }
-
- valid = padata_validate_cpumask(pinst, cbcpumask, -1);
- if (!valid)
- __padata_stop(pinst);
-
-out_replace:
- cpumask_copy(pinst->cpumask.pcpu, pcpumask);
- cpumask_copy(pinst->cpumask.cbcpu, cbcpumask);
-
- err = padata_setup_cpumasks(pinst) ?: padata_replace(pinst, -1);
-
- if (valid)
- __padata_start(pinst);
-
- return err;
-}
-
-/**
- * padata_set_cpumask - Sets specified by @cpumask_type cpumask to the value
- * equivalent to @cpumask.
- * @pinst: padata instance
- * @cpumask_type: PADATA_CPU_SERIAL or PADATA_CPU_PARALLEL corresponding
- * to parallel and serial cpumasks respectively.
- * @cpumask: the cpumask to use
- *
- * Return: 0 on success or negative error code
- */
-int padata_set_cpumask(struct padata_instance *pinst, int cpumask_type,
- cpumask_var_t cpumask)
-{
- struct cpumask *serial_mask, *parallel_mask;
- int err = -EINVAL;
-
- cpus_read_lock();
- mutex_lock(&pinst->lock);
-
- switch (cpumask_type) {
- case PADATA_CPU_PARALLEL:
- serial_mask = pinst->cpumask.cbcpu;
- parallel_mask = cpumask;
- break;
- case PADATA_CPU_SERIAL:
- parallel_mask = pinst->cpumask.pcpu;
- serial_mask = cpumask;
- break;
- default:
- goto out;
- }
-
- err = __padata_set_cpumasks(pinst, parallel_mask, serial_mask);
-
-out:
- mutex_unlock(&pinst->lock);
- cpus_read_unlock();
-
- return err;
-}
-EXPORT_SYMBOL(padata_set_cpumask);
-
-#ifdef CONFIG_HOTPLUG_CPU
-
-static inline int pinst_has_cpu(struct padata_instance *pinst, int cpu)
-{
- return cpumask_test_cpu(cpu, pinst->cpumask.pcpu) ||
- cpumask_test_cpu(cpu, pinst->cpumask.cbcpu);
-}
-
-static int padata_cpu_online(unsigned int cpu, struct hlist_node *node)
-{
- struct padata_instance *pinst;
- int ret;
-
- pinst = hlist_entry_safe(node, struct padata_instance, cpuhp_node);
- if (!pinst_has_cpu(pinst, cpu))
- return 0;
-
- mutex_lock(&pinst->lock);
-
- ret = padata_replace(pinst, -1);
-
- if (padata_validate_cpumask(pinst, pinst->cpumask.pcpu, -1) &&
- padata_validate_cpumask(pinst, pinst->cpumask.cbcpu, -1))
- __padata_start(pinst);
-
- mutex_unlock(&pinst->lock);
- return ret;
-}
-
-static int padata_cpu_offline(unsigned int cpu, struct hlist_node *node)
-{
- struct padata_instance *pinst;
- int ret;
-
- pinst = hlist_entry_safe(node, struct padata_instance, cpuhp_node);
- if (!pinst_has_cpu(pinst, cpu))
- return 0;
-
- mutex_lock(&pinst->lock);
-
- if (!padata_validate_cpumask(pinst, pinst->cpumask.pcpu, cpu) ||
- !padata_validate_cpumask(pinst, pinst->cpumask.cbcpu, cpu))
- __padata_stop(pinst);
-
- ret = padata_replace(pinst, cpu);
-
- mutex_unlock(&pinst->lock);
- return ret;
-}
-
-static enum cpuhp_state hp_online;
-#endif
-
-static void __padata_free(struct padata_instance *pinst)
-{
-#ifdef CONFIG_HOTPLUG_CPU
- cpuhp_state_remove_instance_nocalls(hp_online, &pinst->cpuhp_node);
-#endif
-
- WARN_ON(!list_empty(&pinst->pslist));
-
- free_cpumask_var(pinst->cpumask.pcpu);
- free_cpumask_var(pinst->cpumask.cbcpu);
- free_cpumask_var(pinst->validate_cpumask);
- destroy_workqueue(pinst->serial_wq);
- destroy_workqueue(pinst->parallel_wq);
- kfree(pinst);
-}
-
-#define kobj2pinst(_kobj) \
- container_of(_kobj, struct padata_instance, kobj)
-#define attr2pentry(_attr) \
- container_of_const(_attr, struct padata_sysfs_entry, attr)
-
-static void padata_sysfs_release(struct kobject *kobj)
-{
- struct padata_instance *pinst = kobj2pinst(kobj);
- __padata_free(pinst);
-}
-
-struct padata_sysfs_entry {
- struct attribute attr;
- ssize_t (*show)(struct padata_instance *, const struct attribute *, char *);
- ssize_t (*store)(struct padata_instance *, const struct attribute *,
- const char *, size_t);
-};
-
-static ssize_t show_cpumask(struct padata_instance *pinst,
- const struct attribute *attr, char *buf)
-{
- struct cpumask *cpumask;
- ssize_t len;
-
- mutex_lock(&pinst->lock);
- if (!strcmp(attr->name, "serial_cpumask"))
- cpumask = pinst->cpumask.cbcpu;
- else
- cpumask = pinst->cpumask.pcpu;
-
- len = snprintf(buf, PAGE_SIZE, "%*pb\n",
- nr_cpu_ids, cpumask_bits(cpumask));
- mutex_unlock(&pinst->lock);
- return len < PAGE_SIZE ? len : -EINVAL;
-}
-
-static ssize_t store_cpumask(struct padata_instance *pinst,
- const struct attribute *attr,
- const char *buf, size_t count)
-{
- cpumask_var_t new_cpumask;
- ssize_t ret;
- int mask_type;
-
- if (!alloc_cpumask_var(&new_cpumask, GFP_KERNEL))
- return -ENOMEM;
-
- ret = bitmap_parse(buf, count, cpumask_bits(new_cpumask),
- nr_cpumask_bits);
- if (ret < 0)
- goto out;
-
- mask_type = !strcmp(attr->name, "serial_cpumask") ?
- PADATA_CPU_SERIAL : PADATA_CPU_PARALLEL;
- ret = padata_set_cpumask(pinst, mask_type, new_cpumask);
- if (!ret)
- ret = count;
-
-out:
- free_cpumask_var(new_cpumask);
- return ret;
-}
-
-#define PADATA_ATTR_RW(_name, _show_name, _store_name) \
- static const struct padata_sysfs_entry _name##_attr = \
- __ATTR(_name, 0644, _show_name, _store_name)
-#define PADATA_ATTR_RO(_name, _show_name) \
- static const struct padata_sysfs_entry _name##_attr = \
- __ATTR(_name, 0400, _show_name, NULL)
-
-PADATA_ATTR_RW(serial_cpumask, show_cpumask, store_cpumask);
-PADATA_ATTR_RW(parallel_cpumask, show_cpumask, store_cpumask);
-
-/*
- * Padata sysfs provides the following objects:
- * serial_cpumask [RW] - cpumask for serial workers
- * parallel_cpumask [RW] - cpumask for parallel workers
- */
-static const struct attribute *const padata_default_attrs[] = {
- &serial_cpumask_attr.attr,
- ¶llel_cpumask_attr.attr,
- NULL,
-};
-ATTRIBUTE_GROUPS(padata_default);
-
-static ssize_t padata_sysfs_show(struct kobject *kobj,
- struct attribute *attr, char *buf)
-{
- const struct padata_sysfs_entry *pentry;
- struct padata_instance *pinst;
- ssize_t ret = -EIO;
-
- pinst = kobj2pinst(kobj);
- pentry = attr2pentry(attr);
- if (pentry->show)
- ret = pentry->show(pinst, attr, buf);
-
- return ret;
-}
-
-static ssize_t padata_sysfs_store(struct kobject *kobj, struct attribute *attr,
- const char *buf, size_t count)
-{
- const struct padata_sysfs_entry *pentry;
- struct padata_instance *pinst;
- ssize_t ret = -EIO;
-
- pinst = kobj2pinst(kobj);
- pentry = attr2pentry(attr);
- if (pentry->store)
- ret = pentry->store(pinst, attr, buf, count);
-
- return ret;
-}
-
-static const struct sysfs_ops padata_sysfs_ops = {
- .show = padata_sysfs_show,
- .store = padata_sysfs_store,
-};
-
-static const struct kobj_type padata_attr_type = {
- .sysfs_ops = &padata_sysfs_ops,
- .default_groups = padata_default_groups,
- .release = padata_sysfs_release,
-};
-
-/**
- * padata_alloc - allocate and initialize a padata instance
- * @name: used to identify the instance
- *
- * Return: new instance on success, NULL on error
- */
-struct padata_instance *padata_alloc(const char *name)
-{
- struct padata_instance *pinst;
-
- pinst = kzalloc_obj(struct padata_instance);
- if (!pinst)
- goto err;
-
- pinst->parallel_wq = alloc_workqueue("%s_parallel", WQ_UNBOUND, 0,
- name);
- if (!pinst->parallel_wq)
- goto err_free_inst;
-
- cpus_read_lock();
-
- pinst->serial_wq = alloc_workqueue("%s_serial",
- WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE | WQ_PERCPU,
- 1, name);
- if (!pinst->serial_wq)
- goto err_put_cpus;
-
- if (!alloc_cpumask_var(&pinst->cpumask.pcpu, GFP_KERNEL))
- goto err_free_serial_wq;
- if (!alloc_cpumask_var(&pinst->cpumask.cbcpu, GFP_KERNEL))
- goto err_free_p_mask;
- if (!alloc_cpumask_var(&pinst->validate_cpumask, GFP_KERNEL))
- goto err_free_cb_mask;
-
- INIT_LIST_HEAD(&pinst->pslist);
-
- cpumask_copy(pinst->cpumask.pcpu, cpu_possible_mask);
- cpumask_copy(pinst->cpumask.cbcpu, cpu_possible_mask);
-
- if (padata_setup_cpumasks(pinst))
- goto err_free_v_mask;
-
- __padata_start(pinst);
-
- kobject_init(&pinst->kobj, &padata_attr_type);
- mutex_init(&pinst->lock);
-
-#ifdef CONFIG_HOTPLUG_CPU
- cpuhp_state_add_instance_nocalls_cpuslocked(hp_online,
- &pinst->cpuhp_node);
-#endif
-
- cpus_read_unlock();
-
- return pinst;
-
-err_free_v_mask:
- free_cpumask_var(pinst->validate_cpumask);
-err_free_cb_mask:
- free_cpumask_var(pinst->cpumask.cbcpu);
-err_free_p_mask:
- free_cpumask_var(pinst->cpumask.pcpu);
-err_free_serial_wq:
- destroy_workqueue(pinst->serial_wq);
-err_put_cpus:
- cpus_read_unlock();
- destroy_workqueue(pinst->parallel_wq);
-err_free_inst:
- kfree(pinst);
-err:
- return NULL;
-}
-EXPORT_SYMBOL(padata_alloc);
-
-/**
- * padata_free - free a padata instance
- *
- * @pinst: padata instance to free
- */
-void padata_free(struct padata_instance *pinst)
-{
- kobject_put(&pinst->kobj);
-}
-EXPORT_SYMBOL(padata_free);
-
-/**
- * padata_alloc_shell - Allocate and initialize padata shell.
- *
- * @pinst: Parent padata_instance object.
- *
- * Return: new shell on success, NULL on error
- */
-struct padata_shell *padata_alloc_shell(struct padata_instance *pinst)
-{
- struct parallel_data *pd;
- struct padata_shell *ps;
-
- ps = kzalloc_obj(*ps);
- if (!ps)
- goto out;
-
- ps->pinst = pinst;
-
- cpus_read_lock();
- pd = padata_alloc_pd(ps, -1);
- cpus_read_unlock();
-
- if (!pd)
- goto out_free_ps;
-
- mutex_lock(&pinst->lock);
- RCU_INIT_POINTER(ps->pd, pd);
- list_add(&ps->list, &pinst->pslist);
- mutex_unlock(&pinst->lock);
-
- return ps;
-
-out_free_ps:
- kfree(ps);
-out:
- return NULL;
-}
-EXPORT_SYMBOL(padata_alloc_shell);
-
-/**
- * padata_free_shell - free a padata shell
- *
- * @ps: padata shell to free
- */
-void padata_free_shell(struct padata_shell *ps)
-{
- struct parallel_data *pd;
-
- if (!ps)
- return;
-
- mutex_lock(&ps->pinst->lock);
- list_del(&ps->list);
- pd = rcu_dereference_protected(ps->pd, 1);
- padata_put_pd(pd);
- mutex_unlock(&ps->pinst->lock);
-
- kfree(ps);
-}
-EXPORT_SYMBOL(padata_free_shell);
-
void __init padata_init(void)
{
unsigned int i, possible_cpus;
-#ifdef CONFIG_HOTPLUG_CPU
- int ret;
-
- ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "padata:online",
- padata_cpu_online, padata_cpu_offline);
- if (ret < 0)
- goto err;
- hp_online = ret;
-#endif
possible_cpus = num_possible_cpus();
padata_works = kmalloc_objs(struct padata_work, possible_cpus);
- if (!padata_works)
- goto remove_online_state;
+ if (!padata_works) {
+ pr_warn("padata: initialization failed\n");
+ return;
+ }
for (i = 0; i < possible_cpus; ++i)
list_add(&padata_works[i].pw_list, &padata_free_works);
-
- return;
-
-remove_online_state:
-#ifdef CONFIG_HOTPLUG_CPU
- cpuhp_remove_multi_state(hp_online);
-err:
-#endif
- pr_warn("padata: initialization failed\n");
}
--
2.55.0
^ permalink raw reply related
* Re: [PATCH net] ethtool: Embed FEC hist ranges as buffer in struct
From: Eric Joyner @ 2026-07-13 22:37 UTC (permalink / raw)
To: Vadim Fedorenko, netdev
Cc: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Saeed Mahameed,
Leon Romanovsky, Tariq Toukan, Mark Bloch, Simon Horman,
Maxime Chevallier, Brett Creeley, Breno Leitao, Nikhil P. Rao
In-Reply-To: <45d2be78-4e58-445e-81b2-75dbcd15402f@linux.dev>
On 7/11/2026 2:03 PM, Vadim Fedorenko wrote:
> Caution: This message originated from an External Source. Use proper caution
> when opening attachments, clicking links, or responding.
>
>
> On 11/07/2026 00:00, Eric Joyner wrote:
>> When a driver's .get_fec_stats() handler is called and the driver
>> supports FEC histogram stats, the driver supplies the histogram bin
>> ranges via a pointer. This pointer is assigned while under the netdev
>> ops lock in fec_prepare_data(), but the actual data is only read after
>> the lock is released; so this allows the driver to change the ranges
>> (e.g. from another .get_fec_stats() call) while the current call chain
>> is reading them in fec_fill_reply().
>>
>> Fix this by embedding a buffer for the driver-supplied ranges in struct
>> ethtool_fec_hist instead of using a pointer; this ensures there's an
>> ethtool core-owned consistent copy that can be used after the netdev ops
>> lock is dropped and later in fec_fill_reply(). While some drivers like
>> bnxt use a constant struct for their ranges and won't be affected by
>> this issue, others like mlx5 (and eventually ionic) will use a
>> dynamically constructed range struct and could potentially run into an
>> issue.
>
> I didn't like the idea of dynamic range, FEC is not changing while the
> link is UP, I don't see a reason to dynamically reconstruct histogram
> bins every single call. And the histogram itself is stable per HW per
> FEC, can be constant pre-defined struct in a driver, like in bnxt.
>
> But if dynamic allocation is the only option, then yes, we have to
> change this ABI.
We can discuss this more.
I think overall drivers aren't going to need to dynamically allocate a range; I
mention ionic but at the moment I think there's only going to be two possible
FEC ranges; the sixteen bin one for RS(544,514) and I think what should be a
reduced size eight bin one for low latency RS-FEC RS(272,258) (unlike the 802.3
spec the Ethernet Consortium Spec for LL RS-FEC doesn't talk about a histogram,
but FEC math says those parameters can only correct up to 7-bit errors).
So one option could be to have the pointer be required to point to static
memory; or possibly a pre-defined histogram range entry in the kernel? I don't
see any other drivers currently combining multiple bit-error counts into one bin
and I wasn't sure if that's something the mlx5 driver actually uses, too.
OTOH, doing this dynamic range calculation should be computationally pretty
cheap overall, and provides flexibility without keeping or adding new
concurrency problems (which is an important concern!), so I don't mind the
current approach even if it does look wasteful.
- Eric
>
>> Since the kernel API changed here, change the in-tree drivers that
>> report FEC histogram stats to copy their ranges instead of just
>> supplying a pointer.
>>
>> Fixes: cc2f08129925 ("ethtool: add FEC bins histogram report")
>> Signed-off-by: Eric Joyner <eric.joyner@amd.com>
^ permalink raw reply
* Re: Ethtool is missing C2C link modes
From: Eric Joyner @ 2026-07-13 23:18 UTC (permalink / raw)
To: D H, Siddaraju, Maxime Chevallier, Andrew Lunn, Michal Kubecek,
netdev@vger.kernel.org
Cc: Chintalapalle, Balaji, Das, Shubham, Srinivasan, Vijay,
Samudrala, Sridhar, Keller, Jacob E, Nguyen, Anthony L,
singhai.anjali55@gmail.com, Brandeburg, Jesse
In-Reply-To: <SN7PR11MB6900E94E718175AACB2309109AFD2@SN7PR11MB6900.namprd11.prod.outlook.com>
On 7/10/2026 2:45 PM, D H, Siddaraju wrote:
> Hello Linux Ethernet team, Maxime, Andrew & Michal,
>
> The IEEE AUI chip-to-chip (C2C) is the accepted standard for connecting
> chips that handle subfunctions within the OSI physical layer. Just to
> pick, the C2C is widely used when connecting Ethernet SoCs with retimers
> and PCS SerDes terminated external-phys to offload PHY sublayer functions.
>
> With the existing ethtool link modes, we were not able to fit these C2C
> interfaces on any others (we fitted **SGMII interfaces to baseT link modes)
> and we see this as a gap. If you acknowledge this, we plan to send an
> RFC patch to define below listed C2C link modes to ethtool.
>
> 10G_SFI_C2C SFF-8418
> 25G_AUI_C2C IEEE 802.3 Annex 109A
> CAUI4 C2C IEEE 802.3 Annex 83D
> LAUI2-C2C IEEE 802.3 Annex 135B
> 50GAUI-1 C2C IEEE 802.3 Annex 135F
> 200GAUI-4 C2C IEEE 802.3 Annex 120D
> 100GAUI-1 C2C IEEE 802.3ck Annex 120F
> 200GAUI-2 C2C IEEE 802.3ck Clause 162
> 400GAUI-4 C2C IEEE 802.3ck Clause 163
>
> - Thank you,
> Siddaraju D H
>
I'll piggyback on this thread because I have a related question about missing
ethtool link modes: what about entries for various Active Cable types? e.g. AOC,
ACC, and AEC for the appropriate speeds.
I see some drivers (like i40e and ice) detect these and map them to SR or DA/CR
types, but I don't see them do it in a consistent pattern, either. I couldn't
find anything that suggested a precedent on how to handle these.
- Eric
^ permalink raw reply
* [PATCH net] bpf: tcp: fix double sock release on batch realloc
From: Xiang Mei (Microsoft) @ 2026-07-13 23:32 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
netdev, linux-kernel, bpf, Jordan Rife, Martin KaFai Lau,
Stanislav Fomichev, AutonomousCodeSecurity, tgopinath, kys,
Xiang Mei (Microsoft)
bpf_iter_tcp_batch() releases the current batch via
bpf_iter_tcp_put_batch(), which drops the socket refs and rewrites
each slot with the socket cookie, then grows the batch. cur_sk/end_sk
are kept for bpf_iter_tcp_resume(), but on realloc failure the function
returns ERR_PTR() before resume runs, leaving cur_sk < end_sk over
slots that now hold cookies rather than sock pointers.
bpf_iter_tcp_seq_stop() then calls bpf_iter_tcp_put_batch() again and
dereferences a cookie as a struct sock.
Empty the batch on the failure path so stop() does not release it
again. The sockets were already freed by the first
bpf_iter_tcp_put_batch(), so nothing leaks, and a later read() rescans
the bucket from the start instead of skipping it. The sibling
GFP_NOWAIT failure path still holds real socket references and is left
for stop() to release.
BUG: KASAN: null-ptr-deref in __sock_gen_cookie
Read of size 8 at addr 0000000000000059 by task exploit
...
__sock_gen_cookie (net/core/sock_diag.c:28)
bpf_iter_tcp_put_batch (net/ipv4/tcp_ipv4.c:2918)
bpf_iter_tcp_seq_stop (net/ipv4/tcp_ipv4.c:3270)
bpf_seq_read (kernel/bpf/bpf_iter.c:205)
vfs_read (fs/read_write.c:572)
ksys_read (fs/read_write.c:716)
do_syscall_64
entry_SYSCALL_64_after_hwframe
Kernel panic - not syncing: Fatal exception
Fixes: cdec67a489d4 ("bpf: tcp: Make sure iter->batch always contains a full bucket snapshot")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
---
net/ipv4/tcp_ipv4.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 209ef7522508..dd3ed62704f9 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -3141,8 +3141,11 @@ static struct sock *bpf_iter_tcp_batch(struct seq_file *seq)
bpf_iter_tcp_put_batch(iter);
err = bpf_iter_tcp_realloc_batch(iter, expected * 3 / 2,
GFP_USER);
- if (err)
+ if (err) {
+ iter->cur_sk = 0;
+ iter->end_sk = 0;
return ERR_PTR(err);
+ }
sk = bpf_iter_tcp_resume(seq);
if (!sk)
--
2.43.0
^ permalink raw reply related
* [BUG] nfc: llcp: race between nfc_llcp_send_ui_frame() and llcp_sock_bind() dereferences NULL sock->dev
From: Junwoong Doh @ 2026-07-14 0:00 UTC (permalink / raw)
To: david
Cc: krzk, davem, edumazet, kuba, pabeni, horms, oe-linux-nfc, netdev,
linux-kernel, jdoh.kernel
Hello,
Commit dded08927ca3 ("nfc: llcp: fix NULL error pointer dereference on
sendmsg() after failed bind()") added a NULL check for llcp_sock->local
in llcp_sock_sendmsg(), but it does not handle all the races.
The thread interleaving is the same as Krzysztof mentioned:
https://lore.kernel.org/oe-linux-nfc/20220119074816.6505-2-krzysztof.kozlowski@canonical.com/
In detail:
nfc_llcp_send_ui_frame() checks sock->local == NULL, but it is called
without socket's lock held, which opens a window for a race condition.
Between the sock->local == NULL check and the sock->dev use in
nfc_alloc_send_skb(), llcp_sock_bind() can run concurrently and set
both sock->local and sock->dev to NULL.
This leads to NULL pointer dereference in the nfc_alloc_send_skb() call.
Moreover, the window can be enlarged by the
memcpy_from_msg(msg_data, msg, len) call that sits between
the sock->local check and the sock->dev use.
I used /dev/virtual_nci to set up a virtual NFC device,
so the reproducer must be executed as root:
gcc repro.c -o repro -static -lpthread
./repro
Reproduced crash log:
==================================================================
[ 46.787552][ T9432] Oops: general protection fault, probably for non-canonical address 0xdffffc00000000b4: 0000 [#1] SMP KASAN NOPTI
[ 46.787982][ T9432] KASAN: null-ptr-deref in range [0x00000000000005a0-0x00000000000005a7]
[ 46.788264][ T9432] CPU: 0 UID: 0 PID: 9432 Comm: minimize Not tainted 7.2.0-rc3 #3 PREEMPT(full)
[ 46.788565][ T9432] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[ 46.788869][ T9432] RIP: 0010:nfc_alloc_send_skb+0x3e/0x1a0
[ 46.789080][ T9432] Code: 54 41 89 d4 55 53 48 89 fb 48 8d ab a0 05 00 00 48 83 ec 08 e8 b3 d3 79 f6 48 89 ea 48 b8 00 00 00 00 00 fc ff df 48 c1 ea 03 <0f> b6 04 02 84 c0 74 08 3c 03 0f 8e 1d 01 00 00 48 8d bb a4 05 00
[ 46.789707][ T9432] RSP: 0018:ffffc90003c1f9a8 EFLAGS: 00010216
[ 46.789909][ T9432] RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000000082
[ 46.790164][ T9432] RDX: 00000000000000b4 RSI: ffffffff8b46300d RDI: 0000000000000000
[ 46.790418][ T9432] RBP: 00000000000005a0 R08: ffffc90003c1fa70 R09: ffffed100f11e9ff
[ 46.790672][ T9432] R10: 0000000000000880 R11: 0000000000000000 R12: 0000000000000000
[ 46.790928][ T9432] R13: 0000000000000082 R14: ffff88802c144000 R15: ffffc90003c1fa70
[ 46.791184][ T9432] FS: 00007f788883d640(0000) GS:ffff888124680000(0000) knlGS:0000000000000000
[ 46.791470][ T9432] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 46.791684][ T9432] CR2: 00000000004b0270 CR3: 00000000287d4000 CR4: 00000000000006f0
[ 46.791942][ T9432] Call Trace:
[ 46.792052][ T9432] <TASK>
[ 46.792150][ T9432] nfc_llcp_send_ui_frame+0x412/0x690
[ 46.792331][ T9432] ? __pfx_nfc_llcp_send_ui_frame+0x10/0x10
[ 46.792526][ T9432] ? llcp_sock_sendmsg+0x300/0x480
[ 46.792696][ T9432] ? __local_bh_enable_ip+0xa4/0x120
[ 46.792877][ T9432] llcp_sock_sendmsg+0x364/0x480
[ 46.793042][ T9432] ? bpf_lsm_socket_sendmsg+0x9/0x10
[ 46.793217][ T9432] ? __pfx_llcp_sock_sendmsg+0x10/0x10
[ 46.793398][ T9432] ____sys_sendmsg+0xa27/0xb90
[ 46.793560][ T9432] ? __pfx_____sys_sendmsg+0x10/0x10
[ 46.793734][ T9432] ? __pfx_copy_msghdr_from_user+0x10/0x10
[ 46.793929][ T9432] ? find_held_lock+0x2b/0x80
[ 46.794085][ T9432] ? clockevents_program_event+0x27c/0x990
[ 46.794278][ T9432] ___sys_sendmsg+0x11c/0x1b0
[ 46.794436][ T9432] ? __pfx____sys_sendmsg+0x10/0x10
[ 46.794611][ T9432] ? __fget_files+0x1fb/0x3b0
[ 46.794770][ T9432] __sys_sendmsg+0x142/0x1f0
[ 46.794926][ T9432] ? __pfx___sys_sendmsg+0x10/0x10
[ 46.795097][ T9432] ? __cpu_to_node+0x8a/0x130
[ 46.795256][ T9432] do_syscall_64+0x11f/0x830
[ 46.795414][ T9432] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 46.795608][ T9432] RIP: 0033:0x453cfd
[ 46.795738][ T9432] Code: 28 89 54 24 1c 48 89 74 24 10 89 7c 24 08 e8 6a 86 02 00 8b 54 24 1c 48 8b 74 24 10 41 89 c0 8b 7c 24 08 b8 2e 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 33 44 89 c7 48 89 44 24 08 e8 ae 86 02 00 48
[ 46.796354][ T9432] RSP: 002b:00007f788883d0d0 EFLAGS: 00000293 ORIG_RAX: 000000000000002e
[ 46.796623][ T9432] RAX: ffffffffffffffda RBX: 00007f788883d640 RCX: 0000000000453cfd
[ 46.796879][ T9432] RDX: 0000000000000000 RSI: 00007f788883d120 RDI: 0000000000000006
[ 46.797133][ T9432] RBP: 00007f788883d1d0 R08: 0000000000000000 R09: 00007ffe3551378f
[ 46.797387][ T9432] R10: 0000000000000008 R11: 0000000000000293 R12: 00007f788883d640
[ 46.797641][ T9432] R13: 0000000000000000 R14: 0000000000415330 R15: 00007f788803d000
[ 46.797912][ T9432] </TASK>
[ 46.798017][ T9432] Modules linked in:
[ 46.798231][ T9432] ---[ end trace 0000000000000000 ]---
[ 46.813550][ T9432] RIP: 0010:nfc_alloc_send_skb+0x3e/0x1a0
[ 46.813783][ T9432] Code: 54 41 89 d4 55 53 48 89 fb 48 8d ab a0 05 00 00 48 83 ec 08 e8 b3 d3 79 f6 48 89 ea 48 b8 00 00 00 00 00 fc ff df 48 c1 ea 03 <0f> b6 04 02 84 c0 74 08 3c 03 0f 8e 1d 01 00 00 48 8d bb a4 05 00
[ 46.814427][ T9432] RSP: 0018:ffffc90003c1f9a8 EFLAGS: 00010216
[ 46.814632][ T9432] RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000000082
[ 46.814900][ T9432] RDX: 00000000000000b4 RSI: ffffffff8b46300d RDI: 0000000000000000
[ 46.815161][ T9432] RBP: 00000000000005a0 R08: ffffc90003c1fa70 R09: ffffed100f11e9ff
[ 46.815423][ T9432] R10: 0000000000000880 R11: 0000000000000000 R12: 0000000000000000
[ 46.815684][ T9432] R13: 0000000000000082 R14: ffff88802c144000 R15: ffffc90003c1fa70
[ 46.815948][ T9432] FS: 00007f788883d640(0000) GS:ffff888124680000(0000) knlGS:0000000000000000
[ 46.816243][ T9432] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 46.816462][ T9432] CR2: 00007f871e9f90a0 CR3: 00000000287d4000 CR4: 00000000000006f0
[ 46.816725][ T9432] Kernel panic - not syncing: Fatal exception
[ 46.817025][ T9432] Kernel Offset: disabled
[ 46.817173][ T9432] Rebooting in 86400 seconds..
==================================================================
C reproducer:
==================================================================
#define _GNU_SOURCE
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <linux/nfc.h>
#include <linux/userfaultfd.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define IOCTL_GET_NCIDEV_IDX 0
static void fatal(const char *m) {
perror(m);
exit(1);
}
static int new_llcp_sock(void) {
int s = socket(AF_NFC, SOCK_DGRAM, NFC_SOCKPROTO_LLCP);
if (s < 0) {
fatal("socket");
}
return s;
}
static int do_bind(int s, uint32_t devidx, const char *name) {
struct sockaddr_nfc_llcp a = {
.sa_family = AF_NFC,
.dev_idx = devidx,
.dsap = 0,
.ssap = 1
};
strncpy(a.service_name, name, sizeof(a.service_name));
a.service_name_len = strlen(name);
return bind(s, (struct sockaddr *)&a, sizeof(a));
}
static int open_dev(uint32_t *idx) {
int fd = open("/dev/virtual_nci", O_RDWR);
if (fd < 0) {
fatal("open /dev/virtual_nci");
}
if (ioctl(fd, IOCTL_GET_NCIDEV_IDX, idx) < 0) {
fatal("GET_NCIDEV_IDX");
}
return fd;
}
static int victim;
static void *payload;
static void *sender(void *arg) {
struct iovec iov = {
.iov_base = payload,
.iov_len = 0x1000
};
struct sockaddr_nfc_llcp vdest = {
.sa_family = AF_NFC,
.dsap = 1,
.ssap = 6
};
struct msghdr msg = {
.msg_name = &vdest,
.msg_namelen = sizeof(vdest),
.msg_iov = &iov,
.msg_iovlen = 1
};
sendmsg(victim, &msg, 0);
return NULL;
}
int init_userfaultfd() {
int uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd < 0) {
fatal("userfaultfd");
}
struct uffdio_api api = {
.api = UFFD_API
};
if (ioctl(uffd, UFFDIO_API, &api) < 0) {
fatal("UFFDIO_API");
}
payload = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (payload == MAP_FAILED) {
fatal("mmap");
}
struct uffdio_register reg = {
.range = {
.start = (unsigned long)payload,
.len = 0x1000
},
.mode = UFFDIO_REGISTER_MODE_MISSING,
};
if (ioctl(uffd, UFFDIO_REGISTER, ®) < 0) {
fatal("UFFDIO_REGISTER");
}
return uffd;
}
int main(void) {
uint32_t idx_live, idx_kill;
int fd_live = open_dev(&idx_live);
int helper = new_llcp_sock();
if (do_bind(helper, idx_live, "collide") < 0) {
fatal("helper bind");
}
int fd_kill = open_dev(&idx_kill);
victim = new_llcp_sock();
if (do_bind(victim, idx_kill, "victim") < 0) {
fatal("victim bind");
}
int uffd = init_userfaultfd();
pthread_t th;
if (pthread_create(&th, NULL, sender, payload)) {
fatal("pthread_create");
}
// Execute after page fault raised
struct pollfd pfd = {
.fd = uffd,
.events = POLLIN
};
if (poll(&pfd, 1, -1) < 0) {
fatal("poll");
}
struct uffd_msg um;
if (read(uffd, &um, sizeof(um)) != sizeof(um)) {
fatal("read uffd");
}
close(fd_kill);
do_bind(victim, idx_live, "collide");
struct uffdio_zeropage zp = {
.range = {
.start = payload,
.len = 0x1000,
},
};
ioctl(uffd, UFFDIO_ZEROPAGE, &zp); // Resume sendmsg()
return 0;
}
==================================================================
Kernel commit: a13c140cc289c0b7b3770bce5b3ad42ab35074aa (v7.2-rc3)
Config: https://gist.githubusercontent.com/dandb3/199109d95bd174cb8bd446ed01055861/raw/6889acdc95db08c35d7c5d3288e352da8edaa775/.config
Thanks,
Junwoong
^ permalink raw reply
* Re: [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
From: Johan Alvarado @ 2026-07-14 0:24 UTC (permalink / raw)
To: Mieczyslaw Nalewaj
Cc: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, kuncy7, netdev, linux-kernel
In-Reply-To: <7b164d90-b57f-4746-8b7f-b5bb7fd7fe51@yahoo.com>
Hi Mieczyslaw,
On 7/12/2026 8:05 PM, Mieczyslaw Nalewaj wrote:
[...]
> As discussed earlier in the thread: the rate limiter helper, as well
> as pcs_config()/RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, currently assume
> the SerDes is always muxed to external interface 1 / port 6
> (RTL8365MB_SDS_EXT_INTERFACE_ID / _PORT). That's true for every chip
> currently in rtl8365mb_chip_infos[] (RTL8367S and RTL8367SB both have
> their SGMII/HSGMII-capable extint at { 6, 1, ... }), so nothing is
> broken today.
>
> I still think it's worth guarding against this ahead of time though.
> It isn't an architectural constant of the family, just something that
> happens to hold for the two chips currently in the table, and there's
> no guarantee a future chip won't mux its SerDes to a different port
> or extint id.
I see it the other way around: for the family this driver covers,
the port 6 muxing is architectural. Every entry in
rtl8365mb_chip_infos[] is chip id 0x6367, and on this silicon the
SerDes mux is a pair of bits in SDS_MISC that select SGMII or HSGMII
specifically for MAC8 - that is what the
RTL8365MB_SDS_MISC_MAC8_SEL_* names in the driver reflect. Neither
the register set nor the vendor driver offers a way to route the
SerDes anywhere else on this chip id. A chip that muxes its SerDes
to a different interface would not be a new table entry; it would be
different silicon with a different SerDes datapath.
Your draft illustrates this, I think: it makes the
DIGITAL_INTERFACE_SELECT write follow mb->sds_id, but the SerDes mux
write in pcs_config() still sets the MAC8_SEL bits unconditionally,
because there is nothing else it could set. On the hypothetical
future chip, the generalized path would program the interface mode
for the cached id while still muxing the SerDes to MAC8 - as broken
as before, only harder to see, because the code now looks as if it
handled the case.
The bypass mask has the same problem on a different axis. As came up
in the v4 review, the bit layout of the line rate bypass register is
not a function of the port number across the wider RTL8367 family;
the BIT(port - 5) relationship in RTL8365MB_BYPASS_LINE_RATE_MASK()
is the layout of this chip id specifically. The same goes for the
SerDes tuning tables, the chip option probe and the rate limiter
register addresses: all of them are tied to this silicon, not
derivable from the extint table. Caching the extint port and id
would generalize the two easiest parameters and leave every hard one
behind.
> Since rtl8365mb_sds_probe_option() already walks
> chip_info->extints[] looking for the SGMII/HSGMII-capable one, I'd
> like to have it cache the discovered port and id (mb->sds_port /
> mb->sds_id) instead of relying on the fixed
> RTL8365MB_SDS_EXT_INTERFACE_ID/_PORT. pcs_config() and the bypass
> line-rate mask would then use the cached values, and
> rtl8365mb_sds_raise_rate_limits() would check mb->sds_port == 6 and
> warn+skip instead of assuming, since only the port 6 register
> addresses are known/verified so far.
The warn+skip in particular I think would be actively harmful:
skipping the write means that hypothetical chip ships with the
reset-default ~1.048 Gbps cap - the exact problem this patch exists
to fix, reduced to a one-line boot warning. The MR85X report showed
how easy that cap is to miss; it took multi-client throughput
testing to even notice it. I would rather keep the assumption a
hard, greppable constant next to a comment stating it, so that
whoever adds such a chip trips over it while writing the chip_info
entry and brings the SerDes path up against real hardware - which
they will have to do anyway, for the mux, the tuning tables, the
option probe and the limiter addresses alike.
So I would prefer to keep the series as is. If a chip with the
SerDes on another interface ever appears, its submitter will have a
datasheet or vendor code in hand and can parametrize exactly what
that silicon needs, with values that can be verified. Doing it now
would generalize the two parameters we can test and guess at the
rest.
Best regards,
Johan
^ permalink raw reply
* [PATCH net-next v2 0/4] net: dsa: mxl862xx: support firmware update
From: Daniel Golle @ 2026-07-14 0:50 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
The firmware of MxL862xx managed Ethernet switches can be updated
in-system via the same MDIO bus which is also used to manage the
switch. Wire up the devlink flash_update operation for DSA drivers
and implement firmware update and version reporting in the mxl862xx
driver.
Changes since RFC [1]:
- detect a switch stuck in MCUboot rescue mode at probe, register
the switch without any ports and report "mcuboot-rescue" as the
running firmware version, so devlink flash can recover from a
failed or interrupted update (Andrew Lunn)
- clarify in the commit message of patch 2 that the per-transaction
MDIO bus locking is about other, non-switch devices on the same
MDIO bus (Andrew Lunn)
- mention in the commit message of patch 3 that closing the ports
also stops phylib from polling the switch-internal PHYs during
the transfer (Andrew Lunn)
- split up run-on sentence and explain the dynamically allocated
reprobe work item instead of just pointing at iwlwifi in the
commit message of patch 3 (Manuel Ebner)
- use kzalloc_obj() (Manuel Ebner)
- state the actual duration of a complete flash and reprobe cycle
(just under a minute) in comments and the commit message, and
clarify that the timeout values are generous upper bounds
(Manuel Ebner)
[1] https://lore.kernel.org/all/ak0J-HgzMRea53om@makrotopia.org/
Daniel Golle (4):
net: dsa: wire flash_update devlink callback to drivers
net: dsa: mxl862xx: add SMDIO clause-22 register access
net: dsa: mxl862xx: add devlink flash_update and info_get
net: dsa: mxl862xx: recover switch stuck in MCUboot rescue mode
drivers/net/dsa/mxl862xx/Makefile | 2 +-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 1 +
drivers/net/dsa/mxl862xx/mxl862xx-fw.c | 439 ++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-fw.h | 18 +
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 45 ++
drivers/net/dsa/mxl862xx/mxl862xx-host.h | 2 +
drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 2 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 41 +-
drivers/net/dsa/mxl862xx/mxl862xx.h | 9 +
include/net/dsa.h | 3 +
net/dsa/devlink.c | 13 +
11 files changed, 571 insertions(+), 4 deletions(-)
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.c
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.h
base-commit: f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
--
2.55.0
^ permalink raw reply
* [PATCH net-next v2 1/4] net: dsa: wire flash_update devlink callback to drivers
From: Daniel Golle @ 2026-07-14 0:50 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
In-Reply-To: <cover.1783988826.git.daniel@makrotopia.org>
Add a devlink_flash_update callback to dsa_switch_ops so that DSA
drivers can support devlink dev flash without open-coding the devlink
plumbing. The new trampoline in net/dsa/devlink.c follows the existing
dsa_devlink_info_get pattern exactly.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v2: align continuation lines with the open parenthesis
include/net/dsa.h | 3 +++
net/dsa/devlink.c | 13 +++++++++++++
2 files changed, 16 insertions(+)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc10..c9e19348de61 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -1170,6 +1170,9 @@ struct dsa_switch_ops {
int (*devlink_info_get)(struct dsa_switch *ds,
struct devlink_info_req *req,
struct netlink_ext_ack *extack);
+ int (*devlink_flash_update)(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack);
int (*devlink_sb_pool_get)(struct dsa_switch *ds,
unsigned int sb_index, u16 pool_index,
struct devlink_sb_pool_info *pool_info);
diff --git a/net/dsa/devlink.c b/net/dsa/devlink.c
index ed342f345692..25311a87cbc5 100644
--- a/net/dsa/devlink.c
+++ b/net/dsa/devlink.c
@@ -20,6 +20,18 @@ static int dsa_devlink_info_get(struct devlink *dl,
return -EOPNOTSUPP;
}
+static int dsa_devlink_flash_update(struct devlink *dl,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack)
+{
+ struct dsa_switch *ds = dsa_devlink_to_ds(dl);
+
+ if (!ds->ops->devlink_flash_update)
+ return -EOPNOTSUPP;
+
+ return ds->ops->devlink_flash_update(ds, params, extack);
+}
+
static int dsa_devlink_sb_pool_get(struct devlink *dl,
unsigned int sb_index, u16 pool_index,
struct devlink_sb_pool_info *pool_info)
@@ -169,6 +181,7 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,
static const struct devlink_ops dsa_devlink_ops = {
.info_get = dsa_devlink_info_get,
+ .flash_update = dsa_devlink_flash_update,
.sb_pool_get = dsa_devlink_sb_pool_get,
.sb_pool_set = dsa_devlink_sb_pool_set,
.sb_port_pool_get = dsa_devlink_sb_port_pool_get,
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v2 2/4] net: dsa: mxl862xx: add SMDIO clause-22 register access
From: Daniel Golle @ 2026-07-14 0:50 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
In-Reply-To: <cover.1783988826.git.daniel@makrotopia.org>
Add mxl862xx_smdio_read() and mxl862xx_smdio_write() for clause-22
SMDIO register access. MCUboot rescue mode only exposes clause-22
registers; the existing clause-45 MMD interface is unavailable during
firmware transfer. The MDIO bus lock is held per-transaction (not
across polls) so that SB PDI polling during flash erase does not
starve other non-switch users of the same MDIO bus, such as separate
PHYs providing WAN or management interfaces.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v2: clarify in the commit message that the per-transaction bus locking
is about unrelated non-switch devices on the same MDIO bus
(Andrew Lunn)
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 35 ++++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-host.h | 2 ++
2 files changed, 37 insertions(+)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 4acd216f7cc0..6e582caea1fa 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -495,6 +495,41 @@ int mxl862xx_reset(struct mxl862xx_priv *priv)
return ret;
}
+#define MXL862XX_SMDIO_ADDR_REG 0x1f
+#define MXL862XX_SMDIO_PAGE_MASK 0xfff0
+#define MXL862XX_SMDIO_OFF_MASK 0x000f
+
+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr)
+{
+ struct mii_bus *bus = priv->mdiodev->bus;
+ int phy = priv->mdiodev->addr;
+ int ret;
+
+ mutex_lock(&bus->mdio_lock);
+ ret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,
+ addr & MXL862XX_SMDIO_PAGE_MASK);
+ if (ret >= 0)
+ ret = __mdiobus_read(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK);
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val)
+{
+ struct mii_bus *bus = priv->mdiodev->bus;
+ int phy = priv->mdiodev->addr;
+ int ret;
+
+ mutex_lock(&bus->mdio_lock);
+ ret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,
+ addr & MXL862XX_SMDIO_PAGE_MASK);
+ if (ret >= 0)
+ ret = __mdiobus_write(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK,
+ val);
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
void mxl862xx_host_init(struct mxl862xx_priv *priv)
{
INIT_WORK(&priv->crc_err_work, mxl862xx_crc_err_work_fn);
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
index 66d6ae198aff..4e054c6e4c0e 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
@@ -18,5 +18,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
int mxl862xx_reset(struct mxl862xx_priv *priv);
+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr);
+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val);
#endif /* __MXL862XX_HOST_H */
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v2 3/4] net: dsa: mxl862xx: add devlink flash_update and info_get
From: Daniel Golle @ 2026-07-14 0:51 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
In-Reply-To: <cover.1783988826.git.daniel@makrotopia.org>
Implement runtime firmware upgrade via "devlink dev flash" and version
reporting via "devlink dev info":
devlink dev info mdio_bus/<bus>/<addr>
devlink dev flash mdio_bus/<bus>/<addr> file <firmware.bin>
The driver sends SYS_MISC_FW_UPDATE to enter MCUboot rescue mode and
transfers the signed image over the SB PDI bulk-transfer protocol
(clause-22 SMDIO). Once the transfer has finished and the switch has
rebooted, the driver schedules device_reprobe() for a clean
remove()+probe() cycle.
Before the transfer begins the driver closes all conduit interfaces
and marks every netdev (user and conduit) not-present via
netif_device_detach() so that userspace cannot bring ports back up
during the flash and reprobe cycle, which takes just under a minute.
Closing the ports also stops phylib from polling the switch-internal
PHYs, whose firmware-relayed MDIO access is unavailable while the
switch is in MCUboot mode. Progress is reported through devlink
status notifications. Once the FW_UPDATE command has been sent the
switch is in MCUboot mode and normal operation can only be restored
by a reprobe, so the driver always schedules one regardless of
transfer outcome.
The reprobe work item is dynamically allocated because
device_reprobe() triggers remove() which frees the devm-managed priv
while the work item is still executing, so the work struct cannot be
a member of priv. iwlwifi uses the same approach for its
firmware-triggered device removal, see iwl_trans_pcie_removal_wk().
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v2:
- factor out SB PDI slice flush and devlink status notification
helpers, resolving checkpatch issues
- use kzalloc_obj() (Manuel Ebner)
- add kernel-doc for the new mxl862xx_priv members
- trim comments and state the actual duration of a flash and reprobe
cycle, just under a minute (Manuel Ebner)
- reword commit message: split up run-on sentence, explain the
dynamically allocated reprobe work item (Manuel Ebner), mention
that closing the ports stops phylib polling (Andrew Lunn)
drivers/net/dsa/mxl862xx/Makefile | 2 +-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 1 +
drivers/net/dsa/mxl862xx/mxl862xx-fw.c | 410 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-fw.h | 15 +
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 7 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 3 +
drivers/net/dsa/mxl862xx/mxl862xx.h | 6 +
7 files changed, 443 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.c
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.h
diff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile
index a7be0e6669df..bccac0d0f703 100644
--- a/drivers/net/dsa/mxl862xx/Makefile
+++ b/drivers/net/dsa/mxl862xx/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
+mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index c87a955c13c4..e2aa2934e9e1 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -70,6 +70,7 @@
#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x2)
+#define SYS_MISC_FW_UPDATE (SYS_MISC_MAGIC + 0x1)
#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x2)
#define MXL862XX_XPCS_PCS_CONFIG (MXL862XX_XPCS_MAGIC + 0x1)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
new file mode 100644
index 000000000000..b2c23ccf1370
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Firmware flash and devlink support for MaxLinear MxL862xx
+ *
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/rtnetlink.h>
+#include <net/dsa.h>
+
+#include "mxl862xx.h"
+#include "mxl862xx-api.h"
+#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
+#include "mxl862xx-host.h"
+
+/* SB PDI registers (clause-22 SMDIO address space) */
+#define MXL862XX_SB_PDI_CTRL 0xe100
+#define MXL862XX_SB_PDI_ADDR 0xe101
+#define MXL862XX_SB_PDI_DATA 0xe102
+#define MXL862XX_SB_PDI_STAT 0xe103
+
+/* SB PDI CTRL modes */
+#define MXL862XX_SB_PDI_CTRL_RST 0x00
+#define MXL862XX_SB_PDI_CTRL_WR 0x02
+
+/* SB PDI handshake magic */
+#define MXL862XX_SB_PDI_READY 0xc55c
+#define MXL862XX_SB_PDI_START 0xf48f
+#define MXL862XX_SB_PDI_END 0x3cc3
+
+/* Firmware transfer geometry */
+#define MXL862XX_FW_HDR_SIZE 20
+#define MXL862XX_FW_BANK_HALF 16384 /* words per half-bank */
+#define MXL862XX_FW_BANK_SLICE 32760 /* words per full slice */
+#define MXL862XX_FW_SB1_ADDR 0x7800 /* SB1 word address */
+
+/* Timeouts (generous upper bounds) */
+#define MXL862XX_FW_READY_TIMEOUT_MS 30000
+#define MXL862XX_FW_ACK_TIMEOUT_MS 5000
+#define MXL862XX_FW_ERASE_TIMEOUT_MS 300000
+#define MXL862XX_FW_WRITE_TIMEOUT_MS 120000
+#define MXL862XX_FW_REBOOT_DELAY_MS 5000
+
+static void mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)
+{
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_RST);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+ MXL862XX_SB_PDI_CTRL_RST);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
+ MXL862XX_SB_PDI_CTRL_RST);
+}
+
+static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expected,
+ unsigned long timeout_ms)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
+ int ret;
+
+ do {
+ ret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
+ if (ret < 0)
+ return ret;
+ if ((u16)ret == expected)
+ return 0;
+ usleep_range(10000, 11000);
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+static int mxl862xx_sb_pdi_flush_slice(struct mxl862xx_priv *priv,
+ u32 data_written)
+{
+ mxl862xx_sb_pdi_reset(priv);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT, data_written);
+
+ return mxl862xx_sb_pdi_poll_stat(priv, 0,
+ MXL862XX_FW_WRITE_TIMEOUT_MS);
+}
+
+static void mxl862xx_flash_notify(struct devlink *dl, const char *status,
+ u32 done, u32 total)
+{
+ devlink_flash_update_status_notify(dl, status, NULL, done, total);
+}
+
+/* device_reprobe() -> remove() frees priv while the work runs, so
+ * the work struct cannot live in mxl862xx_priv.
+ */
+struct mxl862xx_reprobe {
+ struct device *dev;
+ struct delayed_work dwork;
+};
+
+static void mxl862xx_reprobe_work_fn(struct work_struct *work)
+{
+ struct mxl862xx_reprobe *reprobe =
+ container_of(work, struct mxl862xx_reprobe, dwork.work);
+
+ if (device_reprobe(reprobe->dev))
+ dev_err(reprobe->dev, "reprobe failed\n");
+ put_device(reprobe->dev);
+ kfree(reprobe);
+ module_put(THIS_MODULE);
+}
+
+/* MCUboot firmware image header */
+struct mxl862xx_fw_hdr {
+ __le32 image_type;
+ __le32 image_size_1;
+ __le32 image_checksum_1;
+ __le32 image_size_2;
+ __le32 image_checksum_2;
+} __packed;
+
+static int mxl862xx_flash_firmware(struct mxl862xx_priv *priv,
+ const struct firmware *fw,
+ struct devlink *dl)
+{
+ const struct mxl862xx_fw_hdr *hdr;
+ u32 word_idx = 0, data_written = 0, idx = 0;
+ unsigned long next_notify = 0;
+ const u8 *payload;
+ u32 payload_size;
+ u16 word, fdata;
+ int ret, i;
+ u32 crc;
+
+ if (fw->size < MXL862XX_FW_HDR_SIZE)
+ return -EINVAL;
+
+ hdr = (const struct mxl862xx_fw_hdr *)fw->data;
+ payload = fw->data + MXL862XX_FW_HDR_SIZE;
+ payload_size = le32_to_cpu(hdr->image_size_1) +
+ le32_to_cpu(hdr->image_size_2);
+
+ if (payload_size > fw->size - MXL862XX_FW_HDR_SIZE) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: firmware file too small for declared size\n");
+ return -EINVAL;
+ }
+
+ if (le32_to_cpu(hdr->image_size_1)) {
+ crc = ~crc32_le(~0U, payload,
+ le32_to_cpu(hdr->image_size_1));
+ if (crc != le32_to_cpu(hdr->image_checksum_1)) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: image 1 CRC mismatch (got %08x, expected %08x)\n",
+ crc, le32_to_cpu(hdr->image_checksum_1));
+ return -EINVAL;
+ }
+ }
+
+ if (le32_to_cpu(hdr->image_size_2)) {
+ crc = ~crc32_le(~0U,
+ payload + le32_to_cpu(hdr->image_size_1),
+ le32_to_cpu(hdr->image_size_2));
+ if (crc != le32_to_cpu(hdr->image_checksum_2)) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: image 2 CRC mismatch (got %08x, expected %08x)\n",
+ crc, le32_to_cpu(hdr->image_checksum_2));
+ return -EINVAL;
+ }
+ }
+
+ /* Step 1: reboot the firmware into MCUboot rescue mode */
+ ret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,
+ false, false);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: FW_UPDATE command failed: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ /* Failures from here on must go through end_magic so MCUboot
+ * reboots instead of waiting forever.
+ */
+
+ /* Step 2: wait for bootloader ready */
+ mxl862xx_flash_notify(dl, "Waiting for bootloader", 0, 0);
+ mxl862xx_sb_pdi_reset(priv);
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
+ MXL862XX_FW_READY_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: bootloader not ready: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 3: start handshake */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_START);
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,
+ MXL862XX_FW_ACK_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: start handshake failed: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 4: transfer image header */
+ mxl862xx_flash_notify(dl, "Erasing flash", 0, 0);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+ for (i = 0; i < MXL862XX_FW_HDR_SIZE / 2; i++) {
+ word = fw->data[i * 2] |
+ ((u16)fw->data[i * 2 + 1] << 8);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);
+ }
+ mxl862xx_sb_pdi_reset(priv);
+
+ /* the byte count in STAT triggers the erase */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_FW_HDR_SIZE);
+
+ /* ACK is byte count + 1 */
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,
+ MXL862XX_FW_ACK_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: header ACK failed: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 5: wait for erase to complete */
+ ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
+ MXL862XX_FW_ERASE_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: erase timeout: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 6: transfer payload */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+
+ while (idx < payload_size) {
+ if (idx + 1 < payload_size) {
+ fdata = payload[idx] |
+ ((u16)payload[idx + 1] << 8);
+ idx += 2;
+ data_written += 2;
+ } else {
+ fdata = payload[idx];
+ idx++;
+ data_written++;
+ }
+
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata);
+ word_idx++;
+
+ if (idx >= payload_size) {
+ ret = mxl862xx_sb_pdi_flush_slice(priv, data_written);
+ break;
+ }
+
+ /* Half-bank boundary: switch to SB1 address */
+ if (word_idx == MXL862XX_FW_BANK_HALF) {
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_RST);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+ MXL862XX_FW_SB1_ADDR);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+ } else if (word_idx >= MXL862XX_FW_BANK_SLICE) {
+ ret = mxl862xx_sb_pdi_flush_slice(priv, data_written);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: write timeout at %u/%u: %pe\n",
+ idx, payload_size, ERR_PTR(ret));
+ goto end_magic;
+ }
+ word_idx = 0;
+ data_written = 0;
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+
+ if (time_after(jiffies, next_notify)) {
+ mxl862xx_flash_notify(dl, "Flashing", idx,
+ payload_size);
+ next_notify = jiffies + msecs_to_jiffies(500);
+ }
+ }
+ }
+
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: final write timeout: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ mxl862xx_flash_notify(dl, "Flashing", payload_size, payload_size);
+
+end_magic:
+ /* reboot MCUboot even after a failed transfer */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_END);
+ msleep(MXL862XX_FW_REBOOT_DELAY_MS);
+
+ return ret;
+}
+
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ const char *compatible;
+ char ver_str[32];
+ int ret;
+
+ if (!of_property_read_string_index(ds->dev->of_node, "compatible", 0,
+ &compatible)) {
+ ret = devlink_info_version_fixed_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
+ compatible);
+ if (ret)
+ return ret;
+ }
+
+ snprintf(ver_str, sizeof(ver_str), "%u.%u.%u",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+
+ return devlink_info_version_running_put(req, "fw", ver_str);
+}
+
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_sys_fw_image_version ver = {};
+ struct mxl862xx_reprobe *reprobe;
+ struct dsa_port *dp;
+ int ret, i;
+
+ if (params->component) {
+ NL_SET_ERR_MSG_MOD(extack, "component is not supported");
+ return -EOPNOTSUPP;
+ }
+
+ dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+
+ /* Close ports while the firmware is still alive so the DSA
+ * core's MDB/FDB tracking is drained, and detach user ports
+ * so userspace cannot reopen them during the flash. The
+ * conduit belongs to the MAC driver and is only closed.
+ */
+ rtnl_lock();
+ dsa_switch_for_each_user_port(dp, ds) {
+ if (dp->user) {
+ dev_close(dp->user);
+ netif_device_detach(dp->user);
+ }
+ }
+ dsa_switch_for_each_cpu_port(dp, ds)
+ dev_close(dp->conduit);
+ rtnl_unlock();
+
+ priv->block_host = true;
+
+ cancel_delayed_work_sync(&priv->stats_work);
+ for (i = 0; i < ds->num_ports; i++)
+ cancel_work_sync(&priv->ports[i].host_flood_work);
+
+ ret = mxl862xx_flash_firmware(priv, params->fw, ds->devlink);
+ if (ret)
+ NL_SET_ERR_MSG_MOD(extack, "firmware transfer failed");
+
+ if (!ret) {
+ /* lift the block to query the new firmware version */
+ priv->block_host = false;
+ memset(&ver, 0, sizeof(ver));
+ if (!MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver) &&
+ ver.iv_major)
+ dev_info(ds->dev, "flash: new firmware %u.%u.%u\n",
+ ver.iv_major, ver.iv_minor,
+ le16_to_cpu(ver.iv_revision));
+ }
+
+ priv->skip_teardown = true;
+
+ reprobe = kzalloc_obj(*reprobe);
+ if (!reprobe)
+ return ret;
+
+ if (!try_module_get(THIS_MODULE)) {
+ kfree(reprobe);
+ return ret;
+ }
+
+ reprobe->dev = get_device(ds->dev);
+ INIT_DELAYED_WORK(&reprobe->dwork, mxl862xx_reprobe_work_fn);
+ schedule_delayed_work(&reprobe->dwork, msecs_to_jiffies(500));
+
+ return ret;
+}
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
new file mode 100644
index 000000000000..a1b60fbacebf
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __MXL862XX_FW_H
+#define __MXL862XX_FW_H
+
+#include <net/dsa.h>
+
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack);
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack);
+
+#endif /* __MXL862XX_FW_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 6e582caea1fa..7ba984e94d82 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -15,6 +15,7 @@
#include <linux/unaligned.h>
#include <net/dsa.h>
#include "mxl862xx.h"
+#include "mxl862xx-cmd.h"
#include "mxl862xx-host.h"
#define CTRL_BUSY_MASK BIT(15)
@@ -336,6 +337,12 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
int ret, cmd_ret;
u16 max, crc, i;
+ if (priv->skip_teardown)
+ return 0;
+
+ if (priv->block_host && cmd != SYS_MISC_FW_UPDATE)
+ return -EBUSY;
+
dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data);
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 45d237b3a40f..e643083d3938 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -21,6 +21,7 @@
#include "mxl862xx.h"
#include "mxl862xx-api.h"
#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
#include "mxl862xx-host.h"
#include "mxl862xx-phylink.h"
@@ -2086,6 +2087,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_pause_stats = mxl862xx_get_pause_stats,
.get_rmon_stats = mxl862xx_get_rmon_stats,
.get_stats64 = mxl862xx_get_stats64,
+ .devlink_info_get = mxl862xx_devlink_info_get,
+ .devlink_flash_update = mxl862xx_devlink_flash_update,
};
static int mxl862xx_probe(struct mdio_device *mdiodev)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 432a5f3f2e08..64d91ad51936 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -319,6 +319,10 @@ struct mxl862xx_fw_version {
* @evlan_ingress_size: per-port ingress Extended VLAN block size
* @evlan_egress_size: per-port egress Extended VLAN block size
* @vf_block_size: per-port VLAN Filter block size
+ * @block_host: reject firmware API commands (except FW_UPDATE)
+ * during a firmware flash
+ * @skip_teardown: discard firmware API commands during the teardown
+ * triggered by the post-flash reprobe
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
@@ -337,6 +341,8 @@ struct mxl862xx_priv {
u16 evlan_ingress_size;
u16 evlan_egress_size;
u16 vf_block_size;
+ bool block_host;
+ bool skip_teardown;
struct delayed_work stats_work;
};
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v2 4/4] net: dsa: mxl862xx: recover switch stuck in MCUboot rescue mode
From: Daniel Golle @ 2026-07-14 0:51 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
In-Reply-To: <cover.1783988826.git.daniel@makrotopia.org>
When the application firmware image is broken (e.g. after an
interrupted flash update) or the sticky rescue flag is set, the
switch stays in MCUboot which only exposes the clause-22 SMDIO
interface used for firmware download. The clause-45 MMD API never
becomes ready, probe fails with -ETIMEDOUT and the user is left
without any way to install a working firmware image other than
physical access to the UART console of the switch.
Detect this state during setup by reading the SB PDI STAT register
via SMDIO: MCUboot signals readiness for a firmware download with
the ready magic. Probe this register first to avoid pointlessly
waiting for the MMD API on a switch sitting in rescue mode, and
probe it again after a ready timeout to catch firmware which was
still alive enough to accept the reset command but then fell into
rescue mode due to a broken image on flash.
In rescue mode, complete the switch registration without any user
interfaces so that devlink is available: user ports fail port_setup
with -ENODEV which makes the DSA core re-register them as unused
ports, while shared and unused ports succeed as their setup must not
fail for the tree to register. The CPU port works without firmware
access since it uses a fixed link and the phylink MAC ops do not
touch the hardware; mac_select_pcs returns no PCS in rescue mode.
Firmware API commands fail fast with -ENODEV instead of running
into the MDIO poll timeout, and the port enable/disable and STP
callbacks invoked by the DSA core during registration and teardown
become no-ops.
devlink dev info reports the running firmware version as
"mcuboot-rescue" so the state can be told apart from an operational
switch. devlink flash update skips the FW_UPDATE command in rescue
mode as MCUboot is already waiting for a download, and proceeds
directly with the SB PDI handshake. After a successful transfer the
usual reprobe cycle restores normal operation; after a failed one
rescue mode is detected again and the user can simply retry.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v2: new patch, allowing recovery from a failed or interrupted update
without physical access to the switch (Andrew Lunn)
drivers/net/dsa/mxl862xx/mxl862xx-fw.c | 49 ++++++++++++++++-----
drivers/net/dsa/mxl862xx/mxl862xx-fw.h | 3 ++
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 3 ++
drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 2 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 38 ++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 3 ++
6 files changed, 85 insertions(+), 13 deletions(-)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
index b2c23ccf1370..88d53bea336f 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
@@ -92,6 +92,24 @@ static void mxl862xx_flash_notify(struct devlink *dl, const char *status,
devlink_flash_update_status_notify(dl, status, NULL, done, total);
}
+/**
+ * mxl862xx_rescue_mode_detect - check whether the switch sits in MCUboot
+ * @priv: driver private data
+ *
+ * MCUboot signals readiness for a firmware download with the SB PDI
+ * ready magic; only the clause-22 SMDIO interface works in this state.
+ *
+ * Return: true if MCUboot is waiting for a firmware download.
+ */
+bool mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv)
+{
+ int ret;
+
+ ret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
+
+ return ret == MXL862XX_SB_PDI_READY;
+}
+
/* device_reprobe() -> remove() frees priv while the work runs, so
* the work struct cannot live in mxl862xx_priv.
*/
@@ -172,13 +190,15 @@ static int mxl862xx_flash_firmware(struct mxl862xx_priv *priv,
}
/* Step 1: reboot the firmware into MCUboot rescue mode */
- ret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,
- false, false);
- if (ret) {
- dev_err(&priv->mdiodev->dev,
- "flash: FW_UPDATE command failed: %pe\n",
- ERR_PTR(ret));
- return ret;
+ if (!priv->rescue_mode) {
+ ret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,
+ false, false);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: FW_UPDATE command failed: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
}
/* Failures from here on must go through end_magic so MCUboot
@@ -328,6 +348,10 @@ int mxl862xx_devlink_info_get(struct dsa_switch *ds,
return ret;
}
+ if (priv->rescue_mode)
+ return devlink_info_version_running_put(req, "fw",
+ "mcuboot-rescue");
+
snprintf(ver_str, sizeof(ver_str), "%u.%u.%u",
priv->fw_version.major, priv->fw_version.minor,
priv->fw_version.revision);
@@ -350,9 +374,13 @@ int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
return -EOPNOTSUPP;
}
- dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
- priv->fw_version.major, priv->fw_version.minor,
- priv->fw_version.revision);
+ if (priv->rescue_mode)
+ dev_info(ds->dev,
+ "flash: recovering switch from MCUboot rescue mode\n");
+ else
+ dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
/* Close ports while the firmware is still alive so the DSA
* core's MDB/FDB tracking is drained, and detach user ports
@@ -383,6 +411,7 @@ int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
if (!ret) {
/* lift the block to query the new firmware version */
priv->block_host = false;
+ priv->rescue_mode = false;
memset(&ver, 0, sizeof(ver));
if (!MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver) &&
ver.iv_major)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
index a1b60fbacebf..57c2000cfee5 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
@@ -5,6 +5,9 @@
#include <net/dsa.h>
+struct mxl862xx_priv;
+
+bool mxl862xx_rescue_mode_detect(struct mxl862xx_priv *priv);
int mxl862xx_devlink_info_get(struct dsa_switch *ds,
struct devlink_info_req *req,
struct netlink_ext_ack *extack);
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 7ba984e94d82..d6c0cdfa0e68 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -340,6 +340,9 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
if (priv->skip_teardown)
return 0;
+ if (priv->rescue_mode)
+ return -ENODEV;
+
if (priv->block_host && cmd != SYS_MISC_FW_UPDATE)
return -EBUSY;
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
index b689652aa9b9..a5b6940b552e 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
@@ -406,6 +406,8 @@ mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
switch (port) {
case 9 ... 16:
+ if (priv->rescue_mode)
+ return NULL;
if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) {
dev_warn_once(dp->ds->dev,
"SerDes PCS unsupported on old firmware.\n");
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index e643083d3938..b7385cfa5474 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -629,9 +629,22 @@ static int mxl862xx_setup(struct dsa_switch *ds)
if (ret)
return ret;
- ret = mxl862xx_wait_ready(ds);
- if (ret)
- return ret;
+ priv->rescue_mode = mxl862xx_rescue_mode_detect(priv);
+ if (!priv->rescue_mode) {
+ ret = mxl862xx_wait_ready(ds);
+ if (ret) {
+ /* the reset may only now have triggered rescue mode */
+ priv->rescue_mode = mxl862xx_rescue_mode_detect(priv);
+ if (!priv->rescue_mode)
+ return ret;
+ }
+ }
+
+ if (priv->rescue_mode) {
+ dev_warn(ds->dev,
+ "switch stuck in MCUboot rescue mode, use devlink to flash new firmware\n");
+ return 0;
+ }
mutex_init(&priv->serdes_lock);
for (i = 0; i < ARRAY_SIZE(priv->serdes_ports); i++)
@@ -716,11 +729,21 @@ static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable)
static int mxl862xx_port_enable(struct dsa_switch *ds, int port,
struct phy_device *phydev)
{
+ struct mxl862xx_priv *priv = ds->priv;
+
+ if (priv->rescue_mode)
+ return 0;
+
return mxl862xx_port_state(ds, port, true);
}
static void mxl862xx_port_disable(struct dsa_switch *ds, int port)
{
+ struct mxl862xx_priv *priv = ds->priv;
+
+ if (priv->rescue_mode)
+ return;
+
if (mxl862xx_port_state(ds, port, false))
dev_err(ds->dev, "failed to disable port %d\n", port);
}
@@ -1338,6 +1361,12 @@ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
bool is_cpu_port = dsa_port_is_cpu(dp);
int ret;
+ /* DSA reinits failed user ports as unused; shared ports must
+ * succeed for the tree to register.
+ */
+ if (priv->rescue_mode)
+ return dsa_port_is_user(dp) ? -ENODEV : 0;
+
ret = mxl862xx_port_state(ds, port, false);
if (ret)
return ret;
@@ -1629,6 +1658,9 @@ static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
struct mxl862xx_priv *priv = ds->priv;
int ret;
+ if (priv->rescue_mode)
+ return;
+
switch (state) {
case BR_STATE_DISABLED:
param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 64d91ad51936..5d21296561e3 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -323,6 +323,8 @@ struct mxl862xx_fw_version {
* during a firmware flash
* @skip_teardown: discard firmware API commands during the teardown
* triggered by the post-flash reprobe
+ * @rescue_mode: switch is stuck in MCUboot; firmware API commands
+ * fail fast, only clause-22 SMDIO works
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
@@ -343,6 +345,7 @@ struct mxl862xx_priv {
u16 vf_block_size;
bool block_host;
bool skip_teardown;
+ bool rescue_mode;
struct delayed_work stats_work;
};
--
2.55.0
^ permalink raw reply related
* [PATCH 1/2] net/socket: Record preference for synchronous wakeups
From: Srikar Dronamraju @ 2026-07-14 1:39 UTC (permalink / raw)
To: LKML, netdev, David S Miller
Cc: Ingo Molnar, Peter Zijlstra, Dietmar Eggemann, Dust Li, D Wythe,
Eric Dumazet, Jakub Kicinski, Jon Maloy, Kuniyuki Iwashima,
linux-sctp, Mahanta Jambigi, Marcelo Ricardo Leitner, Paolo Abeni,
Sidraya Jayagond, Simon Horman, Tony Lu, Wen Gu, Wenjia Zhang,
Willem de Bruijn, Xin Long, Shrikanth Hegde, Vincent Guittot,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Srikar Dronamraju
In-Reply-To: <20260714013940.4068189-4-srikar@linux.ibm.com>
Scheduler differentiates between affine and non-affine wakeups by the
way of sync flags. Scheduler prefers to pull the tasks towards the waker
if the sync flag is set.
In some cases, socket APIs are blindly requesting sync wakeups. This may
cause load-balance issues and non-optimal performance.
Record whether the most recent blocking socket operation could benefit
from synchronous wakeups. Subsequent readiness notifications use this
hint to determine whether WF_SYNC should be propagated.
The flag is advisory and affects only wakeup placement decisions.
Signed-off-by: Srikar Dronamraju <srikar@linux.ibm.com>
---
include/net/sock.h | 1 +
net/socket.c | 56 +++++++++++++++++++++++++++++++++++++++-------
2 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/include/net/sock.h b/include/net/sock.h
index 51185222aac2..acc6b1976dc4 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -1022,6 +1022,7 @@ enum sock_flags {
SOCK_RCVMARK, /* Receive SO_MARK ancillary data with packet */
SOCK_RCVPRIORITY, /* Receive SO_PRIORITY ancillary data with packet */
SOCK_TIMESTAMPING_ANY, /* Copy of sk_tsflags & TSFLAGS_ANY */
+ SOCK_SYNC_WAKEUP, /* Prefer synchronous socket wakeups */
};
#define SK_FLAGS_TIMESTAMP ((1UL << SOCK_TIMESTAMP) | (1UL << SOCK_TIMESTAMPING_RX_SOFTWARE))
diff --git a/net/socket.c b/net/socket.c
index 63c69a0fa74e..0bcb57ae490e 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -1198,15 +1198,27 @@ static void sock_splice_eof(struct file *file)
ops->splice_eof(sock);
}
+static inline void sock_update_sync_wakeup(struct sock *sk, bool nonblock)
+{
+ if (unlikely(!sk))
+ return;
+
+ if (nonblock) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP))
+ sock_reset_flag(sk, SOCK_SYNC_WAKEUP);
+ } else {
+ if (!sock_flag(sk, SOCK_SYNC_WAKEUP))
+ sock_set_flag(sk, SOCK_SYNC_WAKEUP);
+ }
+}
+
static ssize_t sock_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct file *file = iocb->ki_filp;
struct socket *sock = file->private_data;
struct msghdr msg = {.msg_iter = *to};
ssize_t res;
-
- if (file->f_flags & O_NONBLOCK || (iocb->ki_flags & IOCB_NOWAIT))
- msg.msg_flags = MSG_DONTWAIT;
+ bool nonblock;
if (iocb->ki_pos != 0)
return -ESPIPE;
@@ -1214,6 +1226,11 @@ static ssize_t sock_read_iter(struct kiocb *iocb, struct iov_iter *to)
if (!iov_iter_count(to)) /* Match SYS5 behaviour */
return 0;
+ nonblock = (file->f_flags & O_NONBLOCK) || (iocb->ki_flags & IOCB_NOWAIT);
+ if (nonblock)
+ msg.msg_flags = MSG_DONTWAIT;
+
+ sock_update_sync_wakeup(sock->sk, nonblock);
res = sock_recvmsg(sock, &msg, msg.msg_flags);
*to = msg.msg_iter;
return res;
@@ -1225,13 +1242,17 @@ static ssize_t sock_write_iter(struct kiocb *iocb, struct iov_iter *from)
struct socket *sock = file->private_data;
struct msghdr msg = {.msg_iter = *from};
ssize_t res;
+ bool nonblock;
if (iocb->ki_pos != 0)
return -ESPIPE;
- if (file->f_flags & O_NONBLOCK || (iocb->ki_flags & IOCB_NOWAIT))
+ nonblock = (file->f_flags & O_NONBLOCK) || (iocb->ki_flags & IOCB_NOWAIT);
+ if (nonblock)
msg.msg_flags = MSG_DONTWAIT;
+ sock_update_sync_wakeup(sock->sk, nonblock);
+
if (sock->type == SOCK_SEQPACKET)
msg.msg_flags |= MSG_EOR;
@@ -2221,6 +2242,7 @@ int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags,
struct sockaddr_storage address;
int err;
struct msghdr msg;
+ bool nonblock;
err = import_ubuf(ITER_SOURCE, buff, len, &msg.msg_iter);
if (unlikely(err))
@@ -2246,8 +2268,11 @@ int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags,
msg.msg_namelen = addr_len;
}
flags &= ~MSG_INTERNAL_SENDMSG_FLAGS;
- if (sock->file->f_flags & O_NONBLOCK)
+ nonblock = (sock->file->f_flags & O_NONBLOCK);
+ if (nonblock)
flags |= MSG_DONTWAIT;
+
+ sock_update_sync_wakeup(sock->sk, nonblock);
msg.msg_flags = flags;
return __sock_sendmsg(sock, &msg);
}
@@ -2284,6 +2309,7 @@ int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,
};
struct socket *sock;
int err, err2;
+ bool nonblock;
err = import_ubuf(ITER_DEST, ubuf, size, &msg.msg_iter);
if (unlikely(err))
@@ -2297,8 +2323,11 @@ int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,
if (unlikely(!sock))
return -ENOTSOCK;
- if (sock->file->f_flags & O_NONBLOCK)
+ nonblock = (sock->file->f_flags & O_NONBLOCK);
+ if (nonblock)
flags |= MSG_DONTWAIT;
+
+ sock_update_sync_wakeup(sock->sk, nonblock);
err = sock_recvmsg(sock, &msg, flags);
if (err >= 0 && addr != NULL) {
@@ -2634,6 +2663,7 @@ static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
unsigned char *ctl_buf = ctl;
int ctl_len;
ssize_t err;
+ bool nonblock;
err = -ENOBUFS;
@@ -2666,8 +2696,12 @@ static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
flags &= ~MSG_INTERNAL_SENDMSG_FLAGS;
msg_sys->msg_flags = flags;
- if (sock->file->f_flags & O_NONBLOCK)
+ nonblock = (sock->file->f_flags & O_NONBLOCK);
+ if (nonblock)
msg_sys->msg_flags |= MSG_DONTWAIT;
+
+ sock_update_sync_wakeup(sock->sk, nonblock);
+
/*
* If this is sendmmsg() and current destination address is same as
* previously succeeded address, omit asking LSM's decision.
@@ -2887,6 +2921,7 @@ static int ____sys_recvmsg(struct socket *sock, struct msghdr *msg_sys,
unsigned long cmsg_ptr;
int len;
ssize_t err;
+ bool nonblock;
msg_sys->msg_name = &addr;
cmsg_ptr = (unsigned long)msg_sys->msg_control;
@@ -2895,9 +2930,12 @@ static int ____sys_recvmsg(struct socket *sock, struct msghdr *msg_sys,
/* We assume all kernel code knows the size of sockaddr_storage */
msg_sys->msg_namelen = 0;
- if (sock->file->f_flags & O_NONBLOCK)
+ nonblock = (sock->file->f_flags & O_NONBLOCK);
+ if (nonblock)
flags |= MSG_DONTWAIT;
+ sock_update_sync_wakeup(sock->sk, nonblock);
+
if (unlikely(nosec))
err = sock_recvmsg_nosec(sock, msg_sys, flags);
else
@@ -3056,6 +3094,8 @@ static int do_recvmmsg(int fd, struct mmsghdr __user *mmsg,
if (flags & MSG_WAITFORONE)
flags |= MSG_DONTWAIT;
+ sock_update_sync_wakeup(sock->sk, flags & MSG_WAITFORONE);
+
if (timeout) {
ktime_get_ts64(&timeout64);
*timeout = timespec64_sub(end_time, timeout64);
--
2.52.0
^ permalink raw reply related
* [PATCH 0/2] net: Use synchronous wakeups selectively
From: Srikar Dronamraju @ 2026-07-14 1:39 UTC (permalink / raw)
To: LKML, netdev, David S Miller
Cc: Ingo Molnar, Peter Zijlstra, Dietmar Eggemann, Dust Li, D Wythe,
Eric Dumazet, Jakub Kicinski, Jon Maloy, Kuniyuki Iwashima,
linux-sctp, Mahanta Jambigi, Marcelo Ricardo Leitner, Paolo Abeni,
Sidraya Jayagond, Simon Horman, Tony Lu, Wen Gu, Wenjia Zhang,
Willem de Bruijn, Xin Long, Shrikanth Hegde, Vincent Guittot,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Srikar Dronamraju
The scheduler assumes in several wakeup paths that a task using WF_SYNC
is likely to yield the CPU shortly. However several networking wakeup
paths unconditionally use synchronous wakeups even when the waking task
continues execution.
During wakeup, with WF_SYNC flag set, because of the assumption that
current thread is ready to give up, the wakee thread will be migrated
from any core within the chip to the current LLC. If these operations
are frequent, and wakers are actually not going away, then it will lead
to load imbalance and hurt performance. This is especially true in
architectures where LLCs are small and number of LLCs per chip are more.
Running vllm workload was run on Power10 system
No patch with patch %diff
Inference Time (sec) 17.84 16.35 -8.35%
llm query bandwidth (tokens/sec) 14.78 16.21 +9.68%
Lower inference time and higher tokens/sec is better.
Srikar Dronamraju (2):
net/socket: Record preference for synchronous wakeups
net/sock: Propagate WF_SYNC only when requested
include/net/sock.h | 1 +
net/core/sock.c | 31 +++++++++++++++++++------
net/sctp/socket.c | 10 +++++++--
net/smc/af_smc.c | 4 ++--
net/smc/smc_rx.c | 10 +++++++--
net/socket.c | 56 +++++++++++++++++++++++++++++++++++++++-------
net/tipc/socket.c | 22 +++++++++++++-----
net/unix/af_unix.c | 26 ++++++++++++++-------
8 files changed, 126 insertions(+), 34 deletions(-)
--
2.51.0
^ permalink raw reply
* [PATCH 2/2] net/sock: Propagate WF_SYNC only when requested
From: Srikar Dronamraju @ 2026-07-14 1:39 UTC (permalink / raw)
To: LKML, netdev, David S Miller
Cc: Ingo Molnar, Peter Zijlstra, Dietmar Eggemann, Dust Li, D Wythe,
Eric Dumazet, Jakub Kicinski, Jon Maloy, Kuniyuki Iwashima,
linux-sctp, Mahanta Jambigi, Marcelo Ricardo Leitner, Paolo Abeni,
Sidraya Jayagond, Simon Horman, Tony Lu, Wen Gu, Wenjia Zhang,
Willem de Bruijn, Xin Long, Shrikanth Hegde, Vincent Guittot,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Srikar Dronamraju
In-Reply-To: <20260714013940.4068189-4-srikar@linux.ibm.com>
Use SOCK_SYNC_WAKEUP to select between synchronous and asynchronous wakeup
wakeup APIs. This avoids propagating WF_SYNC when no blocking waiter is
expected. All wakeup locations in networking code that currently issue
synchronous poll-style wakeups unconditionally are updated.
Signed-off-by: Srikar Dronamraju <srikar@linux.ibm.com>
---
net/core/sock.c | 31 ++++++++++++++++++++++++-------
net/sctp/socket.c | 10 ++++++++--
net/smc/af_smc.c | 4 ++--
net/smc/smc_rx.c | 10 ++++++++--
net/tipc/socket.c | 22 +++++++++++++++++-----
net/unix/af_unix.c | 26 ++++++++++++++++++--------
6 files changed, 77 insertions(+), 26 deletions(-)
diff --git a/net/core/sock.c b/net/core/sock.c
index 8a59bfaa8096..a214e883b14b 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -3652,9 +3652,15 @@ void sock_def_readable(struct sock *sk)
rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
- if (skwq_has_sleeper(wq))
- wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
+ if (skwq_has_sleeper(wq)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
+ EPOLLRDNORM | EPOLLRDBAND);
+ } else {
+ wake_up_interruptible_poll(&wq->wait, EPOLLIN | EPOLLPRI |
EPOLLRDNORM | EPOLLRDBAND);
+ }
+ }
sk_wake_async_rcu(sk, SOCK_WAKE_WAITD, POLL_IN);
rcu_read_unlock();
}
@@ -3670,9 +3676,15 @@ static void sock_def_write_space(struct sock *sk)
*/
if (sock_writeable(sk)) {
wq = rcu_dereference(sk->sk_wq);
- if (skwq_has_sleeper(wq))
- wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
+ if (skwq_has_sleeper(wq)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
+ EPOLLWRNORM | EPOLLWRBAND);
+ } else {
+ wake_up_interruptible_poll(&wq->wait, EPOLLOUT |
EPOLLWRNORM | EPOLLWRBAND);
+ }
+ }
/* Should agree with poll, otherwise some programs break */
sk_wake_async_rcu(sk, SOCK_WAKE_SPACE, POLL_OUT);
@@ -3695,10 +3707,15 @@ static void sock_def_write_space_wfree(struct sock *sk, int wmem_alloc)
/* rely on refcount_sub from sock_wfree() */
smp_mb__after_atomic();
- if (wq && waitqueue_active(&wq->wait))
- wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
+ if (wq && waitqueue_active(&wq->wait)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
EPOLLWRNORM | EPOLLWRBAND);
-
+ } else {
+ wake_up_interruptible_poll(&wq->wait, EPOLLOUT |
+ EPOLLWRNORM | EPOLLWRBAND);
+ }
+ }
/* Should agree with poll, otherwise some programs break */
sk_wake_async_rcu(sk, SOCK_WAKE_SPACE, POLL_OUT);
}
diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index c7b9e325ec1c..9cb3432f065a 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -9348,9 +9348,15 @@ void sctp_data_ready(struct sock *sk)
rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
- if (skwq_has_sleeper(wq))
- wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN |
+ if (skwq_has_sleeper(wq)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN |
+ EPOLLRDNORM | EPOLLRDBAND);
+ } else {
+ wake_up_interruptible_poll(&wq->wait, EPOLLIN |
EPOLLRDNORM | EPOLLRDBAND);
+ }
+ }
sk_wake_async_rcu(sk, SOCK_WAKE_WAITD, POLL_IN);
rcu_read_unlock();
}
diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
index b5db69073e20..1a6ea2e30769 100644
--- a/net/smc/af_smc.c
+++ b/net/smc/af_smc.c
@@ -819,10 +819,10 @@ static void smc_fback_wakeup_waitqueue(struct smc_sock *smc, void *key)
wake_up_interruptible_all(&wq->wait);
} else {
flags = key_to_poll(key);
- if (flags & (EPOLLIN | EPOLLOUT))
+ if (flags & (EPOLLIN | EPOLLOUT) && sock_flag(&smc->sk, SOCK_SYNC_WAKEUP))
/* sk_data_ready or sk_write_space */
wake_up_interruptible_sync_poll(&wq->wait, flags);
- else if (flags & EPOLLERR)
+ else
/* sk_error_report */
wake_up_interruptible_poll(&wq->wait, flags);
}
diff --git a/net/smc/smc_rx.c b/net/smc/smc_rx.c
index c1d9b923938d..4e288a2364d2 100644
--- a/net/smc/smc_rx.c
+++ b/net/smc/smc_rx.c
@@ -39,9 +39,15 @@ static void smc_rx_wake_up(struct sock *sk)
/* called already in smc_listen_work() */
rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
- if (skwq_has_sleeper(wq))
- wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
+ if (skwq_has_sleeper(wq)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
EPOLLRDNORM | EPOLLRDBAND);
+ } else {
+ wake_up_interruptible_poll(&wq->wait, EPOLLIN | EPOLLPRI |
+ EPOLLRDNORM | EPOLLRDBAND);
+ }
+ }
sk_wake_async_rcu(sk, SOCK_WAKE_WAITD, POLL_IN);
if ((sk->sk_shutdown == SHUTDOWN_MASK) ||
(sk->sk_state == SMC_CLOSED))
diff --git a/net/tipc/socket.c b/net/tipc/socket.c
index e564341e0216..9fa83a89882c 100644
--- a/net/tipc/socket.c
+++ b/net/tipc/socket.c
@@ -2116,9 +2116,15 @@ static void tipc_write_space(struct sock *sk)
rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
- if (skwq_has_sleeper(wq))
- wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
+ if (skwq_has_sleeper(wq)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
EPOLLWRNORM | EPOLLWRBAND);
+ } else {
+ wake_up_interruptible_poll(&wq->wait, EPOLLOUT |
+ EPOLLWRNORM | EPOLLWRBAND);
+ }
+ }
rcu_read_unlock();
}
@@ -2134,9 +2140,15 @@ static void tipc_data_ready(struct sock *sk)
rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
- if (skwq_has_sleeper(wq))
- wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN |
- EPOLLRDNORM | EPOLLRDBAND);
+ if (skwq_has_sleeper(wq)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN |
+ EPOLLRDNORM | EPOLLRDBAND);
+ } else {
+ wake_up_interruptible_poll(&wq->wait, EPOLLIN |
+ EPOLLRDNORM | EPOLLRDBAND);
+ }
+ }
rcu_read_unlock();
}
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..15ebcc2d9d58 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -601,9 +601,15 @@ static void unix_write_space(struct sock *sk)
rcu_read_lock();
if (unix_writable(sk, READ_ONCE(sk->sk_state))) {
wq = rcu_dereference(sk->sk_wq);
- if (skwq_has_sleeper(wq))
- wake_up_interruptible_sync_poll(&wq->wait,
- EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
+ if (skwq_has_sleeper(wq)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&wq->wait,
+ EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
+ } else {
+ wake_up_interruptible_poll(&wq->wait,
+ EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
+ }
+ }
sk_wake_async_rcu(sk, SOCK_WAKE_SPACE, POLL_OUT);
}
rcu_read_unlock();
@@ -2603,11 +2609,15 @@ int __unix_dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t size,
goto out;
}
- if (wq_has_sleeper(&u->peer_wait))
- wake_up_interruptible_sync_poll(&u->peer_wait,
- EPOLLOUT | EPOLLWRNORM |
- EPOLLWRBAND);
-
+ if (wq_has_sleeper(&u->peer_wait)) {
+ if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
+ wake_up_interruptible_sync_poll(&u->peer_wait,
+ EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
+ } else {
+ wake_up_interruptible_poll(&u->peer_wait,
+ EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
+ }
+ }
if (msg->msg_name) {
unix_copy_addr(msg, skb->sk);
--
2.52.0
^ permalink raw reply related
* Re: [PATCH v3 2/3] net: stmmac: fix l3l4 filter rejecting unsupported offload requests
From: Nazle Asmade, Muhammad Nazim Amirul @ 2026-07-14 1:50 UTC (permalink / raw)
To: netdev@vger.kernel.org
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, rmk+kernel@armlinux.org.uk,
maxime.chevallier@bootlin.com, Jose.Abreu@synopsys.com,
linux-kernel@vger.kernel.org
In-Reply-To: <20260630115622.9426-3-muhammad.nazim.amirul.nazle.asmade@altera.com>
On 30/6/2026 7:56 pm, Nazle Asmade, Muhammad Nazim Amirul wrote:
Hi,
do we have any update on this patch status to go in?
BR,
Nazim
> From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
>
> The basic flow parser in tc_add_basic_flow() does not validate match
> keys before proceeding. Unsupported offload configurations such as
> partial protocol masks, non-IPv4 network proto, or non-TCP/UDP transport
> proto are silently accepted instead of returning -EOPNOTSUPP.
>
> Add validation to return -EOPNOTSUPP early for:
> - No network or transport proto present in the key
> - Partial protocol mask (only full mask supported)
> - Network proto is not IPv4
> - Transport proto is not TCP or UDP
>
> Each rejection includes an extack message so the user knows which part
> of the match is unsupported.
>
> Also propagate -EOPNOTSUPP from tc_add_basic_flow() in tc_add_flow()
> by returning it directly rather than using break. The break was silently
> discarding the error for FLOW_CLS_REPLACE operations where entry->in_use
> is already true, causing tc_add_flow() to return 0 (success) for
> unsupported replace requests.
>
> Fixes: 425eabddaf0f ("net: stmmac: Implement L3/L4 Filters using TC Flower")
> Signed-off-by: Rohan G Thomas <rohan.g.thomas@altera.com>
> Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
> ---
> Changes in v3:
> - Add extack messages to each -EOPNOTSUPP return so users know which
> part of the match is unsupported (Jakub Kicinski)
> - Return -EOPNOTSUPP directly instead of break to avoid silently
> reporting success on unsupported FLOW_CLS_REPLACE (Sashiko review)
> - Patches 1/3 and 3/3 are unchanged from v2
>
> Changes in v2:
> - No changes
>
> ---
> .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 34 +++++++++++++++++++
> 1 file changed, 34 insertions(+)
>
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> index d78652718599..14cabe76e53e 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> @@ -446,6 +446,7 @@ static int tc_parse_flow_actions(struct stmmac_priv *priv,
> }
>
> #define ETHER_TYPE_FULL_MASK cpu_to_be16(~0)
> +#define IP_PROTO_FULL_MASK 0xFF
>
> static int tc_add_basic_flow(struct stmmac_priv *priv,
> struct flow_cls_offload *cls,
> @@ -461,6 +462,33 @@ static int tc_add_basic_flow(struct stmmac_priv *priv,
>
> flow_rule_match_basic(rule, &match);
>
> + /* Both network proto and transport proto not present in the key */
> + if (!match.mask || !(match.mask->n_proto || match.mask->ip_proto)) {
> + NL_SET_ERR_MSG_MOD(cls->common.extack,
> + "filter must specify network or transport protocol");
> + return -EOPNOTSUPP;
> + }
> +
> + /* If the proto is present in the key and is not full mask */
> + if ((match.mask->n_proto && match.mask->n_proto != ETHER_TYPE_FULL_MASK) ||
> + (match.mask->ip_proto && match.mask->ip_proto != IP_PROTO_FULL_MASK)) {
> + NL_SET_ERR_MSG_MOD(cls->common.extack,
> + "only full protocol mask is supported");
> + return -EOPNOTSUPP;
> + }
> +
> + /* Network proto is present in the key and is not IPv4 */
> + if (match.mask->n_proto && match.key->n_proto != cpu_to_be16(ETH_P_IP)) {
> + NL_SET_ERR_MSG_MOD(cls->common.extack,
> + "only IPv4 network protocol is supported");
> + return -EOPNOTSUPP;
> + }
> +
> + /* Transport proto is present in the key and is not TCP or UDP */
> + if (match.mask->ip_proto &&
> + match.key->ip_proto != IPPROTO_TCP &&
> + match.key->ip_proto != IPPROTO_UDP) {
> + NL_SET_ERR_MSG_MOD(cls->common.extack,
> + "only TCP and UDP transport protocols are supported");
> + return -EOPNOTSUPP;
> + }
> +
> entry->ip_proto = match.key->ip_proto;
> return 0;
> }
> @@ -598,11 +626,7 @@ static int tc_add_flow(struct stmmac_priv *priv,
> ret = tc_flow_parsers[i].fn(priv, cls, entry);
> if (!ret)
> entry->in_use = true;
> - else if (ret == -EOPNOTSUPP)
> - /* The basic flow parser will return EOPNOTSUPP, if a
> - * requested offload not fully supported by the hw. And
> - * in that case fail early.
> - */
> - break;
> + else if (ret == -EOPNOTSUPP)
> + return ret;
> }
>
> if (!entry->in_use)
^ permalink raw reply
* [PATCH v3 net-next 5/9] octeontx2-af: switch: TL1 scheduling and NPC channel control
From: Ratheesh Kannoth @ 2026-07-14 1:53 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260714015331.1801922-1-rkannoth@marvell.com>
Switch (PAN) mode needs more than one TL1 scheduler queue index so the
hardware can steer traffic to different links according to NPC flow
rules, not only the PF/VF default Tx link.
Add NIX_TXSCH_ALLOC_FLAG_PAN to nix_txsch_alloc requests: use the PAN
link index for scheduler range calculation, allow multiple TL1 queues
when the aggregate level spans start..end, and allocate indices in
that range. Add TXSCHQ_FREE_PAN_TL1 so TL1 entries in that path can be
freed via nix_txsch_free where they were previously skipped.
For NPC install flow, add set_chanmask so callers can keep a non-default
chan_mask when the requester is not the AF; without it, chan_mask was
always forced to 0xFFF for non-AF functions.
Allocate the NIX LF SQ bitmap with the same span used by
bitmap_weight(..., BITS_PER_LONG * 16) in rvu_get_hwinfo().
Extend struct sg_list with cq_idx and len for transmit-side metadata.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../net/ethernet/marvell/octeontx2/af/mbox.h | 15 ++
.../net/ethernet/marvell/octeontx2/af/rvu.c | 16 ++-
.../net/ethernet/marvell/octeontx2/af/rvu.h | 6 +
.../ethernet/marvell/octeontx2/af/rvu_nix.c | 133 +++++++++++++++---
.../marvell/octeontx2/af/rvu_npc_fs.c | 7 +-
.../marvell/octeontx2/nic/otx2_txrx.h | 2 +
6 files changed, 155 insertions(+), 24 deletions(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
index cdfb5a8bafb9..bf8edab569b1 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
@@ -1158,6 +1158,13 @@ struct nix_txsch_alloc_req {
/* Scheduler queue count request at each level */
u16 schq_contig[NIX_TXSCH_LVL_CNT]; /* No of contiguous queues */
u16 schq[NIX_TXSCH_LVL_CNT]; /* No of non-contiguous queues */
+ /* Set only by the single switchdev PF (rvu->rswitch.pcifunc). This is
+ * not the eswitch representor (rvu->rep_pcifunc). That PF requests two
+ * aggregate-level TL2 queues on the PAN link, one for CGX and one for
+ * SDP steering. No other PF or VF sets this flag.
+ */
+#define NIX_TXSCH_ALLOC_FLAG_PAN BIT_ULL(0)
+ u64 flags;
};
struct nix_txsch_alloc_rsp {
@@ -1176,6 +1183,10 @@ struct nix_txsch_alloc_rsp {
struct nix_txsch_free_req {
struct mbox_msghdr hdr;
#define TXSCHQ_FREE_ALL BIT_ULL(0)
+ /* Frees PAN TL2 queues allocated with NIX_TXSCH_ALLOC_FLAG_PAN. Used
+ * only by the switchdev PF (rvu->rswitch.pcifunc), not by other PFs/VFs.
+ */
+#define TXSCHQ_FREE_PAN_TL1 BIT_ULL(1)
u16 flags;
/* Scheduler queue level to be freed */
u16 schq_lvl;
@@ -2115,6 +2126,10 @@ struct npc_install_flow_req {
u8 hw_prio;
u8 req_kw_type; /* Key type to be written */
u8 alloc_entry; /* only for cn20k */
+ /* When set, rvu_mbox_handler_npc_install_flow() keeps caller chan_mask
+ * for switchdev-installed flows instead of the default CPT override.
+ */
+ u8 set_chanmask;
/* For now use any priority, once AF driver is changed to
* allocate least priority entry instead of mid zone then make
* NPC_MCAM_LEAST_PRIO as 3
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
index df4f1c05a2a0..532c2b69fb85 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
@@ -2054,8 +2054,12 @@ int rvu_mbox_handler_iface_get_info(struct rvu *rvu, struct msg_req *req,
info->rq_cnt = 0;
mutex_lock(&rvu->rsrc_lock);
- if (pfvf->sq_bmap)
- info->sq_cnt = bitmap_weight(pfvf->sq_bmap, BITS_PER_LONG * 16);
+ if (pfvf->sq_bmap) {
+ int sq_bmap_bits = rvu_is_switch_pcifunc(rvu, pcifunc) ?
+ NIX_SQ_BMAP_BITS : pfvf->sq_ctx->qsize;
+
+ info->sq_cnt = bitmap_weight(pfvf->sq_bmap, sq_bmap_bits);
+ }
if (pfvf->cq_bmap)
info->cq_cnt = bitmap_weight(pfvf->cq_bmap, BITS_PER_LONG);
@@ -2101,8 +2105,12 @@ int rvu_mbox_handler_iface_get_info(struct rvu *rvu, struct msg_req *req,
info->rq_cnt = 0;
mutex_lock(&rvu->rsrc_lock);
- if (pfvf->sq_bmap)
- info->sq_cnt = bitmap_weight(pfvf->sq_bmap, BITS_PER_LONG * 16);
+ if (pfvf->sq_bmap) {
+ int sq_bmap_bits = rvu_is_switch_pcifunc(rvu, pcifunc) ?
+ NIX_SQ_BMAP_BITS : pfvf->sq_ctx->qsize;
+
+ info->sq_cnt = bitmap_weight(pfvf->sq_bmap, sq_bmap_bits);
+ }
if (pfvf->cq_bmap)
info->cq_cnt = bitmap_weight(pfvf->cq_bmap, BITS_PER_LONG);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
index 8cf1ad9ec749..0662cc6134b0 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
@@ -335,6 +335,7 @@ struct nix_txsch {
u8 lvl;
#define NIX_TXSCHQ_FREE BIT_ULL(1)
#define NIX_TXSCHQ_CFG_DONE BIT_ULL(0)
+#define NIX_SQ_BMAP_BITS (BITS_PER_LONG * 16)
#define TXSCH_MAP_FUNC(__pfvf_map) ((__pfvf_map) & 0xFFFF)
#define TXSCH_MAP_FLAGS(__pfvf_map) ((__pfvf_map) >> 16)
#define TXSCH_MAP(__func, __flags) (((__func) & 0xFFFF) | ((__flags) << 16))
@@ -904,6 +905,11 @@ static inline bool is_pffunc_af(u16 pcifunc)
return !pcifunc;
}
+static inline bool rvu_is_switch_pcifunc(struct rvu *rvu, u16 pcifunc)
+{
+ return rvu->rswitch.pcifunc && pcifunc == rvu->rswitch.pcifunc;
+}
+
static inline bool is_rvu_fwdata_valid(struct rvu *rvu)
{
return (rvu->fwdata->header_magic == RVU_FWDATA_HEADER_MAGIC) &&
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
index 67c9621dbc1d..1a0ba148478e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
@@ -1600,7 +1600,11 @@ int rvu_mbox_handler_nix_lf_alloc(struct rvu *rvu,
if (rc)
goto free_mem;
- pfvf->sq_bmap = kcalloc(req->sq_cnt, sizeof(long), GFP_KERNEL);
+ if (rvu_is_switch_pcifunc(rvu, pcifunc))
+ pfvf->sq_bmap = kcalloc(BITS_TO_LONGS(NIX_SQ_BMAP_BITS),
+ sizeof(long), GFP_KERNEL);
+ else
+ pfvf->sq_bmap = kcalloc(req->sq_cnt, sizeof(long), GFP_KERNEL);
if (!pfvf->sq_bmap) {
rc = -ENOMEM;
goto free_mem;
@@ -2127,6 +2131,25 @@ static void nix_get_txschq_range(struct rvu *rvu, u16 pcifunc,
}
}
+static int nix_get_pan_tx_link(struct rvu *rvu)
+{
+ struct rvu_hwinfo *hw = rvu->hw;
+
+ return hw->cgx_links + hw->lbk_links + 1;
+}
+
+static bool nix_txsch_is_pan_schq(struct rvu *rvu, int schq)
+{
+ int pan_link = nix_get_pan_tx_link(rvu);
+
+ return schq >= pan_link && schq <= pan_link + 1;
+}
+
+static bool nix_txsch_pan_allowed(struct rvu *rvu, u16 pcifunc)
+{
+ return rvu_is_switch_pcifunc(rvu, pcifunc);
+}
+
static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,
struct nix_hw *nix_hw,
struct nix_txsch_alloc_req *req)
@@ -2142,12 +2165,27 @@ static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,
if (!req_schq)
return 0;
- link = nix_get_tx_link(rvu, pcifunc);
+ if (req->flags & NIX_TXSCH_ALLOC_FLAG_PAN) {
+ if (!nix_txsch_pan_allowed(rvu, pcifunc))
+ return NIX_AF_ERR_TLX_ALLOC_FAIL;
+ link = nix_get_pan_tx_link(rvu);
+ } else {
+ link = nix_get_tx_link(rvu, pcifunc);
+ }
/* For traffic aggregating scheduler level, one queue is enough */
if (lvl >= hw->cap.nix_tx_aggr_lvl) {
- if (req_schq != 1)
+ if (req_schq != 1 && !(req->flags & NIX_TXSCH_ALLOC_FLAG_PAN))
+ return NIX_AF_ERR_TLX_ALLOC_FAIL;
+ if (req->schq[lvl] > MAX_TXSCHQ_PER_FUNC ||
+ req->schq_contig[lvl] > MAX_TXSCHQ_PER_FUNC)
return NIX_AF_ERR_TLX_ALLOC_FAIL;
+ if (req->flags & NIX_TXSCH_ALLOC_FLAG_PAN) {
+ if (link >= txsch->schq.max || link + 1 >= txsch->schq.max)
+ return NIX_AF_ERR_TLX_ALLOC_FAIL;
+ if (req_schq > 2)
+ return NIX_AF_ERR_TLX_ALLOC_FAIL;
+ }
return 0;
}
@@ -2176,9 +2214,9 @@ static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,
return 0;
}
-static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
- struct nix_txsch_alloc_rsp *rsp,
- int lvl, int start, int end)
+static int nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
+ struct nix_txsch_alloc_rsp *rsp,
+ int lvl, int start, int end)
{
struct rvu_hwinfo *hw = rvu->hw;
u16 pcifunc = rsp->hdr.pcifunc;
@@ -2188,6 +2226,46 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
* on transmit link to which PF_FUNC is mapped to.
*/
if (lvl >= hw->cap.nix_tx_aggr_lvl) {
+ if (start != end) {
+ int want_contig = rsp->schq_contig[lvl];
+ int got_contig = 0, got = 0;
+ int want = rsp->schq[lvl];
+
+ for (schq = start; schq <= end; schq++) {
+ if (test_bit(schq, txsch->schq.bmap))
+ continue;
+
+ if (got_contig < want_contig) {
+ set_bit(schq, txsch->schq.bmap);
+ rsp->schq_contig_list[lvl][got_contig++] = schq;
+ continue;
+ }
+
+ if (got < want) {
+ set_bit(schq, txsch->schq.bmap);
+ rsp->schq_list[lvl][got++] = schq;
+ }
+ }
+
+ rsp->schq_contig[lvl] = got_contig;
+ rsp->schq[lvl] = got;
+
+ if (got_contig < want_contig || got < want) {
+ for (idx = 0; idx < got_contig; idx++)
+ clear_bit(rsp->schq_contig_list[lvl][idx],
+ txsch->schq.bmap);
+ for (idx = 0; idx < got; idx++)
+ clear_bit(rsp->schq_list[lvl][idx],
+ txsch->schq.bmap);
+ rsp->schq_contig[lvl] = 0;
+ rsp->schq[lvl] = 0;
+ dev_err(rvu->dev,
+ "Could not allocate schq at lvl=%u start=%u end=%u\n",
+ lvl, start, end);
+ return -ENOMEM;
+ }
+ return 0;
+ }
/* A single TL queue is allocated */
if (rsp->schq_contig[lvl]) {
rsp->schq_contig[lvl] = 1;
@@ -2202,7 +2280,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
rsp->schq[lvl] = 1;
rsp->schq_list[lvl][0] = start;
}
- return;
+ return 0;
}
/* Adjust the queue request count if HW supports
@@ -2214,7 +2292,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
if (idx >= (end - start) || test_bit(schq, txsch->schq.bmap)) {
rsp->schq_contig[lvl] = 0;
rsp->schq[lvl] = 0;
- return;
+ return 0;
}
if (rsp->schq_contig[lvl]) {
@@ -2227,7 +2305,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
set_bit(schq, txsch->schq.bmap);
rsp->schq_list[lvl][0] = schq;
}
- return;
+ return 0;
}
/* Allocate contiguous queue indices requesty first */
@@ -2258,6 +2336,8 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
/* Update how many were allocated */
rsp->schq[lvl] = idx;
}
+
+ return 0;
}
int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
@@ -2282,6 +2362,10 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
if (!nix_hw)
return NIX_AF_ERR_INVALID_NIXBLK;
+ if ((req->flags & NIX_TXSCH_ALLOC_FLAG_PAN) &&
+ !nix_txsch_pan_allowed(rvu, pcifunc))
+ return NIX_AF_ERR_TLX_ALLOC_FAIL;
+
mutex_lock(&rvu->rsrc_lock);
/* Check if request is valid as per HW capabilities
@@ -2304,11 +2388,14 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
rsp->schq[lvl] = req->schq[lvl];
rsp->schq_contig[lvl] = req->schq_contig[lvl];
- link = nix_get_tx_link(rvu, pcifunc);
+ if (req->flags & NIX_TXSCH_ALLOC_FLAG_PAN)
+ link = nix_get_pan_tx_link(rvu);
+ else
+ link = nix_get_tx_link(rvu, pcifunc);
if (lvl >= hw->cap.nix_tx_aggr_lvl) {
start = link;
- end = link;
+ end = link + !!(req->flags & NIX_TXSCH_ALLOC_FLAG_PAN);
} else if (hw->cap.nix_fixed_txschq_mapping) {
nix_get_txschq_range(rvu, pcifunc, link, &start, &end);
} else {
@@ -2316,10 +2403,11 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
end = txsch->schq.max;
}
- nix_txsch_alloc(rvu, txsch, rsp, lvl, start, end);
+ if (nix_txsch_alloc(rvu, txsch, rsp, lvl, start, end))
+ goto err;
/* Reset queue config */
- for (idx = 0; idx < req->schq_contig[lvl]; idx++) {
+ for (idx = 0; idx < rsp->schq_contig[lvl]; idx++) {
schq = rsp->schq_contig_list[lvl][idx];
if (!(TXSCH_MAP_FLAGS(pfvf_map[schq]) &
NIX_TXSCHQ_CFG_DONE))
@@ -2329,7 +2417,7 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
nix_reset_tx_schedule(rvu, blkaddr, lvl, schq);
}
- for (idx = 0; idx < req->schq[lvl]; idx++) {
+ for (idx = 0; idx < rsp->schq[lvl]; idx++) {
schq = rsp->schq_list[lvl][idx];
if (!(TXSCH_MAP_FLAGS(pfvf_map[schq]) &
NIX_TXSCHQ_CFG_DONE))
@@ -2625,11 +2713,11 @@ static int nix_txschq_free(struct rvu *rvu, u16 pcifunc)
/* TLs above aggregation level are shared across all PF
* and it's VFs, hence skip freeing them.
*/
- if (lvl >= hw->cap.nix_tx_aggr_lvl)
- continue;
-
txsch = &nix_hw->txsch[lvl];
for (schq = 0; schq < txsch->schq.max; schq++) {
+ if (lvl >= hw->cap.nix_tx_aggr_lvl &&
+ !nix_txsch_is_pan_schq(rvu, schq))
+ continue;
if (TXSCH_MAP_FUNC(txsch->pfvf_map[schq]) != pcifunc)
continue;
nix_reset_tx_schedule(rvu, blkaddr, lvl, schq);
@@ -2673,7 +2761,16 @@ static int nix_txschq_free_one(struct rvu *rvu,
schq = req->schq;
txsch = &nix_hw->txsch[lvl];
- if (lvl >= hw->cap.nix_tx_aggr_lvl || schq >= txsch->schq.max)
+ if (req->flags & TXSCHQ_FREE_PAN_TL1) {
+ if (!nix_txsch_pan_allowed(rvu, pcifunc))
+ return NIX_AF_ERR_TLX_INVALID;
+ if (!nix_txsch_is_pan_schq(rvu, schq))
+ return NIX_AF_ERR_TLX_INVALID;
+ } else if (lvl >= hw->cap.nix_tx_aggr_lvl) {
+ return 0;
+ }
+
+ if (schq >= txsch->schq.max)
return 0;
pfvf_map = txsch->pfvf_map;
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c
index 09c7ee8571df..03bc3e321522 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c
@@ -1828,8 +1828,11 @@ int rvu_mbox_handler_npc_install_flow(struct rvu *rvu,
target = req->hdr.pcifunc;
}
- /* ignore chan_mask in case pf func is not AF, revisit later */
- if (!is_pffunc_af(req->hdr.pcifunc))
+ /* Non-AF requesters normally get the CPT default chan_mask. set_chanmask
+ * preserves caller-supplied chan_mask for switchdev-installed flows; see
+ * npc_install_flow_req.set_chanmask.
+ */
+ if (!is_pffunc_af(req->hdr.pcifunc) && !req->set_chanmask)
req->chan_mask = rvu_get_cpt_chan_mask(rvu);
err = npc_check_unsupported_flows(rvu, req->features, req->intf);
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h
index acf259d72008..73a98b94426b 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h
@@ -78,6 +78,8 @@ struct otx2_rcv_queue {
struct sg_list {
u16 num_segs;
u16 flags;
+ u16 cq_idx;
+ u16 len;
u64 skb;
u64 size[OTX2_MAX_FRAGS_IN_SQE];
u64 dma_addr[OTX2_MAX_FRAGS_IN_SQE];
--
2.43.0
^ permalink raw reply related
* [PATCH v3 net-next 6/9] octeontx2-pf: switch: Register notifiers for switch offload
From: Ratheesh Kannoth @ 2026-07-14 1:53 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260714015331.1801922-1-rkannoth@marvell.com>
The representor enables switch mode via devlink; register and unregister
the switch notifier blocks when that mode is turned on or off so the PF
can observe FIB routes, neighbour updates, IPv4/IPv6 address changes,
netdev state, and switchdev FDB notifications.
Add sw_nb_v4.c and sw_nb_v6.c for IPv4 and IPv6-specific handling, build
sw_nb_v6.o only when CONFIG_IPV6 is set, and extend sw_nb.c with device
filtering for Cavium ports behind bridges and VLANs.
Initialize and tear down the existing sw_fdb, sw_fib, and sw_fl helpers
together with notifier registration.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../ethernet/marvell/octeontx2/nic/Makefile | 6 +-
.../net/ethernet/marvell/octeontx2/nic/rep.c | 38 +-
.../marvell/octeontx2/nic/switch/sw_nb.c | 481 +++++++++++++++++-
.../marvell/octeontx2/nic/switch/sw_nb.h | 37 +-
.../marvell/octeontx2/nic/switch/sw_nb_v4.c | 338 ++++++++++++
.../marvell/octeontx2/nic/switch/sw_nb_v4.h | 21 +
.../marvell/octeontx2/nic/switch/sw_nb_v6.c | 283 +++++++++++
.../marvell/octeontx2/nic/switch/sw_nb_v6.h | 21 +
8 files changed, 1216 insertions(+), 9 deletions(-)
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
index 123b0af23abd..02ab0634f58f 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
@@ -11,7 +11,11 @@ rvu_nicpf-y := otx2_pf.o otx2_common.o otx2_txrx.o otx2_ethtool.o \
otx2_flows.o otx2_tc.o cn10k.o cn20k.o otx2_dmac_flt.o \
otx2_devlink.o qos_sq.o qos.o otx2_xsk.o \
switch/sw_fdb.o switch/sw_fl.o
-rvu_nicpf-$(CONFIG_OCTEONTX_SWITCH) += switch/sw_nb.o switch/sw_fib.o
+rvu_nicpf-$(CONFIG_OCTEONTX_SWITCH) += switch/sw_nb.o switch/sw_fib.o \
+ switch/sw_nb_v4.o
+ifneq ($(CONFIG_IPV6),)
+rvu_nicpf-$(CONFIG_OCTEONTX_SWITCH) += switch/sw_nb_v6.o
+endif
rvu_nicvf-y := otx2_vf.o
rvu_rep-y := rep.o
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
index 257a2ae6a53e..1900235fabc5 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
@@ -15,6 +15,7 @@
#include "cn10k.h"
#include "otx2_reg.h"
#include "rep.h"
+#include "switch/sw_nb.h"
#define DRV_NAME "rvu_rep"
#define DRV_STRING "Marvell RVU Representor Driver"
@@ -399,22 +400,55 @@ static void rvu_rep_get_stats64(struct net_device *dev,
static int rvu_eswitch_config(struct otx2_nic *priv, u8 ena)
{
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+ struct net_device *netdev = priv->netdev;
+#endif
struct devlink_port_attrs attrs = {};
struct esw_cfg_req *req;
+ int mbox_err;
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+ int err;
+#endif
rvu_rep_devlink_set_switch_id(priv, &attrs.switch_id);
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+ if (ena) {
+ err = sw_nb_register(netdev);
+ if (err)
+ return err;
+ }
+#endif
+
mutex_lock(&priv->mbox.lock);
req = otx2_mbox_alloc_msg_esw_cfg(&priv->mbox);
if (!req) {
mutex_unlock(&priv->mbox.lock);
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+ if (ena)
+ sw_nb_unregister(netdev);
+#endif
return -ENOMEM;
}
req->ena = ena;
memcpy(req->switch_id, attrs.switch_id.id, attrs.switch_id.id_len);
- otx2_sync_mbox_msg(&priv->mbox);
+ mbox_err = otx2_sync_mbox_msg(&priv->mbox);
mutex_unlock(&priv->mbox.lock);
- return 0;
+
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+ if (ena && mbox_err) {
+ sw_nb_unregister(netdev);
+ return mbox_err;
+ }
+
+ if (!ena) {
+ err = sw_nb_unregister(netdev);
+ if (err && !mbox_err)
+ return err;
+ }
+#endif
+
+ return mbox_err;
}
static netdev_tx_t rvu_rep_xmit(struct sk_buff *skb, struct net_device *dev)
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
index 2d14a0590c5d..bffe2af003c7 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
@@ -4,14 +4,491 @@
* Copyright (C) 2026 Marvell.
*
*/
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+#include <net/route.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
#include "sw_nb.h"
+#include "sw_fdb.h"
+#include "sw_fib.h"
+#include "sw_fl.h"
+#include "sw_nb_v4.h"
+#include "sw_nb_v6.h"
+
+/* PF netdev for netdev_* logging when notifier info has no device */
+static struct net_device *sw_nb_pf_netdev;
+static bool sw_nb_registered;
+
+static const char *sw_nb_cmd2str[OTX2_CMD_MAX] = {
+ [OTX2_DEV_UP] = "OTX2_DEV_UP",
+ [OTX2_DEV_DOWN] = "OTX2_DEV_DOWN",
+ [OTX2_DEV_CHANGE] = "OTX2_DEV_CHANGE",
+ [OTX2_NEIGH_UPDATE] = "OTX2_NEIGH_UPDATE",
+ [OTX2_FIB_ENTRY_REPLACE] = "OTX2_FIB_ENTRY_REPLACE",
+ [OTX2_FIB_ENTRY_ADD] = "OTX2_FIB_ENTRY_ADD",
+ [OTX2_FIB_ENTRY_DEL] = "OTX2_FIB_ENTRY_DEL",
+ [OTX2_FIB_ENTRY_APPEND] = "OTX2_FIB_ENTRY_APPEND",
+};
+
+const char *sw_nb_get_cmd2str(int cmd)
+{
+ return sw_nb_cmd2str[cmd];
+}
+EXPORT_SYMBOL(sw_nb_get_cmd2str);
+
+bool sw_nb_is_cavium_dev(struct net_device *netdev)
+{
+ struct pci_dev *pdev;
+ struct device *dev;
+
+ dev = netdev->dev.parent;
+ if (!dev || dev->bus != &pci_bus_type)
+ return false;
+
+ pdev = to_pci_dev(dev);
+ if (pdev->vendor != PCI_VENDOR_ID_CAVIUM)
+ return false;
+
+ return true;
+}
+
+struct net_device *sw_nb_resolve_pf_dev(struct net_device *dev)
+{
+ struct net_device *lower, *pf_dev = dev;
+ struct list_head *iter;
-int sw_nb_unregister(void)
+ if (netif_is_bridge_master(dev)) {
+ netdev_for_each_lower_dev(dev, lower, iter) {
+ pf_dev = lower;
+ break;
+ }
+ } else if (is_vlan_dev(dev)) {
+ pf_dev = vlan_dev_real_dev(dev);
+ }
+
+ if (!sw_nb_is_cavium_dev(pf_dev))
+ return NULL;
+
+ return pf_dev;
+}
+
+static int sw_nb_check_slaves(struct net_device *dev,
+ struct netdev_nested_priv *priv)
{
+ int *cnt;
+
+ if (!priv->flags)
+ return 0;
+
+ priv->flags &= sw_nb_is_cavium_dev(dev);
+ if (priv->flags) {
+ cnt = priv->data;
+ (*cnt)++;
+ }
+
return 0;
}
-int sw_nb_register(void)
+bool sw_nb_is_valid_dev(struct net_device *netdev)
+{
+ struct netdev_nested_priv priv;
+ struct net_device *br;
+ int cnt = 0;
+ bool valid;
+
+ priv.flags = true;
+ priv.data = &cnt;
+
+ rcu_read_lock();
+
+ if (netif_is_bridge_master(netdev) || is_vlan_dev(netdev)) {
+ netdev_walk_all_lower_dev_rcu(netdev, sw_nb_check_slaves, &priv);
+ valid = priv.flags && cnt;
+ rcu_read_unlock();
+ return valid;
+ }
+
+ if (netif_is_bridge_port(netdev)) {
+ br = netdev_master_upper_dev_get_rcu(netdev);
+ if (!br) {
+ rcu_read_unlock();
+ return false;
+ }
+ netdev_walk_all_lower_dev_rcu(br, sw_nb_check_slaves, &priv);
+ valid = priv.flags && cnt;
+ rcu_read_unlock();
+ return valid;
+ }
+
+ rcu_read_unlock();
+
+ return sw_nb_is_cavium_dev(netdev);
+}
+
+static int sw_nb_fdb_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ struct switchdev_notifier_fdb_info *fdb_info = ptr;
+
+ if (!sw_nb_is_valid_dev(dev))
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ if (fdb_info->is_local)
+ break;
+ break;
+
+ case SWITCHDEV_FDB_DEL_TO_DEVICE:
+ if (fdb_info->is_local)
+ break;
+ break;
+
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sw_nb_fdb = {
+ .notifier_call = sw_nb_fdb_event,
+};
+
+static void __maybe_unused
+sw_nb_fib_event_dump(unsigned long event, void *ptr)
+{
+ struct fib_entry_notifier_info *fen_info = ptr;
+ struct net_device *log_dev;
+ struct fib_nh *fib_nh;
+ struct fib_info *fi;
+ int i;
+
+ fi = fen_info->fi;
+ log_dev = (fi && fi->fib_nhs) ? fi->fib_nh->fib_nh_dev : sw_nb_pf_netdev;
+ if (log_dev)
+ netdev_info(log_dev, "%s: FIB event=%lu dst=%pI4 dstlen=%u type=%u\n",
+ __func__, event, (const __be32 *)&fen_info->dst,
+ fen_info->dst_len, fen_info->type);
+
+ if (!fi)
+ return;
+
+ fib_nh = fi->fib_nh;
+ for (i = 0; i < fi->fib_nhs; i++, fib_nh++) {
+ if (!fib_nh->fib_nh_dev)
+ continue;
+ netdev_info(fib_nh->fib_nh_dev,
+ "%s: dev=%s saddr=%pI4 gw=%pI4\n",
+ __func__, fib_nh->fib_nh_dev->name,
+ &fib_nh->nh_saddr, &fib_nh->fib_nh_gw4);
+ }
+}
+
+#define SWITCH_NB_FIB_EVENT_DUMP(...) \
+ sw_nb_fib_event_dump(__VA_ARGS__)
+
+int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev)
+{
+ switch (event) {
+ case FIB_EVENT_ENTRY_REPLACE:
+ return OTX2_FIB_ENTRY_REPLACE;
+ case FIB_EVENT_ENTRY_ADD:
+ return OTX2_FIB_ENTRY_ADD;
+ case FIB_EVENT_ENTRY_DEL:
+ return OTX2_FIB_ENTRY_DEL;
+ default:
+ break;
+ }
+
+ netdev_err(netdev, "Wrong FIB event %d\n", event);
+ return -1;
+}
+
+static int sw_nb_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct fib_notifier_info *info = ptr;
+
+ switch (event) {
+ case FIB_EVENT_ENTRY_REPLACE:
+ case FIB_EVENT_ENTRY_ADD:
+ case FIB_EVENT_ENTRY_DEL:
+ break;
+ default:
+ if (sw_nb_pf_netdev)
+ netdev_dbg(sw_nb_pf_netdev,
+ "%s: Won't process FIB event %lu\n",
+ __func__, event);
+ return NOTIFY_DONE;
+ }
+
+ switch (info->family) {
+ case AF_INET:
+ return sw_nb_v4_fib_event(nb, event, ptr);
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+ return sw_nb_v6_fib_event(nb, event, ptr);
+#endif
+ default:
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sw_nb_fib = {
+ .notifier_call = sw_nb_fib_event,
+};
+
+static int sw_nb_net_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct neighbour *n = ptr;
+
+ if (!sw_nb_is_valid_dev(n->dev))
+ return NOTIFY_DONE;
+
+ if (event != NETEVENT_NEIGH_UPDATE)
+ return NOTIFY_DONE;
+
+ switch (n->tbl->family) {
+ case AF_INET:
+ return sw_nb_net_v4_neigh_update(nb, event, ptr);
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+ return sw_nb_net_v6_neigh_update(nb, event, ptr);
+#endif
+ default:
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sw_nb_netevent = {
+ .notifier_call = sw_nb_net_event,
+
+};
+
+int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev)
+{
+ switch (event) {
+ case NETDEV_CHANGE:
+ return OTX2_DEV_CHANGE;
+ case NETDEV_UP:
+ return OTX2_DEV_UP;
+ case NETDEV_DOWN:
+ return OTX2_DEV_DOWN;
+ default:
+ break;
+ }
+ netdev_dbg(netdev, "%s: Wrong interaddr event %d\n",
+ __func__, event);
+ return -1;
+}
+
+static struct notifier_block sw_nb_v4_inetaddr = {
+ .notifier_call = sw_nb_v4_inetaddr_event,
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static struct notifier_block sw_nb_v6_inetaddr = {
+ .notifier_call = sw_nb_v6_inetaddr_event,
+};
+#endif
+
+static int sw_nb_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct in_device *idev;
+ struct inet6_dev *i6dev;
+
+ if (event != NETDEV_CHANGE &&
+ event != NETDEV_UP &&
+ event != NETDEV_DOWN) {
+ return NOTIFY_DONE;
+ }
+
+ if (!sw_nb_is_valid_dev(dev))
+ return NOTIFY_DONE;
+
+ idev = __in_dev_get_rtnl(dev);
+ if (idev)
+ sw_nb_v4_netdev_event(unused, event, ptr);
+
+#if IS_ENABLED(CONFIG_IPV6)
+ i6dev = __in6_dev_get(dev);
+ if (i6dev)
+ sw_nb_v6_netdev_event(unused, event, ptr);
+#endif
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sw_nb_netdev = {
+ .notifier_call = sw_nb_netdev_event,
+};
+
+int sw_nb_unregister(struct net_device *netdev)
+{
+ int err, ret = 0;
+
+ if (!sw_nb_registered)
+ return 0;
+
+ err = unregister_switchdev_notifier(&sw_nb_fdb);
+ if (err) {
+ netdev_err(netdev, "Failed to unregister switchdev nb\n");
+ ret = err;
+ }
+
+ err = unregister_fib_notifier(&init_net, &sw_nb_fib);
+ if (err) {
+ netdev_err(netdev, "Failed to unregister fib nb\n");
+ if (!ret)
+ ret = err;
+ }
+
+ err = unregister_netevent_notifier(&sw_nb_netevent);
+ if (err) {
+ netdev_err(netdev, "Failed to unregister netevent\n");
+ if (!ret)
+ ret = err;
+ }
+
+ err = unregister_inetaddr_notifier(&sw_nb_v4_inetaddr);
+ if (err) {
+ netdev_err(netdev, "Failed to unregister addr event\n");
+ if (!ret)
+ ret = err;
+ }
+
+#if IS_ENABLED(CONFIG_IPV6)
+ err = unregister_inet6addr_notifier(&sw_nb_v6_inetaddr);
+ if (err) {
+ netdev_err(netdev, "Failed to unregister addr event\n");
+ if (!ret)
+ ret = err;
+ }
+#endif
+
+ err = unregister_netdevice_notifier(&sw_nb_netdev);
+ if (err) {
+ netdev_err(netdev, "Failed to unregister netdev notifier\n");
+ if (!ret)
+ ret = err;
+ }
+
+ sw_fl_deinit();
+ sw_fib_deinit();
+ sw_fdb_deinit();
+
+ sw_nb_pf_netdev = NULL;
+ sw_nb_registered = false;
+
+ return ret;
+}
+EXPORT_SYMBOL(sw_nb_unregister);
+
+int sw_nb_register(struct net_device *netdev)
+{
+ int err;
+
+ if (sw_nb_registered)
+ return -EBUSY;
+
+ sw_nb_pf_netdev = netdev;
+
+ err = sw_fdb_init();
+ if (err)
+ goto err_clear;
+
+ err = sw_fib_init();
+ if (err)
+ goto err_fdb;
+
+ err = sw_fl_init();
+ if (err)
+ goto err_fib;
+
+ err = register_switchdev_notifier(&sw_nb_fdb);
+ if (err) {
+ netdev_err(netdev, "Failed to register switchdev nb\n");
+ goto err_helpers;
+ }
+
+ err = register_fib_notifier(&init_net, &sw_nb_fib, NULL, NULL);
+ if (err) {
+ netdev_err(netdev, "Failed to register fb notifier block\n");
+ goto err1;
+ }
+
+ err = register_netevent_notifier(&sw_nb_netevent);
+ if (err) {
+ netdev_err(netdev, "Failed to register netevent\n");
+ goto err2;
+ }
+
+#if IS_ENABLED(CONFIG_IPV6)
+ err = register_inet6addr_notifier(&sw_nb_v6_inetaddr);
+ if (err) {
+ netdev_err(netdev, "Failed to register addr event\n");
+ goto err3;
+ }
+#endif
+
+ err = register_inetaddr_notifier(&sw_nb_v4_inetaddr);
+ if (err) {
+ netdev_err(netdev, "Failed to register addr event\n");
+ goto err4;
+ }
+
+ err = register_netdevice_notifier(&sw_nb_netdev);
+ if (err) {
+ netdev_err(netdev, "Failed to register netdevice nb\n");
+ goto err5;
+ }
+
+ sw_nb_registered = true;
+
return 0;
+
+err5:
+ unregister_inetaddr_notifier(&sw_nb_v4_inetaddr);
+
+err4:
+#if IS_ENABLED(CONFIG_IPV6)
+ unregister_inet6addr_notifier(&sw_nb_v6_inetaddr);
+
+err3:
+#endif
+ unregister_netevent_notifier(&sw_nb_netevent);
+
+err2:
+ unregister_fib_notifier(&init_net, &sw_nb_fib);
+
+err1:
+ unregister_switchdev_notifier(&sw_nb_fdb);
+
+err_helpers:
+ sw_fl_deinit();
+err_fib:
+ sw_fib_deinit();
+err_fdb:
+ sw_fdb_deinit();
+err_clear:
+ sw_nb_pf_netdev = NULL;
+ return err;
}
+EXPORT_SYMBOL(sw_nb_register);
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
index 73cc1e99b8ec..e995c0e6046b 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
@@ -9,12 +9,41 @@
#include <linux/kconfig.h>
+struct net_device;
+struct otx2_nic;
+struct af2pf_fdb_refresh_req;
+struct msg_rsp;
+
#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
-int sw_nb_register(void);
-int sw_nb_unregister(void);
+enum {
+ OTX2_DEV_UP = 1,
+ OTX2_DEV_DOWN,
+ OTX2_DEV_CHANGE,
+ OTX2_NEIGH_UPDATE,
+ OTX2_FIB_ENTRY_REPLACE,
+ OTX2_FIB_ENTRY_ADD,
+ OTX2_FIB_ENTRY_DEL,
+ OTX2_FIB_ENTRY_APPEND,
+ OTX2_CMD_MAX,
+};
+
+int sw_nb_register(struct net_device *netdev);
+int sw_nb_unregister(struct net_device *netdev);
+bool sw_nb_is_valid_dev(struct net_device *netdev);
+struct net_device *sw_nb_resolve_pf_dev(struct net_device *dev);
+
+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp);
+
+bool sw_nb_is_cavium_dev(struct net_device *netdev);
+int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev);
+int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev);
+
+const char *sw_nb_get_cmd2str(int cmd);
#else
-static inline int sw_nb_register(void) { return 0; }
-static inline int sw_nb_unregister(void) { return 0; }
+static inline int sw_nb_register(struct net_device *netdev) { return 0; }
+static inline int sw_nb_unregister(struct net_device *netdev) { return 0; }
#endif
#endif /* SW_NB_H_ */
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
new file mode 100644
index 000000000000..0e7006e507fd
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+#include <net/route.h>
+#include <linux/inetdevice.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
+#include "sw_fdb.h"
+#include "sw_fib.h"
+#include "sw_fl.h"
+#include "sw_nb_v4.h"
+
+int sw_nb_v4_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct netdev_hw_addr *dev_addr;
+ struct net_device *pf_dev;
+ struct in_device *idev;
+ struct in_ifaddr *ifa;
+ struct fib_entry *entry;
+ struct otx2_nic *pf;
+
+ idev = __in_dev_get_rtnl(dev);
+ if (!idev || !idev->ifa_list)
+ return NOTIFY_DONE;
+
+ ifa = rtnl_dereference(idev->ifa_list);
+
+ entry = kcalloc(1, sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+ entry->dst = ifa->ifa_address;
+ entry->dst_len = 32;
+ entry->mac_valid = 1;
+ entry->host = 1;
+
+ pf_dev = sw_nb_resolve_pf_dev(dev);
+ if (!pf_dev) {
+ kfree(entry);
+ return NOTIFY_DONE;
+ }
+
+ if (netif_is_bridge_master(dev)) {
+ entry->bridge = 1;
+ } else if (is_vlan_dev(dev)) {
+ entry->vlan_valid = 1;
+ entry->vlan_tag = cpu_to_be16(vlan_dev_vlan_id(dev));
+ }
+
+ pf = netdev_priv(pf_dev);
+ entry->port_id = pf->pcifunc;
+
+ for_each_dev_addr(dev, dev_addr) {
+ ether_addr_copy(entry->mac, dev_addr->addr);
+ break;
+ }
+
+ netdev_dbg(dev, "%s: pushing netdev event from HOST interface address %pI4, %pM, dev=%s\n",
+ __func__, &entry->dst, entry->mac, dev->name);
+ kfree(entry);
+
+ return NOTIFY_DONE;
+}
+
+int sw_nb_v4_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+ struct net_device *dev = ifa->ifa_dev->dev;
+ struct netdev_hw_addr *dev_addr;
+ struct net_device *pf_dev;
+ struct in_device *idev;
+ struct fib_entry *entry;
+ struct otx2_nic *pf;
+
+ if (event != NETDEV_CHANGE &&
+ event != NETDEV_UP &&
+ event != NETDEV_DOWN) {
+ return NOTIFY_DONE;
+ }
+
+ if (!sw_nb_is_valid_dev(dev))
+ return NOTIFY_DONE;
+
+ idev = __in_dev_get_rtnl(dev);
+ if (!idev || !idev->ifa_list)
+ return NOTIFY_DONE;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+ entry->dst = ifa->ifa_address;
+ entry->dst_len = 32;
+ entry->mac_valid = 1;
+ entry->host = 1;
+
+ pf_dev = sw_nb_resolve_pf_dev(dev);
+ if (!pf_dev) {
+ kfree(entry);
+ return NOTIFY_DONE;
+ }
+
+ if (netif_is_bridge_master(dev)) {
+ entry->bridge = 1;
+ } else if (is_vlan_dev(dev)) {
+ entry->vlan_valid = 1;
+ entry->vlan_tag = cpu_to_be16(vlan_dev_vlan_id(dev));
+ }
+
+ pf = netdev_priv(pf_dev);
+ entry->port_id = pf->pcifunc;
+
+ for_each_dev_addr(dev, dev_addr) {
+ ether_addr_copy(entry->mac, dev_addr->addr);
+ break;
+ }
+
+ netdev_dbg(dev, "%s: pushing inetaddr event from HOST interface address %pI4, %pM, %s\n",
+ __func__, &entry->dst, entry->mac, dev->name);
+
+ kfree(entry);
+ return NOTIFY_DONE;
+}
+
+int sw_nb_v4_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct fib_entry_notifier_info *fen_info = ptr;
+ struct net_device *dev, *pf_dev = NULL;
+ struct fib_entry *entries, *iter;
+ struct netdev_hw_addr *dev_addr;
+ struct neighbour *neigh;
+ struct fib_nh *fib_nh;
+ struct fib_info *fi;
+ struct otx2_nic *pf;
+ __be32 *haddr;
+ int hcnt = 0;
+ int cnt, i;
+
+ /* Process only UNICAST routes add or del */
+ if (fen_info->type != RTN_UNICAST)
+ return NOTIFY_DONE;
+
+ fi = fen_info->fi;
+ if (!fi)
+ return NOTIFY_DONE;
+
+ if (fi->fib_nh_is_v6) {
+ struct net_device *log_dev = (fi->fib_nhs > 0) ?
+ fi->fib_nh->fib_nh_dev : NULL;
+
+ if (log_dev)
+ netdev_dbg(log_dev, "%s: Received v6 notification\n",
+ __func__);
+ return NOTIFY_DONE;
+ }
+
+ entries = kcalloc(fi->fib_nhs, sizeof(*entries), GFP_ATOMIC);
+ if (!entries)
+ return NOTIFY_DONE;
+
+ haddr = kcalloc(fi->fib_nhs, sizeof(*haddr), GFP_ATOMIC);
+ if (!haddr) {
+ kfree(entries);
+ return NOTIFY_DONE;
+ }
+
+ iter = entries;
+ fib_nh = fi->fib_nh;
+ for (i = 0; i < fi->fib_nhs; i++, fib_nh++) {
+ dev = fib_nh->fib_nh_dev;
+
+ if (!dev)
+ continue;
+
+ if (dev->type != ARPHRD_ETHER)
+ continue;
+
+ if (!sw_nb_is_valid_dev(dev))
+ continue;
+
+ iter->cmd = sw_nb_fib_event_to_otx2_event(event, dev);
+ iter->dst = (__force __be32)fen_info->dst;
+ iter->dst_len = fen_info->dst_len;
+ iter->gw = fib_nh->fib_nh_gw4;
+
+ netdev_dbg(dev, "%s: FIB route Rule cmd=%llu dst=%pI4 dst_len=%u gw=%pI4\n",
+ __func__, iter->cmd, &iter->dst, iter->dst_len, &iter->gw);
+
+ pf_dev = sw_nb_resolve_pf_dev(dev);
+ if (!pf_dev) {
+ iter++;
+ continue;
+ }
+
+ if (netif_is_bridge_master(dev)) {
+ iter->bridge = 1;
+ } else if (is_vlan_dev(dev)) {
+ iter->vlan_valid = 1;
+ iter->vlan_tag = cpu_to_be16(vlan_dev_vlan_id(dev));
+ }
+
+ pf = netdev_priv(pf_dev);
+ iter->port_id = pf->pcifunc;
+
+ if (!fib_nh->fib_nh_gw4) {
+ if (iter->dst || iter->dst_len)
+ iter++;
+
+ continue;
+ }
+ iter->gw_valid = 1;
+
+ if (fib_nh->nh_saddr)
+ haddr[hcnt++] = fib_nh->nh_saddr;
+
+ rcu_read_lock();
+ neigh = ip_neigh_gw4(fib_nh->fib_nh_dev, fib_nh->fib_nh_gw4);
+ if (!neigh) {
+ rcu_read_unlock();
+ iter++;
+ continue;
+ }
+
+ if (is_valid_ether_addr(neigh->ha)) {
+ iter->mac_valid = 1;
+ neigh_ha_snapshot(iter->mac, neigh, fib_nh->fib_nh_dev);
+ }
+
+ iter++;
+ rcu_read_unlock();
+ }
+
+ cnt = iter - entries;
+ if (!cnt) {
+ kfree(entries);
+ kfree(haddr);
+ return NOTIFY_DONE;
+ }
+
+ if (pf_dev)
+ netdev_dbg(pf_dev, "pf_dev is %s cnt=%d\n", pf_dev->name, cnt);
+ kfree(entries);
+
+ if (!hcnt) {
+ kfree(haddr);
+ return NOTIFY_DONE;
+ }
+
+ entries = kcalloc(hcnt, sizeof(*entries), GFP_ATOMIC);
+ if (!entries) {
+ kfree(haddr);
+ return NOTIFY_DONE;
+ }
+
+ iter = entries;
+
+ for (i = 0; i < hcnt; i++, iter++) {
+ iter->cmd = sw_nb_fib_event_to_otx2_event(event, pf_dev);
+ iter->dst = haddr[i];
+ iter->dst_len = 32;
+ iter->mac_valid = 1;
+ iter->host = 1;
+ iter->port_id = pf->pcifunc;
+
+ for_each_dev_addr(pf_dev, dev_addr) {
+ ether_addr_copy(iter->mac, dev_addr->addr);
+ break;
+ }
+
+ netdev_dbg(pf_dev, "%s: FIB host Rule cmd=%llu dst=%pI4 dst_len=%u gw=%pI4 %s\n",
+ __func__, iter->cmd, &iter->dst, iter->dst_len, &iter->gw,
+ pf_dev->name);
+ }
+ kfree(entries);
+ kfree(haddr);
+ return NOTIFY_DONE;
+}
+
+int sw_nb_net_v4_neigh_update(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *pf_dev;
+ struct neighbour *n = ptr;
+ struct fib_entry *entry;
+ struct otx2_nic *pf;
+
+ if (n->tbl != &arp_tbl)
+ return NOTIFY_DONE;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ entry->cmd = OTX2_NEIGH_UPDATE;
+ entry->dst = *(__be32 *)n->primary_key;
+ entry->dst_len = n->tbl->key_len * 8;
+ entry->mac_valid = 1;
+ entry->nud_state = n->nud_state;
+ neigh_ha_snapshot(entry->mac, n, n->dev);
+
+ pf_dev = sw_nb_resolve_pf_dev(n->dev);
+ if (!pf_dev) {
+ kfree(entry);
+ return NOTIFY_DONE;
+ }
+
+ if (netif_is_bridge_master(n->dev)) {
+ entry->bridge = 1;
+ } else if (is_vlan_dev(n->dev)) {
+ entry->vlan_valid = 1;
+ entry->vlan_tag = cpu_to_be16(vlan_dev_vlan_id(n->dev));
+ }
+
+ pf = netdev_priv(pf_dev);
+ entry->port_id = pf->pcifunc;
+
+ kfree(entry);
+ return NOTIFY_DONE;
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h
new file mode 100644
index 000000000000..c6dbf4b93a9a
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_NB_V4_H_
+#define SW_NB_V4_H_
+
+int sw_nb_v4_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_net_v4_neigh_update(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_v4_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_v4_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr);
+#endif // SW_NB_V4_H__
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
new file mode 100644
index 000000000000..0d29c5526df8
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+#include <net/route.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+#include <net/ip6_fib.h>
+#include <net/nexthop.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
+#include "sw_fdb.h"
+#include "sw_fib.h"
+#include "sw_fl.h"
+#include "sw_nb_v6.h"
+
+#if IS_ENABLED(CONFIG_IPV6)
+
+int sw_nb_v6_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct netdev_hw_addr *dev_addr;
+ struct net_device *pf_dev;
+ struct inet6_ifaddr *ifp;
+ struct inet6_dev *i6dev;
+ struct fib_entry *entry;
+ struct in6_addr addr;
+ struct otx2_nic *pf;
+ u32 prefix_len;
+
+ i6dev = __in6_dev_get(dev);
+ if (!i6dev)
+ return NOTIFY_DONE;
+
+ rcu_read_lock();
+ ifp = list_first_entry_or_null(&i6dev->addr_list,
+ struct inet6_ifaddr, if_list);
+ if (!ifp) {
+ rcu_read_unlock();
+ return NOTIFY_DONE;
+ }
+
+ if (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) {
+ rcu_read_unlock();
+ return NOTIFY_DONE;
+ }
+
+ addr = ifp->addr;
+ prefix_len = ifp->prefix_len;
+ rcu_read_unlock();
+
+ entry = kcalloc(1, sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ pf_dev = sw_nb_resolve_pf_dev(dev);
+ if (!pf_dev) {
+ kfree(entry);
+ return NOTIFY_DONE;
+ }
+
+ entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+ memcpy(entry->dst6, &addr, sizeof(entry->dst6));
+ entry->dst6_plen = prefix_len;
+ entry->host = 1;
+ entry->ipv6 = 1;
+
+ pf = netdev_priv(pf_dev);
+ entry->port_id = pf->pcifunc;
+
+ for_each_dev_addr(dev, dev_addr) {
+ entry->mac_valid = 1;
+ ether_addr_copy(entry->mac, dev_addr->addr);
+ break;
+ }
+
+ netdev_dbg(dev, "netdev event addr=%pI6c plen=%u mac=%pM\n",
+ &addr, prefix_len, entry->mac);
+ kfree(entry);
+ return NOTIFY_DONE;
+}
+
+int sw_nb_v6_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct fib6_entry_notifier_info *f6_eni;
+ struct fib_notifier_info *info = ptr;
+ struct net_device *fib_dev, *pf_dev;
+ struct fib_entry *entry;
+ struct fib6_info *f6i;
+ struct neighbour *neigh;
+ struct fib6_nh *nh6;
+ struct rt6key *key;
+ struct otx2_nic *pf;
+
+ f6_eni = container_of(info, struct fib6_entry_notifier_info, info);
+ f6i = f6_eni->rt;
+
+ fib_dev = fib6_info_nh_dev(f6i);
+
+ if (!fib_dev)
+ return NOTIFY_DONE;
+
+ if (fib_dev->type != ARPHRD_ETHER)
+ return NOTIFY_DONE;
+
+ if (!sw_nb_is_valid_dev(fib_dev))
+ return NOTIFY_DONE;
+
+ if (f6i->fib6_type != RTN_UNICAST)
+ return NOTIFY_DONE;
+
+ key = &f6i->fib6_dst;
+ /* TODO: vlan and bridge support */
+ if (ipv6_addr_type(&key->addr) & IPV6_ADDR_LINKLOCAL)
+ return NOTIFY_DONE;
+
+ netdev_dbg(fib_dev, "fib6dst rt6key.addr=%pI6c len=%u\n", &key->addr,
+ key->plen);
+
+ netdev_dbg(fib_dev, "fib6flags=%#x proto=%u type=%u\n",
+ f6i->fib6_flags, f6i->fib6_protocol, f6i->fib6_type);
+
+ nh6 = f6i->nh ? nexthop_fib6_nh(f6i->nh) : f6i->fib6_nh;
+ netdev_dbg(nh6->fib_nh_dev ? nh6->fib_nh_dev : fib_dev,
+ "nh family=%u dev=%s gw=%pI6c gwfamily=%u\n",
+ nh6->fib_nh_family,
+ nh6->fib_nh_dev ? nh6->fib_nh_dev->name : "No dev",
+ &nh6->fib_nh_gw6, nh6->fib_nh_gw_family);
+
+ pf_dev = sw_nb_resolve_pf_dev(fib_dev);
+ if (!pf_dev)
+ return NOTIFY_DONE;
+
+ pf = netdev_priv(pf_dev);
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ entry->cmd = sw_nb_fib_event_to_otx2_event(event, fib_dev);
+ entry->ipv6 = 1;
+ entry->port_id = pf->pcifunc;
+ memcpy(entry->dst6, &key->addr, sizeof(entry->dst6));
+ entry->dst6_plen = key->plen;
+
+ memcpy(entry->gw6, &nh6->fib_nh_gw6, sizeof(nh6->fib_nh_gw6));
+ entry->gw_valid = !!(ipv6_addr_type(&nh6->fib_nh_gw6) & IPV6_ADDR_UNICAST);
+
+ rcu_read_lock();
+ neigh = ip_neigh_gw6(fib_dev, &nh6->fib_nh_gw6);
+ if (!neigh) {
+ rcu_read_unlock();
+ kfree(entry);
+ return NOTIFY_DONE;
+ }
+
+ if (is_valid_ether_addr(neigh->ha)) {
+ entry->mac_valid = 1;
+ neigh_ha_snapshot(entry->mac, neigh, fib_dev);
+ netdev_dbg(fib_dev, "fib found MAC=%pM\n", entry->mac);
+ }
+
+ rcu_read_unlock();
+ kfree(entry);
+
+ return NOTIFY_DONE;
+}
+
+int sw_nb_net_v6_neigh_update(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *pf_dev;
+ struct neighbour *n = ptr;
+ struct fib_entry *entry;
+ struct otx2_nic *pf;
+
+ if (n->tbl != &nd_tbl)
+ return NOTIFY_DONE;
+
+ if (ipv6_addr_type((struct in6_addr *)n->primary_key) & IPV6_ADDR_LINKLOCAL)
+ return NOTIFY_DONE;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ pf_dev = sw_nb_resolve_pf_dev(n->dev);
+ if (!pf_dev) {
+ kfree(entry);
+ return NOTIFY_DONE;
+ }
+
+ pf = netdev_priv(pf_dev);
+
+ entry->cmd = OTX2_NEIGH_UPDATE;
+ entry->dst6_plen = n->tbl->key_len * 8;
+ memcpy(entry->dst6, (struct in6_addr *)n->primary_key,
+ sizeof(entry->dst6));
+ entry->ipv6 = 1;
+ entry->nud_state = n->nud_state;
+ neigh_ha_snapshot(entry->mac, n, n->dev);
+ entry->mac_valid = 1;
+ entry->port_id = pf->pcifunc;
+
+ netdev_dbg(n->dev, "v6 neigh update %pI6c mac=%pM plen=%u\n",
+ n->primary_key, entry->mac, n->tbl->key_len * 8);
+ kfree(entry);
+
+ return NOTIFY_DONE;
+}
+
+int sw_nb_v6_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct inet6_ifaddr *ifa6 = (struct inet6_ifaddr *)ptr;
+ struct net_device *dev = ifa6->idev->dev;
+ struct netdev_hw_addr *dev_addr;
+ struct net_device *pf_dev;
+ struct fib_entry *entry;
+ struct otx2_nic *pf;
+
+ if (event != NETDEV_CHANGE &&
+ event != NETDEV_UP &&
+ event != NETDEV_DOWN) {
+ return NOTIFY_DONE;
+ }
+
+ if (dev->type != ARPHRD_ETHER)
+ return NOTIFY_DONE;
+
+ if (!sw_nb_is_valid_dev(dev))
+ return NOTIFY_DONE;
+
+ if (ipv6_addr_type(&ifa6->addr) & IPV6_ADDR_LINKLOCAL)
+ return NOTIFY_DONE;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ pf_dev = sw_nb_resolve_pf_dev(dev);
+ if (!pf_dev) {
+ kfree(entry);
+ return NOTIFY_DONE;
+ }
+
+ pf = netdev_priv(pf_dev);
+
+ entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+ memcpy(entry->dst6, &ifa6->addr, sizeof(entry->dst6));
+ entry->dst6_plen = ifa6->prefix_len;
+ entry->mac_valid = 1;
+ entry->host = 1;
+ entry->ipv6 = 1;
+ entry->port_id = pf->pcifunc;
+
+ for_each_dev_addr(dev, dev_addr) {
+ ether_addr_copy(entry->mac, dev_addr->addr);
+ entry->mac_valid = 1;
+ break;
+ }
+
+ netdev_dbg(dev, "inetaddr addr=%pI6c len=%u %pM\n",
+ &ifa6->addr, ifa6->prefix_len, entry->mac);
+ kfree(entry);
+
+ return NOTIFY_DONE;
+}
+#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
new file mode 100644
index 000000000000..f73efc98c311
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_NB_V6_H_
+#define SW_NB_V6_H_
+
+int sw_nb_v6_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_net_v6_neigh_update(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_v6_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_v6_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr);
+#endif // SW_NB_V6_H__
--
2.43.0
^ permalink raw reply related
* [PATCH v3 net-next 7/9] octeontx2: switch: plumb bridge FDB updates through AF and switchdev
From: Ratheesh Kannoth @ 2026-07-14 1:53 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260714015331.1801922-1-rkannoth@marvell.com>
Handle switchdev FDB add and delete notifications on the PF by queuing
work that sends fdb_notify mailbox messages to the AF. The AF queues
those updates and pushes L2 rules toward the switchdev image with
af2swdev notify messages when firmware is ready.
Teach the AF swdev2af path to initialize L2 offload workqueues on
firmware up/down and to accept refresh requests that enqueue FDB
entries for AF to PF mailbox delivery. Add an AF to PF (and VF) upstream
message for FDB refresh, handle it in the VF driver, and treat it like
the CGX link event when acknowledging mailbox completion in the AF.
On refresh, invoke the switchdev notifier so the host bridge can learn
the updated FDB entry.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../net/ethernet/marvell/octeontx2/af/mbox.h | 2 +
.../net/ethernet/marvell/octeontx2/af/rvu.c | 2 +
.../marvell/octeontx2/af/switch/rvu_sw.c | 51 +-
.../marvell/octeontx2/af/switch/rvu_sw.h | 1 +
.../marvell/octeontx2/af/switch/rvu_sw_l2.c | 486 ++++++++++++++++++
.../marvell/octeontx2/af/switch/rvu_sw_l2.h | 3 +
.../ethernet/marvell/octeontx2/nic/otx2_pf.c | 2 +
.../ethernet/marvell/octeontx2/nic/otx2_vf.c | 44 ++
.../marvell/octeontx2/nic/switch/sw_fdb.c | 218 ++++++++
.../marvell/octeontx2/nic/switch/sw_fdb.h | 1 +
.../marvell/octeontx2/nic/switch/sw_nb.c | 2 +
.../marvell/octeontx2/nic/switch/sw_nb.h | 8 +-
12 files changed, 815 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
index bf8edab569b1..e8cc7b68ad75 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
@@ -1996,6 +1996,7 @@ struct af2pf_fdb_refresh_req {
struct mbox_msghdr hdr;
u16 pcifunc;
u8 mac[6];
+ u64 flags;
};
struct iface_info {
@@ -2035,6 +2036,7 @@ struct fl_info {
struct swdev2af_notify_req {
struct mbox_msghdr hdr;
u64 msg_type;
+/* Mutually exclusive message selectors (not a combinable bitmask). */
#define SWDEV2AF_MSG_TYPE_FW_STATUS BIT_ULL(0)
#define SWDEV2AF_MSG_TYPE_REFRESH_FDB BIT_ULL(1)
#define SWDEV2AF_MSG_TYPE_REFRESH_FL BIT_ULL(2)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
index 532c2b69fb85..217ce85033ac 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
@@ -23,6 +23,7 @@
#include "cn20k/reg.h"
#include "cn20k/api.h"
#include "cn20k/npc.h"
+#include "switch/rvu_sw.h"
#define DRV_NAME "rvu_af"
#define DRV_STRING "Marvell OcteonTX2 RVU Admin Function Driver"
@@ -3859,6 +3860,7 @@ static void rvu_remove(struct pci_dev *pdev)
rvu_cgx_exit(rvu);
rvu_fwdata_exit(rvu);
rvu_mcs_exit(rvu);
+ rvu_sw_shutdown();
rvu_mbox_destroy(&rvu->afpf_wq_info);
rvu_disable_sriov(rvu);
rvu_reset_all_blocks(rvu);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
index 403d57870efe..b9cd7c7524b9 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
@@ -9,6 +9,8 @@
#include "rvu.h"
#include "rvu_sw.h"
+#include "rvu_sw_l2.h"
+#include "rvu_sw_fl.h"
u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc)
{
@@ -26,9 +28,56 @@ u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc)
FIELD_PREP(GENMASK_ULL(15, 0), pcifunc);
}
+static bool rvu_sw_swdev2af_msg_valid(u64 msg_type)
+{
+ return msg_type == SWDEV2AF_MSG_TYPE_FW_STATUS ||
+ msg_type == SWDEV2AF_MSG_TYPE_REFRESH_FDB ||
+ msg_type == SWDEV2AF_MSG_TYPE_REFRESH_FL;
+}
+
+static int rvu_sw_swdev2af_sender_check(struct rvu *rvu,
+ struct swdev2af_notify_req *req,
+ u64 msg_type)
+{
+ u16 sender = req->hdr.pcifunc;
+
+ if (!rvu_sw_swdev2af_msg_valid(msg_type))
+ return -EINVAL;
+
+ if (!rvu_is_switch_pcifunc(rvu, sender))
+ return -EPERM;
+
+ return 0;
+}
+
int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,
struct swdev2af_notify_req *req,
struct msg_rsp *rsp)
{
- return 0;
+ int rc;
+
+ rc = rvu_sw_swdev2af_sender_check(rvu, req, req->msg_type);
+ if (rc)
+ return rc;
+
+ switch (req->msg_type) {
+ case SWDEV2AF_MSG_TYPE_FW_STATUS:
+ rc = rvu_sw_l2_init_offl_wq(rvu, req->hdr.pcifunc, req->fw_up);
+ break;
+
+ case SWDEV2AF_MSG_TYPE_REFRESH_FDB:
+ rc = rvu_sw_l2_fdb_list_entry_add(rvu, req->pcifunc, req->mac);
+ break;
+
+ default:
+ rc = -EOPNOTSUPP;
+ break;
+ }
+
+ return rc;
+}
+
+void rvu_sw_shutdown(void)
+{
+ rvu_sw_l2_shutdown();
}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
index e9ad32c84576..a0cb2a9ce7ab 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
@@ -12,5 +12,6 @@
#define RVU_SW_INVALID_PORT_ID ((u32)~0U)
u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc);
+void rvu_sw_shutdown(void);
#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
index 5f805bfa81ed..f61b2d15768b 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
@@ -4,11 +4,497 @@
* Copyright (C) 2026 Marvell.
*
*/
+
+#include <linux/bitfield.h>
#include "rvu.h"
+#include "rvu_sw.h"
+#include "rvu_sw_l2.h"
+
+#define M(_name, _id, _fn_name, _req_type, _rsp_type) \
+static struct _req_type __maybe_unused \
+*otx2_mbox_alloc_msg_ ## _fn_name(struct rvu *rvu, int devid) \
+{ \
+ struct _req_type *req; \
+ \
+ req = (struct _req_type *)otx2_mbox_alloc_msg_rsp( \
+ &rvu->afpf_wq_info.mbox_up, devid, sizeof(struct _req_type), \
+ sizeof(struct _rsp_type)); \
+ if (!req) \
+ return NULL; \
+ req->hdr.sig = OTX2_MBOX_REQ_SIG; \
+ req->hdr.id = _id; \
+ return req; \
+}
+MBOX_UP_AF2SWDEV_MESSAGES
+MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES
+#undef M
+
+#define RVU_SW_L2_LIST_MAX 4096
+
+struct l2_entry {
+ struct list_head list;
+ u64 flags;
+ u32 port_id;
+ u8 mac[ETH_ALEN];
+};
+
+static DEFINE_MUTEX(l2_offl_list_lock);
+static LIST_HEAD(l2_offl_lh);
+static atomic_t l2_offl_list_cnt = ATOMIC_INIT(0);
+
+static DEFINE_MUTEX(fdb_refresh_list_lock);
+static LIST_HEAD(fdb_refresh_lh);
+static atomic_t fdb_refresh_list_cnt = ATOMIC_INIT(0);
+
+struct rvu_sw_l2_work {
+ struct rvu *rvu;
+ struct work_struct work;
+};
+
+/* Work queue for switchdev message handling. There is only
+ * one switch HW per SoC, so one instance of each type of
+ * workqueue is enough.
+ */
+static struct rvu_sw_l2_work l2_offl_work;
+static struct workqueue_struct *rvu_sw_l2_offl_wq;
+
+static struct rvu_sw_l2_work fdb_refresh_work;
+static struct workqueue_struct *fdb_refresh_wq;
+
+static bool fw_is_up;
+static DEFINE_SPINLOCK(rvu_sw_l2_state_lock);
+
+static void rvu_sw_l2_list_cnt_warn(struct device *dev, atomic_t *cnt,
+ const char *name)
+{
+ int n = atomic_read(cnt);
+
+ if (n < 0)
+ dev_warn(dev, "L2 %s list count underflow: %d\n", name, n);
+ else if (n > RVU_SW_L2_LIST_MAX)
+ dev_warn(dev, "L2 %s list count overflow: %d (max %d)\n",
+ name, n, RVU_SW_L2_LIST_MAX);
+}
+
+static void rvu_sw_l2_list_cnt_inc(struct device *dev, atomic_t *cnt,
+ const char *name)
+{
+ atomic_inc(cnt);
+ rvu_sw_l2_list_cnt_warn(dev, cnt, name);
+}
+
+static void rvu_sw_l2_list_cnt_dec(struct device *dev, atomic_t *cnt,
+ const char *name)
+{
+ atomic_dec(cnt);
+ rvu_sw_l2_list_cnt_warn(dev, cnt, name);
+}
+
+static void rvu_sw_l2_destroy_wqs(struct rvu *rvu)
+{
+ struct workqueue_struct *offl_wq, *refresh_wq;
+ struct l2_entry *entry;
+
+ spin_lock_bh(&rvu_sw_l2_state_lock);
+ rvu->rswitch.flags &= ~RVU_SWITCH_FLAG_FW_READY;
+ rvu->rswitch.pcifunc = 0;
+ fw_is_up = false;
+ offl_wq = rvu_sw_l2_offl_wq;
+ refresh_wq = fdb_refresh_wq;
+ rvu_sw_l2_offl_wq = NULL;
+ fdb_refresh_wq = NULL;
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+ if (refresh_wq) {
+ cancel_work_sync(&fdb_refresh_work.work);
+ destroy_workqueue(refresh_wq);
+
+ mutex_lock(&fdb_refresh_list_lock);
+ rvu_sw_l2_list_cnt_warn(rvu->dev, &fdb_refresh_list_cnt,
+ "fdb refresh");
+ while (1) {
+ entry = list_first_entry_or_null(&fdb_refresh_lh,
+ struct l2_entry, list);
+ if (!entry)
+ break;
+
+ list_del_init(&entry->list);
+ kfree(entry);
+ }
+ atomic_set(&fdb_refresh_list_cnt, 0);
+ mutex_unlock(&fdb_refresh_list_lock);
+ }
+
+ if (offl_wq) {
+ cancel_work_sync(&l2_offl_work.work);
+ destroy_workqueue(offl_wq);
+
+ mutex_lock(&l2_offl_list_lock);
+ rvu_sw_l2_list_cnt_warn(rvu->dev, &l2_offl_list_cnt, "offload");
+ while (1) {
+ entry = list_first_entry_or_null(&l2_offl_lh,
+ struct l2_entry, list);
+ if (!entry)
+ break;
+
+ list_del_init(&entry->list);
+ kfree(entry);
+ }
+ atomic_set(&l2_offl_list_cnt, 0);
+ mutex_unlock(&l2_offl_list_lock);
+ }
+}
+
+/* High-frequency link state transitions or aggressive FDB
+ * aging intervals can induce rapid fdb churn. To prevent
+ * thrashing, inhibit hardware offloading of these transient
+ * forwarding states to the switching ASIC. When processing an ADD,
+ * drop a queued DELETE for the same MAC that has not yet been sent to
+ * hardware; the ADD reflects the desired final state and supersedes it.
+ */
+static void rvu_sw_l2_offl_drop_pending_del(u8 *mac)
+{
+ struct l2_entry *entry, *tmp;
+
+ mutex_lock(&l2_offl_list_lock);
+ list_for_each_entry_safe(entry, tmp, &l2_offl_lh, list) {
+ if (!ether_addr_equal(mac, entry->mac))
+ continue;
+
+ if (!(entry->flags & FDB_DEL))
+ continue;
+
+ list_del_init(&entry->list);
+ rvu_sw_l2_list_cnt_dec(l2_offl_work.rvu->dev, &l2_offl_list_cnt,
+ "offload");
+ kfree(entry);
+ break;
+ }
+ mutex_unlock(&l2_offl_list_lock);
+}
+
+static int rvu_sw_l2_offl_rule_push(struct rvu *rvu, struct l2_entry *l2_entry)
+{
+ struct af2swdev_notify_req *req;
+ int swdev_pf;
+
+ swdev_pf = rvu_get_pf(rvu->pdev, rvu->rswitch.pcifunc);
+
+ mutex_lock(&rvu->mbox_lock);
+ req = otx2_mbox_alloc_msg_af2swdev_notify(rvu, swdev_pf);
+ if (!req) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -ENOMEM;
+ }
+
+ ether_addr_copy(req->mac, l2_entry->mac);
+ req->flags = l2_entry->flags;
+ req->port_id = l2_entry->port_id;
+
+ otx2_mbox_wait_for_zero(&rvu->afpf_wq_info.mbox_up, swdev_pf);
+ otx2_mbox_msg_send_up(&rvu->afpf_wq_info.mbox_up, swdev_pf);
+
+ mutex_unlock(&rvu->mbox_lock);
+ return 0;
+}
+
+static int rvu_sw_l2_fdb_refresh_send(struct rvu *rvu, u16 pcifunc, u8 *mac)
+{
+ struct af2pf_fdb_refresh_req *req;
+ int pf, vidx;
+
+ if (!is_pf_func_valid(rvu, pcifunc))
+ return -EINVAL;
+
+ pf = rvu_get_pf(rvu->pdev, pcifunc);
+
+ mutex_lock(&rvu->mbox_lock);
+
+ if (pf) {
+ if (pf >= rvu->afpf_wq_info.mbox_up.ndevs) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -EINVAL;
+ }
+
+ req = otx2_mbox_alloc_msg_af2pf_fdb_refresh(rvu, pf);
+ if (!req) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -ENOMEM;
+ }
+
+ req->hdr.pcifunc = pcifunc;
+ ether_addr_copy(req->mac, mac);
+ req->pcifunc = pcifunc;
+ req->flags = FDB_ADD;
+
+ otx2_mbox_wait_for_zero(&rvu->afpf_wq_info.mbox_up, pf);
+ otx2_mbox_msg_send_up(&rvu->afpf_wq_info.mbox_up, pf);
+ } else {
+ vidx = pcifunc - 1;
+
+ if (vidx < 0 || vidx >= rvu->afvf_wq_info.mbox_up.ndevs) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -EINVAL;
+ }
+
+ req = (struct af2pf_fdb_refresh_req *)
+ otx2_mbox_alloc_msg_rsp(&rvu->afvf_wq_info.mbox_up, vidx,
+ sizeof(*req), sizeof(struct msg_rsp));
+ if (!req) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -ENOMEM;
+ }
+ req->hdr.sig = OTX2_MBOX_REQ_SIG;
+ req->hdr.id = MBOX_MSG_AF2PF_FDB_REFRESH;
+
+ req->hdr.pcifunc = pcifunc;
+ ether_addr_copy(req->mac, mac);
+ req->pcifunc = pcifunc;
+ req->flags = FDB_ADD;
+
+ otx2_mbox_wait_for_zero(&rvu->afvf_wq_info.mbox_up, vidx);
+ otx2_mbox_msg_send_up(&rvu->afvf_wq_info.mbox_up, vidx);
+ }
+
+ mutex_unlock(&rvu->mbox_lock);
+
+ return 0;
+}
+
+static void rvu_sw_l2_fdb_refresh_wq_handler(struct work_struct *work)
+{
+ struct rvu_sw_l2_work *fdb_work;
+ struct l2_entry *l2_entry;
+
+ fdb_work = container_of(work, struct rvu_sw_l2_work, work);
+
+ while (1) {
+ mutex_lock(&fdb_refresh_list_lock);
+ l2_entry = list_first_entry_or_null(&fdb_refresh_lh,
+ struct l2_entry, list);
+ if (!l2_entry) {
+ mutex_unlock(&fdb_refresh_list_lock);
+ return;
+ }
+
+ list_del_init(&l2_entry->list);
+ rvu_sw_l2_list_cnt_dec(fdb_work->rvu->dev, &fdb_refresh_list_cnt,
+ "fdb refresh");
+ mutex_unlock(&fdb_refresh_list_lock);
+
+ rvu_sw_l2_fdb_refresh_send(fdb_work->rvu, l2_entry->port_id,
+ l2_entry->mac);
+ kfree(l2_entry);
+ }
+}
+
+static void rvu_sw_l2_offl_rule_wq_handler(struct work_struct *work)
+{
+ struct rvu_sw_l2_work *offl_work;
+ struct l2_entry *l2_entry;
+ int budget = 16;
+ bool add_fdb;
+
+ offl_work = container_of(work, struct rvu_sw_l2_work, work);
+
+ while (budget--) {
+ mutex_lock(&l2_offl_list_lock);
+ l2_entry = list_first_entry_or_null(&l2_offl_lh, struct l2_entry, list);
+ if (!l2_entry) {
+ mutex_unlock(&l2_offl_list_lock);
+ return;
+ }
+
+ list_del_init(&l2_entry->list);
+ rvu_sw_l2_list_cnt_dec(offl_work->rvu->dev, &l2_offl_list_cnt,
+ "offload");
+ mutex_unlock(&l2_offl_list_lock);
+
+ add_fdb = !!(l2_entry->flags & FDB_ADD);
+
+ if (add_fdb)
+ rvu_sw_l2_offl_drop_pending_del(l2_entry->mac);
+
+ if (rvu_sw_l2_offl_rule_push(offl_work->rvu, l2_entry))
+ dev_err(offl_work->rvu->dev,
+ "%s: Error to push l2 rule\n",
+ __func__);
+ kfree(l2_entry);
+ }
+
+ spin_lock_bh(&rvu_sw_l2_state_lock);
+ if (rvu_sw_l2_offl_wq && atomic_read(&l2_offl_list_cnt))
+ queue_work(rvu_sw_l2_offl_wq, &l2_offl_work.work);
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+}
+
+int rvu_sw_l2_init_offl_wq(struct rvu *rvu, u16 pcifunc, bool fw_up)
+{
+ struct rvu_switch *rswitch = &rvu->rswitch;
+
+ if (!fw_up) {
+ rvu_sw_l2_destroy_wqs(rvu);
+ return 0;
+ }
+
+ spin_lock_bh(&rvu_sw_l2_state_lock);
+ if (fw_is_up && rvu_sw_l2_offl_wq && fdb_refresh_wq) {
+ rswitch->pcifunc = pcifunc;
+ rswitch->flags |= RVU_SWITCH_FLAG_FW_READY;
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+ return 0;
+ }
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+ if (rvu_sw_l2_offl_wq || fdb_refresh_wq)
+ rvu_sw_l2_destroy_wqs(rvu);
+
+ l2_offl_work.rvu = rvu;
+ INIT_WORK(&l2_offl_work.work, rvu_sw_l2_offl_rule_wq_handler);
+ rvu_sw_l2_offl_wq = alloc_workqueue("swdev_rvu_sw_l2_offl_wq", 0, 0);
+ if (!rvu_sw_l2_offl_wq) {
+ dev_err(rvu->dev, "L2 offl workqueue allocation failed\n");
+ return -ENOMEM;
+ }
+
+ fdb_refresh_work.rvu = rvu;
+ INIT_WORK(&fdb_refresh_work.work, rvu_sw_l2_fdb_refresh_wq_handler);
+ fdb_refresh_wq = alloc_workqueue("swdev_fdb_refresh_wq", 0, 0);
+ if (!fdb_refresh_wq) {
+ dev_err(rvu->dev, "fdb refresh workqueue allocation failed\n");
+ destroy_workqueue(rvu_sw_l2_offl_wq);
+ rvu_sw_l2_offl_wq = NULL;
+ return -ENOMEM;
+ }
+
+ spin_lock_bh(&rvu_sw_l2_state_lock);
+ fw_is_up = true;
+ rswitch->pcifunc = pcifunc;
+ rswitch->flags |= RVU_SWITCH_FLAG_FW_READY;
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+ return 0;
+}
+
+int rvu_sw_l2_fdb_list_entry_add(struct rvu *rvu, u16 pcifunc, u8 *mac)
+{
+ struct workqueue_struct *wq;
+ struct l2_entry *l2_entry;
+
+ if (!is_pf_func_valid(rvu, pcifunc))
+ return -EINVAL;
+
+ spin_lock_bh(&rvu_sw_l2_state_lock);
+ if (!fdb_refresh_wq) {
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+ return -EINVAL;
+ }
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+ if (atomic_read(&fdb_refresh_list_cnt) >= RVU_SW_L2_LIST_MAX) {
+ rvu_sw_l2_list_cnt_warn(rvu->dev, &fdb_refresh_list_cnt,
+ "fdb refresh");
+ return -ENOMEM;
+ }
+
+ l2_entry = kcalloc(1, sizeof(*l2_entry), GFP_KERNEL);
+ if (!l2_entry)
+ return -ENOMEM;
+
+ l2_entry->port_id = pcifunc;
+ ether_addr_copy(l2_entry->mac, mac);
+
+ mutex_lock(&fdb_refresh_list_lock);
+ if (atomic_read(&fdb_refresh_list_cnt) >= RVU_SW_L2_LIST_MAX) {
+ rvu_sw_l2_list_cnt_warn(rvu->dev, &fdb_refresh_list_cnt,
+ "fdb refresh");
+ mutex_unlock(&fdb_refresh_list_lock);
+ kfree(l2_entry);
+ return -ENOMEM;
+ }
+ list_add_tail(&l2_entry->list, &fdb_refresh_lh);
+ rvu_sw_l2_list_cnt_inc(rvu->dev, &fdb_refresh_list_cnt, "fdb refresh");
+ mutex_unlock(&fdb_refresh_list_lock);
+
+ spin_lock_bh(&rvu_sw_l2_state_lock);
+ wq = fdb_refresh_wq;
+ if (wq)
+ queue_work(wq, &fdb_refresh_work.work);
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+ if (!wq) {
+ mutex_lock(&fdb_refresh_list_lock);
+ list_del_init(&l2_entry->list);
+ rvu_sw_l2_list_cnt_dec(rvu->dev, &fdb_refresh_list_cnt,
+ "fdb refresh");
+ mutex_unlock(&fdb_refresh_list_lock);
+ kfree(l2_entry);
+ return -EINVAL;
+ }
+
+ return 0;
+}
int rvu_mbox_handler_fdb_notify(struct rvu *rvu,
struct fdb_notify_req *req,
struct msg_rsp *rsp)
{
+ struct workqueue_struct *wq;
+ struct l2_entry *l2_entry;
+
+ spin_lock_bh(&rvu_sw_l2_state_lock);
+ if (!(rvu->rswitch.flags & RVU_SWITCH_FLAG_FW_READY) ||
+ !rvu_sw_l2_offl_wq) {
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+ return 0;
+ }
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+ if (atomic_read(&l2_offl_list_cnt) >= RVU_SW_L2_LIST_MAX) {
+ rvu_sw_l2_list_cnt_warn(rvu->dev, &l2_offl_list_cnt, "offload");
+ return -ENOMEM;
+ }
+
+ l2_entry = kcalloc(1, sizeof(*l2_entry), GFP_KERNEL);
+ if (!l2_entry)
+ return -ENOMEM;
+
+ l2_entry->port_id = rvu_sw_port_id(rvu, req->hdr.pcifunc);
+ ether_addr_copy(l2_entry->mac, req->mac);
+ l2_entry->flags = req->flags;
+
+ mutex_lock(&l2_offl_list_lock);
+ if (atomic_read(&l2_offl_list_cnt) >= RVU_SW_L2_LIST_MAX) {
+ rvu_sw_l2_list_cnt_warn(rvu->dev, &l2_offl_list_cnt, "offload");
+ mutex_unlock(&l2_offl_list_lock);
+ kfree(l2_entry);
+ return -ENOMEM;
+ }
+ list_add_tail(&l2_entry->list, &l2_offl_lh);
+ rvu_sw_l2_list_cnt_inc(rvu->dev, &l2_offl_list_cnt, "offload");
+ mutex_unlock(&l2_offl_list_lock);
+
+ spin_lock_bh(&rvu_sw_l2_state_lock);
+ wq = rvu_sw_l2_offl_wq;
+ if (wq)
+ queue_work(wq, &l2_offl_work.work);
+ spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+ if (!wq) {
+ mutex_lock(&l2_offl_list_lock);
+ list_del_init(&l2_entry->list);
+ rvu_sw_l2_list_cnt_dec(rvu->dev, &l2_offl_list_cnt, "offload");
+ mutex_unlock(&l2_offl_list_lock);
+ kfree(l2_entry);
+ }
+
return 0;
}
+
+void rvu_sw_l2_shutdown(void)
+{
+ if (!fdb_refresh_wq && !rvu_sw_l2_offl_wq)
+ return;
+
+ rvu_sw_l2_destroy_wqs(l2_offl_work.rvu);
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
index ff28612150c9..6685431d60a2 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
@@ -8,4 +8,7 @@
#ifndef RVU_SW_L2_H
#define RVU_SW_L2_H
+int rvu_sw_l2_init_offl_wq(struct rvu *rvu, u16 pcifunc, bool fw_up);
+int rvu_sw_l2_fdb_list_entry_add(struct rvu *rvu, u16 pcifunc, u8 *mac);
+void rvu_sw_l2_shutdown(void);
#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
index 2e33b33ec993..0cd6049c637e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
@@ -28,6 +28,7 @@
#include <rvu_trace.h>
#include "cn10k_ipsec.h"
#include "otx2_xsk.h"
+#include "switch/sw_nb.h"
#define DRV_NAME "rvu_nicpf"
#define DRV_STRING "Marvell RVU NIC Physical Function Driver"
@@ -993,6 +994,7 @@ static int otx2_process_mbox_msg_up(struct otx2_nic *pf,
MBOX_UP_CGX_MESSAGES
MBOX_UP_MCS_MESSAGES
MBOX_UP_REP_MESSAGES
+MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES
#undef M
break;
default:
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
index b022f52c6845..6f2fc4caf70c 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
@@ -9,6 +9,7 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/net_tstamp.h>
+#include <net/switchdev.h>
#include "otx2_common.h"
#include "otx2_reg.h"
@@ -114,6 +115,33 @@ static void otx2vf_vfaf_mbox_handler(struct work_struct *work)
}
}
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+static int otx2vf_mbox_af2pf_fdb_refresh(struct otx2_nic *vf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp)
+{
+ struct switchdev_notifier_fdb_info item = {0};
+
+ item.addr = req->mac;
+ item.info.dev = vf->netdev;
+ if (req->flags & FDB_DEL)
+ call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
+ item.info.dev, &item.info, NULL);
+ else
+ call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
+ item.info.dev, &item.info, NULL);
+
+ return 0;
+}
+#else
+static int otx2vf_mbox_af2pf_fdb_refresh(struct otx2_nic *vf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp)
+{
+ return 0;
+}
+#endif
+
static int otx2vf_process_mbox_msg_up(struct otx2_nic *vf,
struct mbox_msghdr *req)
{
@@ -141,6 +169,22 @@ static int otx2vf_process_mbox_msg_up(struct otx2_nic *vf,
err = otx2_mbox_up_handler_cgx_link_event(
vf, (struct cgx_link_info_msg *)req, rsp);
return err;
+
+ case MBOX_MSG_AF2PF_FDB_REFRESH:
+ rsp = (struct msg_rsp *)otx2_mbox_alloc_msg(&vf->mbox.mbox_up, 0,
+ sizeof(struct msg_rsp));
+ if (!rsp)
+ return -ENOMEM;
+
+ rsp->hdr.id = MBOX_MSG_AF2PF_FDB_REFRESH;
+ rsp->hdr.sig = OTX2_MBOX_RSP_SIG;
+ rsp->hdr.pcifunc = req->pcifunc;
+ rsp->hdr.rc = 0;
+ err = otx2vf_mbox_af2pf_fdb_refresh(vf,
+ (struct af2pf_fdb_refresh_req *)req,
+ rsp);
+ return err;
+
default:
otx2_reply_invalid_msg(&vf->mbox.mbox_up, 0, 0, req->id);
return -ENODEV;
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
index 6842c8d91ffc..eb5e2ce44ca2 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
@@ -4,13 +4,231 @@
* Copyright (C) 2026 Marvell.
*
*/
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
#include "sw_fdb.h"
+#if !IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+
+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp)
+{
+ return 0;
+}
+
+#else
+
+#define SW_FDB_LIST_MAX 4096
+
+static DEFINE_SPINLOCK(sw_fdb_llock);
+static LIST_HEAD(sw_fdb_lh);
+static atomic_t sw_fdb_list_cnt = ATOMIC_INIT(0);
+
+struct sw_fdb_list_entry {
+ struct list_head list;
+ u64 flags;
+ struct otx2_nic *pf;
+ netdevice_tracker dev_tracker;
+ u8 mac[ETH_ALEN];
+ bool add_fdb;
+};
+
+static struct workqueue_struct *sw_fdb_wq;
+static struct work_struct sw_fdb_work;
+
+static void sw_fdb_list_cnt_warn(struct net_device *netdev)
+{
+ int n = atomic_read(&sw_fdb_list_cnt);
+
+ if (n < 0)
+ netdev_warn(netdev, "FDB list count underflow: %d\n", n);
+ else if (n > SW_FDB_LIST_MAX)
+ netdev_warn(netdev, "FDB list count overflow: %d (max %d)\n",
+ n, SW_FDB_LIST_MAX);
+}
+
+static int sw_fdb_list_count(void)
+{
+ return atomic_read(&sw_fdb_list_cnt);
+}
+
+static void sw_fdb_list_cnt_inc(struct net_device *netdev)
+{
+ atomic_inc(&sw_fdb_list_cnt);
+ sw_fdb_list_cnt_warn(netdev);
+}
+
+static void sw_fdb_list_cnt_dec(struct net_device *netdev)
+{
+ atomic_dec(&sw_fdb_list_cnt);
+ sw_fdb_list_cnt_warn(netdev);
+}
+
+static int sw_fdb_add_or_del(struct otx2_nic *pf,
+ const unsigned char *addr,
+ bool add_fdb)
+{
+ struct fdb_notify_req *req;
+ int rc;
+
+ mutex_lock(&pf->mbox.lock);
+ req = otx2_mbox_alloc_msg_fdb_notify(&pf->mbox);
+ if (!req) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ ether_addr_copy(req->mac, addr);
+ req->flags = add_fdb ? FDB_ADD : FDB_DEL;
+
+ rc = otx2_sync_mbox_msg(&pf->mbox);
+out:
+ mutex_unlock(&pf->mbox.lock);
+ return rc;
+}
+
+static void sw_fdb_wq_handler(struct work_struct *work)
+{
+ struct sw_fdb_list_entry *entry;
+ struct workqueue_struct *wq;
+ LIST_HEAD(tlist);
+
+ spin_lock_bh(&sw_fdb_llock);
+ list_splice_init(&sw_fdb_lh, &tlist);
+ spin_unlock_bh(&sw_fdb_llock);
+
+ while ((entry =
+ list_first_entry_or_null(&tlist,
+ struct sw_fdb_list_entry,
+ list)) != NULL) {
+ list_del_init(&entry->list);
+ sw_fdb_list_cnt_dec(entry->pf->netdev);
+ if (sw_fdb_add_or_del(entry->pf, entry->mac, entry->add_fdb))
+ netdev_err(entry->pf->netdev,
+ "Error to add/del fdb %pM entry\n",
+ entry->mac);
+ netdev_put(entry->pf->netdev, &entry->dev_tracker);
+ kfree(entry);
+ }
+
+ spin_lock_bh(&sw_fdb_llock);
+ wq = sw_fdb_wq;
+ if (wq && !list_empty(&sw_fdb_lh))
+ queue_work(wq, &sw_fdb_work);
+ spin_unlock_bh(&sw_fdb_llock);
+}
+
+int sw_fdb_add_to_list(struct net_device *dev, u8 *mac, bool add_fdb)
+{
+ struct otx2_nic *pf = netdev_priv(dev);
+ struct sw_fdb_list_entry *entry;
+ struct workqueue_struct *wq;
+
+ spin_lock_bh(&sw_fdb_llock);
+ if (!sw_fdb_wq) {
+ spin_unlock_bh(&sw_fdb_llock);
+ return -EINVAL;
+ }
+ spin_unlock_bh(&sw_fdb_llock);
+
+ if (sw_fdb_list_count() >= SW_FDB_LIST_MAX)
+ return -ENOMEM;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return -ENOMEM;
+
+ ether_addr_copy(entry->mac, mac);
+ entry->add_fdb = add_fdb;
+ entry->pf = pf;
+ netdev_hold(dev, &entry->dev_tracker, GFP_ATOMIC);
+
+ spin_lock_bh(&sw_fdb_llock);
+ wq = sw_fdb_wq;
+ if (wq) {
+ list_add_tail(&entry->list, &sw_fdb_lh);
+ sw_fdb_list_cnt_inc(dev);
+ queue_work(wq, &sw_fdb_work);
+ }
+ spin_unlock_bh(&sw_fdb_llock);
+
+ if (!wq) {
+ netdev_put(dev, &entry->dev_tracker);
+ kfree(entry);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
int sw_fdb_init(void)
{
+ INIT_WORK(&sw_fdb_work, sw_fdb_wq_handler);
+ sw_fdb_wq = alloc_workqueue("sw_fdb_wq", 0, 0);
+ if (!sw_fdb_wq)
+ return -ENOMEM;
+
return 0;
}
void sw_fdb_deinit(void)
{
+ struct sw_fdb_list_entry *entry;
+ struct workqueue_struct *wq;
+ LIST_HEAD(tlist);
+
+ spin_lock_bh(&sw_fdb_llock);
+ wq = sw_fdb_wq;
+ sw_fdb_wq = NULL;
+ spin_unlock_bh(&sw_fdb_llock);
+
+ if (!wq)
+ return;
+
+ cancel_work_sync(&sw_fdb_work);
+ destroy_workqueue(wq);
+
+ spin_lock_bh(&sw_fdb_llock);
+ list_splice_init(&sw_fdb_lh, &tlist);
+ spin_unlock_bh(&sw_fdb_llock);
+
+ while ((entry =
+ list_first_entry_or_null(&tlist,
+ struct sw_fdb_list_entry,
+ list)) != NULL) {
+ list_del_init(&entry->list);
+ sw_fdb_list_cnt_dec(entry->pf->netdev);
+ netdev_put(entry->pf->netdev, &entry->dev_tracker);
+ kfree(entry);
+ }
+}
+
+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp)
+{
+ struct switchdev_notifier_fdb_info item = {0};
+
+ item.addr = req->mac;
+ item.info.dev = pf->netdev;
+ if (req->flags & FDB_DEL)
+ call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
+ item.info.dev, &item.info, NULL);
+ else
+ call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
+ item.info.dev, &item.info, NULL);
+
+ return 0;
}
+#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
index d4314d6d3ee4..3b06a77e6b56 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
@@ -7,6 +7,7 @@
#ifndef SW_FDB_H_
#define SW_FDB_H_
+int sw_fdb_add_to_list(struct net_device *dev, u8 *mac, bool add_fdb);
void sw_fdb_deinit(void);
int sw_fdb_init(void);
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
index bffe2af003c7..8aa357e9db21 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
@@ -148,11 +148,13 @@ static int sw_nb_fdb_event(struct notifier_block *unused,
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (fdb_info->is_local)
break;
+ sw_fdb_add_to_list(dev, (u8 *)fdb_info->addr, true);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (fdb_info->is_local)
break;
+ sw_fdb_add_to_list(dev, (u8 *)fdb_info->addr, false);
break;
default:
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
index e995c0e6046b..a701574de1e4 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
@@ -14,6 +14,10 @@ struct otx2_nic;
struct af2pf_fdb_refresh_req;
struct msg_rsp;
+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp);
+
#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
enum {
OTX2_DEV_UP = 1,
@@ -32,10 +36,6 @@ int sw_nb_unregister(struct net_device *netdev);
bool sw_nb_is_valid_dev(struct net_device *netdev);
struct net_device *sw_nb_resolve_pf_dev(struct net_device *dev);
-int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
- struct af2pf_fdb_refresh_req *req,
- struct msg_rsp *rsp);
-
bool sw_nb_is_cavium_dev(struct net_device *netdev);
int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev);
int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev);
--
2.43.0
^ permalink raw reply related
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