* [PATCH iproute2-next v2 2/2] dpll: add frequency monitoring support
From: Ivan Vecera @ 2026-05-03 15:13 UTC (permalink / raw)
To: netdev; +Cc: Petr Oros, David Ahern, Stephen Hemminger
In-Reply-To: <20260503151352.136509-1-ivecera@redhat.com>
Add support for the new frequency monitoring feature from the kernel
patch series "dpll: add actual frequency monitoring feature". This
includes:
- DPLL_A_FREQUENCY_MONITOR device attribute (enable/disable)
- DPLL_A_PIN_MEASURED_FREQUENCY pin attribute displayed as fractional Hz
using DPLL_PR_MEASURED_FREQUENCY macro (kernel reports in mHz)
- device set: frequency-monitor { enable | disable }
- Refactor phase-offset-monitor to use new dpll_parse_attr_feature_state
helper shared with frequency-monitor
- Update man page and bash-completion
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
Changes:
v2 - fixed very long line in the man page
---
bash-completion/dpll | 4 +--
dpll/dpll.c | 62 ++++++++++++++++++++++++++++++++++----------
man/man8/dpll.8 | 19 ++++++++++++--
3 files changed, 68 insertions(+), 17 deletions(-)
diff --git a/bash-completion/dpll b/bash-completion/dpll
index 542b99c2fce2..7ddcf529d429 100644
--- a/bash-completion/dpll
+++ b/bash-completion/dpll
@@ -100,7 +100,7 @@ _dpll_device()
COMPREPLY=( $( compgen -W "automatic manual" -- "$cur" ) )
return 0
;;
- phase-offset-monitor)
+ phase-offset-monitor|frequency-monitor)
COMPREPLY=( $( compgen -W "enable disable true false 0 1" -- "$cur" ) )
return 0
;;
@@ -110,7 +110,7 @@ _dpll_device()
;;
*)
COMPREPLY=( $( compgen -W "id mode phase-offset-monitor \
- phase-offset-avg-factor" -- "$cur" ) )
+ phase-offset-avg-factor frequency-monitor" -- "$cur" ) )
return 0
;;
esac
diff --git a/dpll/dpll.c b/dpll/dpll.c
index e8056ff6a24b..6d8c0cbb8a34 100644
--- a/dpll/dpll.c
+++ b/dpll/dpll.c
@@ -313,6 +313,26 @@ static int dpll_parse_attr_str(struct dpll *dpll, struct nlmsghdr *nlh,
return 0;
}
+static int dpll_parse_attr_feature_state(struct dpll *dpll,
+ struct nlmsghdr *nlh,
+ const char *arg_name, int attr_id)
+{
+ const char *str = dpll_argv_next(dpll);
+ bool val;
+
+ if (!str) {
+ pr_err("%s requires an argument\n", arg_name);
+ return -EINVAL;
+ }
+ if (str_to_bool(str, &val)) {
+ pr_err("invalid %s value: %s (use enable/disable)\n",
+ arg_name, str);
+ return -EINVAL;
+ }
+ mnl_attr_put_u32(nlh, attr_id, val ? 1 : 0);
+ return 0;
+}
+
static int dpll_parse_attr_enum(struct dpll *dpll, struct nlmsghdr *nlh,
const char *arg_name, int attr_id,
int (*parse_func)(struct dpll *, __u32 *))
@@ -425,6 +445,21 @@ static __s64 mnl_attr_get_sint(const struct nlattr *attr)
} \
} while (0)
+/* Measured frequency - JSON prints raw mHz value, FP prints fractional Hz */
+#define DPLL_PR_MEASURED_FREQUENCY(tb, attr_id) \
+ do { \
+ if (tb[attr_id]) { \
+ __u64 val = mnl_attr_get_u64(tb[attr_id]); \
+ lldiv_t d = lldiv(val, \
+ DPLL_PIN_MEASURED_FREQUENCY_DIVIDER); \
+ print_lluint(PRINT_JSON, "measured-frequency", \
+ NULL, val); \
+ print_s64(PRINT_FP, NULL, \
+ " measured-frequency: %lld.", d.quot); \
+ print_s64(PRINT_FP, NULL, "%03lld Hz\n", d.rem); \
+ } \
+ } while (0)
+
/* Generic version with custom format */
#define DPLL_PR_ENUM_STR_FMT(tb, attr_id, name, format_str, name_func) \
do { \
@@ -657,6 +692,7 @@ static void cmd_device_help(void)
pr_err(" dpll device set id DEVICE_ID [ mode { automatic | manual } ]\n");
pr_err(" [ phase-offset-monitor { enable | disable } ]\n");
pr_err(" [ phase-offset-avg-factor NUM ]\n");
+ pr_err(" [ frequency-monitor { enable | disable } ]\n");
pr_err(" dpll device id-get [ module-name NAME ] [ clock-id ID ] [ type TYPE ]\n");
}
@@ -1058,6 +1094,10 @@ static void dpll_device_print_attrs(const struct nlmsghdr *nlh,
str_enable_disable);
DPLL_PR_UINT(tb, DPLL_A_PHASE_OFFSET_AVG_FACTOR,
"phase-offset-avg-factor");
+ DPLL_PR_ENUM_STR_FMT(tb, DPLL_A_FREQUENCY_MONITOR,
+ "frequency-monitor",
+ " frequency-monitor: %s\n",
+ str_enable_disable);
}
/* Netlink callback - device get (single device) */
@@ -1219,25 +1259,20 @@ static int cmd_device_set(struct dpll *dpll)
dpll_parse_mode))
return -EINVAL;
} else if (dpll_argv_match(dpll, "phase-offset-monitor")) {
- const char *str = dpll_argv_next(dpll);
- bool val;
-
- if (!str) {
- pr_err("phase-offset-monitor requires an argument\n");
- return -EINVAL;
- }
- if (str_to_bool(str, &val)) {
- pr_err("invalid phase-offset-monitor value: %s (use enable/disable)\n",
- str);
+ if (dpll_parse_attr_feature_state(dpll, nlh,
+ "phase-offset-monitor",
+ DPLL_A_PHASE_OFFSET_MONITOR))
return -EINVAL;
- }
- mnl_attr_put_u32(nlh, DPLL_A_PHASE_OFFSET_MONITOR,
- val ? 1 : 0);
} else if (dpll_argv_match(dpll, "phase-offset-avg-factor")) {
if (dpll_parse_attr_u32(dpll, nlh,
"phase-offset-avg-factor",
DPLL_A_PHASE_OFFSET_AVG_FACTOR))
return -EINVAL;
+ } else if (dpll_argv_match(dpll, "frequency-monitor")) {
+ if (dpll_parse_attr_feature_state(dpll, nlh,
+ "frequency-monitor",
+ DPLL_A_FREQUENCY_MONITOR))
+ return -EINVAL;
} else {
pr_err("unknown option: %s\n", dpll_argv(dpll));
return -EINVAL;
@@ -1601,6 +1636,7 @@ static void dpll_pin_print_attrs(struct nlattr **tb)
DPLL_PR_ENUM_STR(tb, DPLL_A_PIN_TYPE, "type", dpll_pin_type_name);
DPLL_PR_U64_FMT(tb, DPLL_A_PIN_FREQUENCY, "frequency",
" frequency: %" PRIu64 " Hz\n");
+ DPLL_PR_MEASURED_FREQUENCY(tb, DPLL_A_PIN_MEASURED_FREQUENCY);
dpll_pin_print_freq_supported(tb[DPLL_A_PIN_FREQUENCY_SUPPORTED]);
diff --git a/man/man8/dpll.8 b/man/man8/dpll.8
index 89f17af74923..4b7461d15437 100644
--- a/man/man8/dpll.8
+++ b/man/man8/dpll.8
@@ -111,7 +111,7 @@ Temperature (if supported)
Type (PPS or EEC)
.RE
-.SS dpll device set id ID [ mode { automatic | manual } ] [ phase-offset-monitor { enable | disable } ] [ phase-offset-avg-factor FACTOR ]
+.SS dpll device set id ID [ PARAMETER VALUE ] ...
Configure DPLL device parameters.
@@ -140,6 +140,14 @@ When enabled, the kernel continuously measures and reports phase differences.
Set the averaging factor (1-255) applied to phase offset calculations.
Higher values provide smoother but slower-responding measurements.
+.TP
+.BI frequency-monitor " { enable | disable | true | false | 0 | 1 }"
+Enable or disable frequency monitoring on the device. When enabled, the
+kernel continuously measures and reports actual pin frequencies, which can
+be read via the
+.B measured-frequency
+field in pin show output.
+
.SS dpll device id-get [ module-name NAME ] [ clock-id ID ] [ type TYPE ]
Retrieve the device ID based on identifying attributes. Useful for scripting
@@ -233,7 +241,9 @@ Board label (hardware label from device tree or ACPI)
.IP \[bu]
Pin type (mux, ext, synce-eth-port, int-oscillator, gnss)
.IP \[bu]
-Frequency and supported frequency ranges
+Configured frequency and supported frequency ranges
+.IP \[bu]
+Measured frequency in Hz (when frequency monitoring is enabled)
.IP \[bu]
Capabilities (state-can-change, priority-can-change, direction-can-change)
.IP \[bu]
@@ -372,6 +382,11 @@ Press Ctrl+C to stop monitoring.
.B dpll device set id 0 phase-offset-monitor enable
.fi
+.SS Enable frequency monitoring on device 0
+.nf
+.B dpll device set id 0 frequency-monitor enable
+.fi
+
.SS Show all EEC devices
.nf
.B dpll device show type eec
--
2.53.0
^ permalink raw reply related
* [PATCH iproute2-next v2 1/2] dpll: add ps unit to phase-related pin attributes
From: Ivan Vecera @ 2026-05-03 15:13 UTC (permalink / raw)
To: netdev; +Cc: Petr Oros, David Ahern, Stephen Hemminger
In-Reply-To: <20260503151352.136509-1-ivecera@redhat.com>
Display phase-adjust-min, phase-adjust-max and phase-adjust values
with ps unit. Add DPLL_PR_PHASE_OFFSET macro that properly formats
phase-offset as fractional picoseconds by dividing the raw kernel
value by DPLL_PHASE_OFFSET_DIVIDER.
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
dpll/dpll.c | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/dpll/dpll.c b/dpll/dpll.c
index b6ba3283e0ba..e8056ff6a24b 100644
--- a/dpll/dpll.c
+++ b/dpll/dpll.c
@@ -410,6 +410,21 @@ static __s64 mnl_attr_get_sint(const struct nlattr *attr)
} \
} while (0)
+/* Phase offset - JSON prints raw sub-ps value, FP prints fractional ps */
+#define DPLL_PR_PHASE_OFFSET(tb, attr_id) \
+ do { \
+ if (tb[attr_id]) { \
+ __s64 val = mnl_attr_get_sint(tb[attr_id]); \
+ lldiv_t d = lldiv(llabs(val), \
+ DPLL_PHASE_OFFSET_DIVIDER); \
+ print_s64(PRINT_JSON, "phase-offset", NULL, val); \
+ print_string(PRINT_FP, NULL, " phase-offset %s", \
+ val < 0 ? "-" : ""); \
+ print_s64(PRINT_FP, NULL, "%lld.", d.quot); \
+ print_s64(PRINT_FP, NULL, "%03lld ps", d.rem); \
+ } \
+ } while (0)
+
/* Generic version with custom format */
#define DPLL_PR_ENUM_STR_FMT(tb, attr_id, name, format_str, name_func) \
do { \
@@ -1507,8 +1522,7 @@ static void dpll_pin_print_parent_devices(struct nlattr *attr)
" prio %u");
DPLL_PR_ENUM_STR_FMT(tb_parent, DPLL_A_PIN_STATE, "state",
" state %s", dpll_pin_state_name);
- DPLL_PR_SINT_FMT(tb_parent, DPLL_A_PIN_PHASE_OFFSET,
- "phase-offset", " phase-offset %" PRId64);
+ DPLL_PR_PHASE_OFFSET(tb_parent, DPLL_A_PIN_PHASE_OFFSET);
print_nl();
close_json_object();
@@ -1592,10 +1606,13 @@ static void dpll_pin_print_attrs(struct nlattr **tb)
dpll_pin_print_capabilities(tb[DPLL_A_PIN_CAPABILITIES]);
- DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST_MIN, "phase-adjust-min");
- DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST_MAX, "phase-adjust-max");
+ DPLL_PR_INT_FMT(tb, DPLL_A_PIN_PHASE_ADJUST_MIN, "phase-adjust-min",
+ " phase-adjust-min: %d ps\n");
+ DPLL_PR_INT_FMT(tb, DPLL_A_PIN_PHASE_ADJUST_MAX, "phase-adjust-max",
+ " phase-adjust-max: %d ps\n");
DPLL_PR_UINT(tb, DPLL_A_PIN_PHASE_ADJUST_GRAN, "phase-adjust-gran");
- DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST, "phase-adjust");
+ DPLL_PR_INT_FMT(tb, DPLL_A_PIN_PHASE_ADJUST, "phase-adjust",
+ " phase-adjust: %d ps\n");
if (json || !tb[DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT])
DPLL_PR_SINT(tb, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
--
2.53.0
^ permalink raw reply related
* [PATCH iproute2-next v2 0/2] dpll: phase unit display and frequency monitoring
From: Ivan Vecera @ 2026-05-03 15:13 UTC (permalink / raw)
To: netdev; +Cc: Petr Oros, David Ahern, Stephen Hemminger
This series improves dpll pin output formatting and adds support for
the frequency monitoring feature.
Patch 1 adds picosecond unit to phase-adjust-min, phase-adjust-max
and phase-adjust attributes. It also introduces the DPLL_PR_PHASE_OFFSET
macro that properly formats phase-offset as fractional picoseconds by
dividing the raw kernel value by DPLL_PHASE_OFFSET_DIVIDER.
Patch 2 adds support for the new frequency monitoring feature including
the DPLL_A_FREQUENCY_MONITOR device attribute and
DPLL_A_PIN_MEASURED_FREQUENCY pin attribute. The measured frequency is
displayed as fractional Hz using the DPLL_PR_MEASURED_FREQUENCY macro
since the kernel reports the value in millihertz. It also refactors
phase-offset-monitor parsing into a shared helper.
Tested on EDS2 development board with zl3073x DPLL:
# dpll pin show package-label REF0P
pin id 196:
module-name: zl3073x
clock-id: 13709406750444215013
board-label: SyncE IN M1 CLK1
package-label: REF0P
type: synce-eth-port
frequency: 125000000 Hz
measured-frequency: 124999326.000 Hz
frequency-supported:
2500000 Hz
25000000 Hz
125000000 Hz
capabilities: 0x6 state-can-change priority-can-change
phase-adjust-min: -2147483648 ps
phase-adjust-max: 2147483647 ps
phase-adjust: 0 ps
parent-device:
id 14 direction input prio 10 state selectable phase-offset 0.000 ps
id 15 direction input prio 0 state connected phase-offset 323.000 ps
Changes:
v2:
- fixed very long line in the man page
Ivan Vecera (2):
dpll: add ps unit to phase-related pin attributes
dpll: add frequency monitoring support
bash-completion/dpll | 4 +-
dpll/dpll.c | 89 +++++++++++++++++++++++++++++++++++---------
man/man8/dpll.8 | 19 +++++++++-
3 files changed, 90 insertions(+), 22 deletions(-)
--
2.53.0
^ permalink raw reply
* Re: [PATCH iproute2-next 2/2] dpll: add frequency monitoring support
From: Ivan Vecera @ 2026-05-03 15:11 UTC (permalink / raw)
To: David Ahern, netdev; +Cc: Stephen Hemminger, Petr Oros
In-Reply-To: <d99ca8ef-c11f-4190-bf08-b94c3ac76462@kernel.org>
Hi David,
On 5/2/26 8:29 PM, David Ahern wrote:
> On 4/28/26 9:21 AM, Ivan Vecera wrote:
>> diff --git a/man/man8/dpll.8 b/man/man8/dpll.8
>> index 89f17af74923..59ec4208f251 100644
>> --- a/man/man8/dpll.8
>> +++ b/man/man8/dpll.8
>> @@ -111,7 +111,7 @@ Temperature (if supported)
>> Type (PPS or EEC)
>> .RE
>>
>> -.SS dpll device set id ID [ mode { automatic | manual } ] [ phase-offset-monitor { enable | disable } ] [ phase-offset-avg-factor FACTOR ]
>> +.SS dpll device set id ID [ mode { automatic | manual } ] [ phase-offset-monitor { enable | disable } ] [ phase-offset-avg-factor FACTOR ] [ frequency-monitor { enable | disable } ]
>
> very long line; please fix up.
Will fix in v2.
>
> Keep Petr reviewed by on patch 1.
>
> Claude has some comments on the man page in general:
>
> ● I found three issues. Let me show them clearly:
>
> Issue 1 — Typo: "locked-ho-ack" should be "locked-ho-acq" (line 107)
>
> The man page description of device show output says locked-ho-ack but
> the code (dpll_lock_status_name) returns locked-ho-acq
> (ACQuired, not ACKnowledge).
>
> Issue 2 — Wrong mode values in output description (lines 104–105)
>
> The "Output includes" section says:
>
> ▎ Operating mode (manual, automatic, holdover, freerun)
>
> But dpll_mode_map and DPLL_MODE_* only define manual and automatic.
> Holdover and freerun are not DPLL modes — they are lock statuses.
> This should read (manual, automatic).
>
> Issue 3 — Undocumented but accepted alternate values for monitors
> (lines 134, 144)
>
> The man page documents { enable | disable | true | false | 0 | 1 } for
> phase-offset-monitor and frequency-monitor. The tool's own
> help text shows only { enable | disable } and the error message says
> "use enable/disable". While str_to_bool technically accepts
> true/false/0/1, the canonical interface is enable | disable. The extra
> forms in the man page are inconsistent with the help text.
Will send a separate fix for the dpll man page (non-next)...
Thanks,
Ivan
^ permalink raw reply
* Re: [PATCH 2/5] dt-bindings: clock: qcom: Add Qualcomm Shikra SoC Global Clock Controller
From: Krzysztof Kozlowski @ 2026-05-03 14:24 UTC (permalink / raw)
To: Imran Shaik
Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Richard Cochran, Ajit Pandey,
Taniya Das, Jagadeesh Kona, linux-arm-msm, linux-clk, devicetree,
linux-kernel, netdev
In-Reply-To: <20260429-shikra-gcc-rpmcc-clks-v1-2-c3cd77558b7a@oss.qualcomm.com>
On Wed, Apr 29, 2026 at 04:21:50PM +0530, Imran Shaik wrote:
> Add device tree bindings for the global clock controller on Qualcomm
> Shikra SoC.
>
> Signed-off-by: Imran Shaik <imran.shaik@oss.qualcomm.com>
> ---
> .../devicetree/bindings/clock/qcom,shikra-gcc.yaml | 63 +++++
> include/dt-bindings/clock/qcom,shikra-gcc.h | 259 +++++++++++++++++++++
> 2 files changed, 322 insertions(+)
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 1/5] dt-bindings: clock: qcom,rpmcc: Add Qualcomm Shikra SoC RPMCC
From: Krzysztof Kozlowski @ 2026-05-03 14:24 UTC (permalink / raw)
To: Imran Shaik
Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Richard Cochran, Ajit Pandey,
Taniya Das, Jagadeesh Kona, linux-arm-msm, linux-clk, devicetree,
linux-kernel, netdev
In-Reply-To: <20260429-shikra-gcc-rpmcc-clks-v1-1-c3cd77558b7a@oss.qualcomm.com>
On Wed, Apr 29, 2026 at 04:21:49PM +0530, Imran Shaik wrote:
> Add bindings documentation for RPM clock controller on Qualcomm Shikra SoC.
>
> Signed-off-by: Imran Shaik <imran.shaik@oss.qualcomm.com>
> ---
> Documentation/devicetree/bindings/clock/qcom,rpmcc.yaml | 2 ++
> 1 file changed, 2 insertions(+)
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2] tty: synclink_gt: remove broken driver
From: Andrew Lunn @ 2026-05-03 14:23 UTC (permalink / raw)
To: Ethan Nelson-Moore
Cc: Greg Kroah-Hartman, linux-doc, netdev, linux-serial,
rust-for-linux, Jonathan Corbet, Shuah Khan, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Jiri Slaby, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Bagas Sanjaya, Haren Myneni,
Eric Biggers, Qingfang Deng, Julian Braha
In-Reply-To: <CADkSEUgPtjkKC684O3qB=koKDPwJoUj-qU_4Z_18NAU_+bBqkw@mail.gmail.com>
On Sat, May 02, 2026 at 11:00:53PM -0700, Ethan Nelson-Moore wrote:
> Hi, Greg,
>
> On Sat, May 2, 2026 at 10:44 PM Greg Kroah-Hartman
> <gregkh@linuxfoundation.org> wrote:
> > Then that means someone uses it somewhere. Don't generate bindings for
> > something that will break because it is no longer in the tree :(
> That project generates bindings for every UAPI header automatically,
> but has a hardcoded list of them, so its presence there doesn't mean
> anyone is using it.
>
> > If no one does use it, then please get that project to fix their code so
> > that we don't break their build.
> They have had to remove headers from their list that got removed from
> the kernel before. I will send them a pull request to remove this
> header and then resend this patch with the UAPI header removal
> restored. Does that sound good to you?
Sounds like a whack a mole problem. I assume the recent removal of ATM
broke it as well? Maybe __has_include() could be used?
Andrew
^ permalink raw reply
* Re: [syzbot] [mptcp?] KMSAN: uninit-value in mptcp_established_options
From: syzbot @ 2026-05-03 13:01 UTC (permalink / raw)
To: davem, edumazet, geliang, horms, kmta1236, kuba, kuni1840, kuniyu,
linux-kernel, martineau, matttbe, mptcp, netdev, pabeni,
syzkaller-bugs
In-Reply-To: <69f44505.050a0220.3cbe47.0008.GAE@google.com>
syzbot has found a reproducer for the following issue on:
HEAD commit: 66edb901bf87 Merge tag 'v7.1-p3' of git://git.kernel.org/p..
git tree: upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=1086da36580000
kernel config: https://syzkaller.appspot.com/x/.config?x=1c3f61154f3bb7e5
dashboard link: https://syzkaller.appspot.com/bug?extid=ff020673c5e3d94d9478
compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
syz repro: https://syzkaller.appspot.com/x/repro.syz?x=1260fece580000
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/9014c04fc561/disk-66edb901.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/a96b1e11a924/vmlinux-66edb901.xz
kernel image: https://storage.googleapis.com/syzbot-assets/680236de6331/bzImage-66edb901.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+ff020673c5e3d94d9478@syzkaller.appspotmail.com
=====================================================
BUG: KMSAN: uninit-value in mptcp_write_data_fin net/mptcp/options.c:542 [inline]
BUG: KMSAN: uninit-value in mptcp_established_options_dss net/mptcp/options.c:590 [inline]
BUG: KMSAN: uninit-value in mptcp_established_options+0x112f/0x3530 net/mptcp/options.c:874
mptcp_write_data_fin net/mptcp/options.c:542 [inline]
mptcp_established_options_dss net/mptcp/options.c:590 [inline]
mptcp_established_options+0x112f/0x3530 net/mptcp/options.c:874
tcp_established_options+0x312/0xcc0 net/ipv4/tcp_output.c:1192
__tcp_transmit_skb+0x5dc/0x5fe0 net/ipv4/tcp_output.c:1575
__tcp_send_ack+0x967/0xad0 net/ipv4/tcp_output.c:4499
tcp_send_ack+0x3d/0x60 net/ipv4/tcp_output.c:4505
mptcp_subflow_shutdown+0x164/0x690 net/mptcp/protocol.c:3137
mptcp_check_send_data_fin+0x31b/0x3d0 net/mptcp/protocol.c:3218
__mptcp_wr_shutdown net/mptcp/protocol.c:3234 [inline]
__mptcp_close+0x860/0x1360 net/mptcp/protocol.c:3313
mptcp_close+0x42/0x260 net/mptcp/protocol.c:3367
inet_release+0x1ee/0x2a0 net/ipv4/af_inet.c:442
__sock_release net/socket.c:722 [inline]
sock_close+0xd6/0x2f0 net/socket.c:1514
__fput+0x60e/0x1010 fs/file_table.c:510
____fput+0x25/0x30 fs/file_table.c:538
task_work_run+0x208/0x2b0 kernel/task_work.c:233
resume_user_mode_work include/linux/resume_user_mode.h:50 [inline]
__exit_to_user_mode_loop kernel/entry/common.c:67 [inline]
exit_to_user_mode_loop+0x306/0x1b60 kernel/entry/common.c:98
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:238 [inline]
syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
do_syscall_64+0x236/0xf80 arch/x86/entry/syscall_64.c:100
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Local variable opts created at:
__tcp_transmit_skb+0x4d/0x5fe0 net/ipv4/tcp_output.c:1536
__tcp_send_ack+0x967/0xad0 net/ipv4/tcp_output.c:4499
CPU: 0 UID: 0 PID: 5905 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
=====================================================
---
If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.
^ permalink raw reply
* [PATCH batadv 8/8] batman-adv: tt: prevent TVLV entry number overflow
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
In-Reply-To: <20260503-fixes-followup-v1-0-4313278918d3@narfation.org>
The helpers to prepare the buffers for the local and global TT based
replies are trying to sum up all TT entries which can be found for each
VLAN. In theory, this sum can be too big for an u16 and therefore overflow.
A too small buffer would then be allocated for the TVLV.
The too small buffer will be handled gracefully by
batadv_tt_tvlv_generate() and is not causing a buffer overflow - just a
truncated reply. But this overflow shouldn't have happened in the first and
the too small buffer should never have been allocated when an overflow was
detected.
Cc: stable@kernel.org
Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
net/batman-adv/translation-table.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
index 5a005d4e6cc6..630ae8a66beb 100644
--- a/net/batman-adv/translation-table.c
+++ b/net/batman-adv/translation-table.c
@@ -804,11 +804,18 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
u16 total_entries = 0;
u8 *tt_change_ptr;
int vlan_entries;
+ u16 sum_entries;
spin_lock_bh(&orig_node->vlan_list_lock);
hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
vlan_entries = atomic_read(&vlan->tt.num_entries);
- total_entries += vlan_entries;
+
+ if (check_add_overflow(vlan_entries, total_entries, &sum_entries)) {
+ *tt_len = 0;
+ goto out;
+ }
+
+ total_entries = sum_entries;
num_vlan++;
}
@@ -896,11 +903,18 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
u16 total_entries = 0;
u16 tvlv_len;
u8 *tt_change_ptr;
+ u16 sum_entries;
spin_lock_bh(&bat_priv->meshif_vlan_list_lock);
hlist_for_each_entry(vlan, &bat_priv->meshif_vlan_list, list) {
vlan_entries = atomic_read(&vlan->tt.num_entries);
- total_entries += vlan_entries;
+
+ if (check_add_overflow(vlan_entries, total_entries, &sum_entries)) {
+ tvlv_len = 0;
+ goto out;
+ }
+
+ total_entries = sum_entries;
num_vlan++;
}
--
2.47.3
^ permalink raw reply related
* [PATCH batadv 7/8] batman-adv: tt: avoid empty VLAN responses
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
In-Reply-To: <20260503-fixes-followup-v1-0-4313278918d3@narfation.org>
The commit 16116dac2339 ("batman-adv: prevent TT request storms by not
sending inconsistent TT TLVLs") added checks to the local (direct) TT
response code. But the response can also be done indirectly by another node
using the global TT state. To avoid such inconsistency states reported in
the original fix, also avoid sending empty VLANs for replies from the
global TT state.
Cc: stable@kernel.org
Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
net/batman-adv/translation-table.c | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
index f5b9143c803a..5a005d4e6cc6 100644
--- a/net/batman-adv/translation-table.c
+++ b/net/batman-adv/translation-table.c
@@ -797,24 +797,26 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
s32 *tt_len)
{
u16 num_vlan = 0;
- u16 num_entries = 0;
u16 tvlv_len = 0;
unsigned int change_offset;
struct batadv_tvlv_tt_vlan_data *tt_vlan;
struct batadv_orig_node_vlan *vlan;
+ u16 total_entries = 0;
u8 *tt_change_ptr;
+ int vlan_entries;
spin_lock_bh(&orig_node->vlan_list_lock);
hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+ total_entries += vlan_entries;
num_vlan++;
- num_entries += atomic_read(&vlan->tt.num_entries);
}
change_offset = struct_size(*tt_data, vlan_data, num_vlan);
/* if tt_len is negative, allocate the space needed by the full table */
if (*tt_len < 0)
- *tt_len = batadv_tt_len(num_entries);
+ *tt_len = batadv_tt_len(total_entries);
if (change_offset > U16_MAX || *tt_len > U16_MAX - change_offset) {
*tt_len = 0;
@@ -832,17 +834,28 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
(*tt_data)->flags = BATADV_NO_FLAGS;
(*tt_data)->ttvn = atomic_read(&orig_node->last_ttvn);
- (*tt_data)->num_vlan = htons(num_vlan);
tt_vlan = (*tt_data)->vlan_data;
+ num_vlan = 0;
hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
+ vlan_entries = atomic_read(&vlan->tt.num_entries);
+ if (vlan_entries < 1)
+ continue;
+
tt_vlan->vid = htons(vlan->vid);
tt_vlan->crc = htonl(vlan->tt.crc);
tt_vlan->reserved = 0;
tt_vlan++;
+ num_vlan++;
}
+ /* recalculate in case number of VLANs reduced */
+ change_offset = struct_size(*tt_data, vlan_data, num_vlan);
+ tvlv_len = *tt_len + change_offset;
+
+ (*tt_data)->num_vlan = htons(num_vlan);
+
tt_change_ptr = (u8 *)*tt_data + change_offset;
*tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
--
2.47.3
^ permalink raw reply related
* [PATCH batadv 6/8] batman-adv: tt: fix TOCTOU race for reported vlans
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
In-Reply-To: <20260503-fixes-followup-v1-0-4313278918d3@narfation.org>
The local TT based TVLV is generated by first checking the number of VLANs
which have at least one TT entry. A new buffer with the correct size for
the VLANs is then allocated. Only then, the list of VLANs s used to fill
the VLAN entries in the buffer. During this time, the meshif_vlan_list_lock
is held. But the actual number of TT entries of each VLAN can still
increase during this time - just not the number of VLANs in the list.
But the prefilter used in the buffer size calculation might still cause an
increase of the number of VLANs which need to be stored. Simply because a
VLAN might now suddenly have at least one entry when it had none in the
pre-alloc check - and then needs to occupy space which was not allocated.
It is better to overestimate the buffer size at the beginning and then fill
the buffer only with the VLANs which are not empty.
Cc: stable@kernel.org
Fixes: 16116dac2339 ("batman-adv: prevent TT request storms by not sending inconsistent TT TLVLs")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
net/batman-adv/translation-table.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
index 06548dae1039..f5b9143c803a 100644
--- a/net/batman-adv/translation-table.c
+++ b/net/batman-adv/translation-table.c
@@ -887,11 +887,8 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
spin_lock_bh(&bat_priv->meshif_vlan_list_lock);
hlist_for_each_entry(vlan, &bat_priv->meshif_vlan_list, list) {
vlan_entries = atomic_read(&vlan->tt.num_entries);
- if (vlan_entries < 1)
- continue;
-
- num_vlan++;
total_entries += vlan_entries;
+ num_vlan++;
}
change_offset = struct_size(*tt_data, vlan_data, num_vlan);
@@ -913,9 +910,9 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
(*tt_data)->flags = BATADV_NO_FLAGS;
(*tt_data)->ttvn = atomic_read(&bat_priv->tt.vn);
- (*tt_data)->num_vlan = htons(num_vlan);
tt_vlan = (*tt_data)->vlan_data;
+ num_vlan = 0;
hlist_for_each_entry(vlan, &bat_priv->meshif_vlan_list, list) {
vlan_entries = atomic_read(&vlan->tt.num_entries);
if (vlan_entries < 1)
@@ -926,8 +923,15 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
tt_vlan->reserved = 0;
tt_vlan++;
+ num_vlan++;
}
+ /* recalculate in case number of VLANs reduced */
+ change_offset = struct_size(*tt_data, vlan_data, num_vlan);
+ tvlv_len = *tt_len + change_offset;
+
+ (*tt_data)->num_vlan = htons(num_vlan);
+
tt_change_ptr = (u8 *)*tt_data + change_offset;
*tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
--
2.47.3
^ permalink raw reply related
* [PATCH batadv 5/8] batman-adv: tt: reject oversized local TVLV buffers
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
In-Reply-To: <20260503-fixes-followup-v1-0-4313278918d3@narfation.org>
The commit 3a359bf5c61d ("batman-adv: reject oversized global TT response
buffers") added a check to ensure that a global return buffer size can be
stored in an u16. The same buffer handling also exists for the local data
buffer but was not touched.
A similar check should be also be in place for the local TVLV buffer. It
doesn't have the similar attack surface because it is only generated from
locally discovered MAC addresses but the dynamic nature could still cause
temporarily to large buffers.
Cc: stable@kernel.org
Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
net/batman-adv/translation-table.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
index 05cddcf994f6..06548dae1039 100644
--- a/net/batman-adv/translation-table.c
+++ b/net/batman-adv/translation-table.c
@@ -877,12 +877,12 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
{
struct batadv_tvlv_tt_vlan_data *tt_vlan;
struct batadv_meshif_vlan *vlan;
+ size_t change_offset;
u16 num_vlan = 0;
u16 vlan_entries = 0;
u16 total_entries = 0;
u16 tvlv_len;
u8 *tt_change_ptr;
- int change_offset;
spin_lock_bh(&bat_priv->meshif_vlan_list_lock);
hlist_for_each_entry(vlan, &bat_priv->meshif_vlan_list, list) {
@@ -900,8 +900,10 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
if (*tt_len < 0)
*tt_len = batadv_tt_len(total_entries);
- tvlv_len = *tt_len;
- tvlv_len += change_offset;
+ if (check_add_overflow(*tt_len, change_offset, &tvlv_len)) {
+ tvlv_len = 0;
+ goto out;
+ }
*tt_data = kmalloc(tvlv_len, GFP_ATOMIC);
if (!*tt_data) {
--
2.47.3
^ permalink raw reply related
* [PATCH batadv 4/8] batman-adv: tt: fix negative tt_buff_len
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
In-Reply-To: <20260503-fixes-followup-v1-0-4313278918d3@narfation.org>
batadv_orig_node::tt_buff_len was declared as s16, but the field is never
intended to hold a negative value. When a value greater than 32767 is
assigned, it wraps to a negative signed integer.
In batadv_send_other_tt_response(), tt_buff_len is temporarily widened to
s32. The incorrectly negative s16 value propagates into the s32, causing
batadv_tt_prepare_tvlv_global_data() to allocate a full sized buffer but
populates only a small portion of it with the collected changeset. All
remaining bits are kept uninitialized.
Using an u16 avoids this type confusion and ensures that no (negative) sign
extension is performed in batadv_send_other_tt_response().
Cc: stable@kernel.org
Fixes: a73105b8d4c7 ("batman-adv: improved client announcement mechanism")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
net/batman-adv/types.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
index daa06f421154..0f3814b458cc 100644
--- a/net/batman-adv/types.h
+++ b/net/batman-adv/types.h
@@ -452,7 +452,7 @@ struct batadv_orig_node {
* @tt_buff_len: length of the last tt changeset this node received
* from the orig node
*/
- s16 tt_buff_len;
+ u16 tt_buff_len;
/** @tt_buff_lock: lock that protects tt_buff and tt_buff_len */
spinlock_t tt_buff_lock;
--
2.47.3
^ permalink raw reply related
* [PATCH batadv 3/8] batman-adv: bla: only purge non-released claims
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
In-Reply-To: <20260503-fixes-followup-v1-0-4313278918d3@narfation.org>
When batadv_bla_purge_claims() goes through the list of claims, it is only
traversing the hash list with an rcu_read_lock(). Due to a potential
parallel batadv_claim_put(), it can happen that it encounters a claim which
was actually in the process of being released+freed by
batadv_claim_release(). In this case, backbone_gw is set to NULL before the
delayed RCU kfree is started. Calling batadv_bla_claim_get_backbone_gw() is
then no longer allowed because it would cause a NULL-ptr derefence.
To avoid this, only claims with a valid reference counter must be purged.
All others are already taken care of.
Cc: stable@kernel.org
Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
net/batman-adv/bridge_loop_avoidance.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
index 8b77dd2ecfa4..9dbf945b4922 100644
--- a/net/batman-adv/bridge_loop_avoidance.c
+++ b/net/batman-adv/bridge_loop_avoidance.c
@@ -1288,6 +1288,13 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
rcu_read_lock();
hlist_for_each_entry_rcu(claim, head, hash_entry) {
+ /* only purge claims not currently in the process of being released.
+ * Such claims could otherwise have a NULL-ptr* backbone_gw set because
+ * they already went through batadv_handle_unclaim()
+ */
+ if (!kref_get_unless_zero(&claim->refcount))
+ continue;
+
backbone_gw = batadv_bla_claim_get_backbone_gw(claim);
if (now)
goto purge_now;
@@ -1313,6 +1320,7 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
claim->addr, claim->vid);
skip:
batadv_backbone_gw_put(backbone_gw);
+ batadv_claim_put(claim);
}
rcu_read_unlock();
}
--
2.47.3
^ permalink raw reply related
* [PATCH batadv 2/8] batman-adv: bla: prevent use-after-free when deleting claims
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
In-Reply-To: <20260503-fixes-followup-v1-0-4313278918d3@narfation.org>
When batadv_bla_del_backbone_claims() removes all claims for a backbone, it
does this by dropping the link entry in the hash list. This list entry
itself was one of the references which need to be dropped at the same time
via batadv_claim_put().
But the batadv_claim_put() must not be done before the last access to the
claim object in this function. Otherwise the claim might be freed already
by the batadv_claim_release() function before the list entry was dropped.
Cc: stable@kernel.org
Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
net/batman-adv/bridge_loop_avoidance.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
index 51fe028b9088..8b77dd2ecfa4 100644
--- a/net/batman-adv/bridge_loop_avoidance.c
+++ b/net/batman-adv/bridge_loop_avoidance.c
@@ -318,8 +318,8 @@ batadv_bla_del_backbone_claims(struct batadv_bla_backbone_gw *backbone_gw)
if (claim->backbone_gw != backbone_gw)
continue;
- batadv_claim_put(claim);
hlist_del_rcu(&claim->hash_entry);
+ batadv_claim_put(claim);
}
spin_unlock_bh(list_lock);
}
--
2.47.3
^ permalink raw reply related
* [PATCH batadv 1/8] batman-adv: tp_meter: fix tp_num leak on kmalloc failure
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
In-Reply-To: <20260503-fixes-followup-v1-0-4313278918d3@narfation.org>
When batadv_tp_start() or batadv_tp_init_recv() fail to allocate a new
tp_vars object, the previously incremented bat_priv->tp_num counter is
never decremented. This causes tp_num to drift upward on each allocation
failure. Since only BATADV_TP_MAX_NUM sessions can be started and the count
is never reduced for these failed allocations, it causes to an exhaustion
of throughput meter sessions. In worst case, no new throughput meter
session can be started until the mesh interface is removed.
The error handling must decrement tp_num releasing the lock and aborting
the creation of an throughput meter session
Cc: stable@kernel.org
Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
net/batman-adv/tp_meter.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
index 58ca59a2799e..066c76113fc4 100644
--- a/net/batman-adv/tp_meter.c
+++ b/net/batman-adv/tp_meter.c
@@ -994,6 +994,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
tp_vars = kmalloc_obj(*tp_vars, GFP_ATOMIC);
if (!tp_vars) {
+ atomic_dec(&bat_priv->tp_num);
spin_unlock_bh(&bat_priv->tp_list_lock);
batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
"Meter: %s cannot allocate list elements\n",
@@ -1366,8 +1367,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
}
tp_vars = kmalloc_obj(*tp_vars, GFP_ATOMIC);
- if (!tp_vars)
+ if (!tp_vars) {
+ atomic_dec(&bat_priv->tp_num);
goto out_unlock;
+ }
ether_addr_copy(tp_vars->other_end, icmp->orig);
tp_vars->role = BATADV_TP_RECEIVER;
--
2.47.3
^ permalink raw reply related
* [PATCH batadv 0/8] batman-adv: follow up fixes
From: Sven Eckelmann @ 2026-05-03 12:22 UTC (permalink / raw)
To: Marek Lindner, Simon Wunderlich, Antonio Quartulli,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: b.a.t.m.a.n, netdev, linux-kernel, Ao Zhou, Haoze Xie,
Jiexun Wang, Juefei Pu, Luxing Yin, Ren Wei, Ruide Cao, Xin Liu,
Yifan Wu, Yuan Tan, Sven Eckelmann, stable
While reviewing the fixes submitted to batman-adv in the recent weeks,
further problems in similar or adjecent code was identified. This was either
noticed in the manual review or reported by sashiko.dev.
Especially in the TT code, you have the global and the local translation
table. But when a bug was fixed, often only one of the two codepaths was
fixed. It was now tried to sync the TVLV preparation code between to of
them - not style wise but fixes wise. Besides the hardening, it will also
make the code less confusing.
The BLA and TP fixes are just some reference counting fixes - either
reference leak fixes or missing reference handling.
Signed-off-by: Sven Eckelmann <sven@narfation.org>
---
Sven Eckelmann (8):
batman-adv: tp_meter: fix tp_num leak on kmalloc failure
batman-adv: bla: prevent use-after-free when deleting claims
batman-adv: bla: only purge non-released claims
batman-adv: tt: fix negative tt_buff_len
batman-adv: tt: reject oversized local TVLV buffers
batman-adv: tt: fix TOCTOU race for reported vlans
batman-adv: tt: avoid empty VLAN responses
batman-adv: tt: prevent TVLV entry number overflow
net/batman-adv/bridge_loop_avoidance.c | 10 ++++++-
net/batman-adv/tp_meter.c | 5 +++-
net/batman-adv/translation-table.c | 55 +++++++++++++++++++++++++++-------
net/batman-adv/types.h | 2 +-
4 files changed, 58 insertions(+), 14 deletions(-)
---
base-commit: 3d3cf6a7314aca4df0a6dde28ce784a2a30d0166
change-id: 20260503-fixes-followup-064092b7ff55
Best regards,
--
Sven Eckelmann <sven@narfation.org>
^ permalink raw reply
* [PATCH net-next v3 2/2] selftests: openvswitch: add pop_vlan test
From: Minxi Hou @ 2026-05-03 12:09 UTC (permalink / raw)
To: netdev
Cc: aconole, echaudro, i.maximets, davem, edumazet, kuba, pabeni,
horms, shuah, Minxi Hou
In-Reply-To: <20260503120946.51869-1-houminxi@gmail.com>
Add test_pop_vlan() to verify OVS kernel datapath pop_vlan action
correctly strips 802.1Q VLAN tags from frames.
Test structure:
- Baseline: untagged forwarding validates basic connectivity.
- Negative: forward without pop_vlan, assert VLAN tag preserved.
- Positive: forward with pop_vlan, assert tag stripped and
untagged ICMP echo request arrives.
Add start_capture/stop_capture helpers using ovs_wait for
deterministic tcpdump readiness instead of ad-hoc sleep.
Signed-off-by: Minxi Hou <houminxi@gmail.com>
---
.../selftests/net/openvswitch/openvswitch.sh | 192 ++++++++++++++++++
1 file changed, 192 insertions(+)
diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index b327d3061ed5..7c937b48d9b6 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -27,6 +27,7 @@ tests="
upcall_interfaces ovs: test the upcall interfaces
tunnel_metadata ovs: test extraction of tunnel metadata
drop_reason drop: test drop reasons are emitted
+ pop_vlan vlan-pop: POP_VLAN action strips 802.1Q tag
psample psample: Sampling packets with psample"
info() {
@@ -830,6 +831,197 @@ test_tunnel_metadata() {
return 0
}
+# Start tcpdump capture with deterministic readiness wait.
+# Usage: start_capture <netns> <iface> <pcap_path> <out_pid_var> <out_log_var>
+# $4 and $5 are variable NAMES — start_capture writes the tcpdump PID
+# and log path into those caller variables via nameref (bash 4.3+).
+# Contract: caller MUST call stop_capture with the returned PID and log
+# before returning from the function.
+start_capture() {
+ local ns="$1" iface="$2" pcap="$3"
+ local -n _out_pid="$4"
+ local -n _out_log="$5"
+ local log pid
+
+ command -v tcpdump >/dev/null 2>&1 || {
+ info "tcpdump missing"
+ return $ksft_skip
+ }
+
+ log=$(mktemp)
+ ip netns exec "$ns" tcpdump -nei "$iface" \
+ -w "$pcap" -U 2>"$log" &
+ pid=$!
+ ovs_wait grep -q "listening on" "$log" || {
+ kill $pid 2>/dev/null
+ wait $pid 2>/dev/null
+ rm -f "$log"
+ info "FAIL: tcpdump failed to start on $iface"
+ return 1
+ }
+ kill -0 $pid 2>/dev/null || {
+ wait $pid 2>/dev/null
+ rm -f "$log"
+ info "FAIL: tcpdump died after start on $iface"
+ return 1
+ }
+ # $pid/$log expand now (intentional — captures concrete values)
+ on_exit "kill $pid 2>/dev/null; rm -f $log"
+ _out_pid=$pid
+ _out_log=$log
+}
+
+# Stop capture and cleanup temp files.
+# Usage: stop_capture <pid> <log_path>
+stop_capture() {
+ kill "$1" 2>/dev/null
+ wait "$1" 2>/dev/null
+ rm -f "$2"
+}
+
+test_pop_vlan() {
+ modprobe -q openvswitch 2>/dev/null || true
+ [ -d /sys/module/openvswitch ] || return $ksft_skip
+ local ns_err
+ ns_err=$(mktemp)
+ if ! ip netns add __test_pop_vlan_netns__ 2>"$ns_err"; then
+ if grep -q "File exists" "$ns_err"; then
+ ip netns del __test_pop_vlan_netns__ 2>/dev/null
+ else
+ info "CONFIG_NET_NS missing or unavailable"
+ rm -f "$ns_err"
+ return $ksft_skip
+ fi
+ fi
+ ip netns del __test_pop_vlan_netns__ 2>/dev/null
+ rm -f "$ns_err"
+ modprobe -q 8021q 2>/dev/null || true
+ [ -d /sys/module/8021q ] || \
+ { info "CONFIG_VLAN_8021Q missing"; return $ksft_skip; }
+
+ local sbx="test_pop_vlan"
+ sbx_add "$sbx" || return $?
+ ovs_add_dp "$sbx" vlandp || return 1
+
+ # Validate basic connectivity before testing pop_vlan.
+ # --- baseline: untagged forwarding ---
+ ovs_add_netns_and_veths "$sbx" vlandp \
+ ns1 veth1 ns1veth 192.0.2.1/24 || return 1
+ ovs_add_netns_and_veths "$sbx" vlandp \
+ ns2 veth2 ns2veth 192.0.2.2/24 || return 1
+
+ # ARP + IPv4 bidirectional (all untagged)
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' '2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+ ip netns exec ns1 ping -c 3 -W 2 192.0.2.2 || return 1
+
+ # --- POP_VLAN test ---
+ # ns1: VLAN sub-interface generates tagged frames
+ ip -n ns1 link add link ns1veth name ns1veth.10 \
+ type vlan id 10 || return 1
+ on_exit "ip -n ns1 link del ns1veth.10 2>/dev/null || true"
+ ip -n ns1 addr add 198.51.100.1/24 dev ns1veth.10 || return 1
+ ip -n ns1 link set ns1veth.10 up || return 1
+
+ # ns2: no VLAN sub-interface. POP delivers untagged frames to ns2veth
+ ip -n ns2 addr add 198.51.100.2/24 dev ns2veth || return 1
+ on_exit "ip -n ns2 addr del 198.51.100.2/24 dev ns2veth 2>/dev/null || true"
+
+ # veth disable VLAN offload + GRO (ensure kernel software tag processing)
+ if command -v ethtool >/dev/null 2>&1; then
+ ip netns exec ns1 ethtool -k ns1veth 2>/dev/null | grep -q vlan-offload && \
+ ip netns exec ns1 ethtool -K ns1veth rx-vlan-offload off \
+ tx-vlan-offload off gro off 2>/dev/null || true
+ ip netns exec ns2 ethtool -k ns2veth 2>/dev/null | grep -q vlan-offload && \
+ ip netns exec ns2 ethtool -K ns2veth rx-vlan-offload off \
+ tx-vlan-offload off gro off 2>/dev/null || true
+ fi
+
+ ovs_del_flows "$sbx" vlandp
+
+ # Static ARP avoids VLAN-tagged ARP complexity (ns2 has no VLAN
+ # sub-interface, so tagged ARP would be invisible to ns2).
+ local ns1veth10mac ns2mac
+ ns1veth10mac=$(ip -n ns1 link show ns1veth.10 | \
+ awk '/link\/ether/ {print $2}')
+ ns2mac=$(ip -n ns2 link show ns2veth | \
+ awk '/link\/ether/ {print $2}')
+ [ -n "$ns1veth10mac" ] && echo "$ns1veth10mac" | \
+ grep -qE "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" || return 1
+ [ -n "$ns2mac" ] && echo "$ns2mac" | \
+ grep -qE "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" || return 1
+ ip -n ns1 neigh replace 198.51.100.2 lladdr "$ns2mac" \
+ dev ns1veth.10 nud permanent || return 1
+ ip -n ns2 neigh replace 198.51.100.1 lladdr "$ns1veth10mac" \
+ dev ns2veth nud permanent || return 1
+
+ # --- Negative check: fwd without pop_vlan, VLAN tag stays ---
+ local vlan_match='in_port(1),eth(),eth_type(0x8100),'
+ vlan_match+='vlan(vid=10),'
+ vlan_match+='encap(eth_type(0x0800),'
+ vlan_match+='ipv4(src=198.51.100.1,proto=1),icmp())'
+ ovs_add_flow "$sbx" vlandp "$vlan_match" '2' || return 1
+
+ local pcap_no_pop
+ pcap_no_pop=$(mktemp --suffix=.pcap)
+ on_exit "rm -f $pcap_no_pop"
+ local tpid tlog
+ start_capture ns2 ns2veth "$pcap_no_pop" tpid tlog || return $?
+
+ ip netns exec ns1 ping -I ns1veth.10 -c 3 -W 1 198.51.100.2 \
+ >/dev/null 2>&1 || true
+ stop_capture "$tpid" "$tlog"
+
+ # assert: VLAN tag still present (no pop_vlan in action)
+ tcpdump -nr "$pcap_no_pop" 'vlan' 2>/dev/null | grep -q . || {
+ info "FAIL: negative check: no VLAN tag (expected tag present)"
+ return 1
+ }
+
+ ovs_del_flows "$sbx" vlandp
+
+ # --- Positive: pop_vlan strips tag ---
+ ovs_add_flow "$sbx" vlandp "$vlan_match" 'pop_vlan,2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+
+ local pcap
+ pcap=$(mktemp --suffix=.pcap)
+ on_exit "rm -f $pcap"
+ local tpid2 tlog2
+ start_capture ns2 ns2veth "$pcap" tpid2 tlog2 || return $?
+
+ # ns1veth.10 only accepts tagged frames;
+ # ns2 sends untagged reply → dropped by ns1
+ local ping_rc=0
+ ip netns exec ns1 ping -I ns1veth.10 -c 3 -W 1 198.51.100.2 \
+ >/dev/null 2>&1 || ping_rc=$?
+ stop_capture "$tpid2" "$tlog2"
+
+ # ping failure is expected (reply path asymmetric)
+ [ "$ping_rc" -ne 0 ] || {
+ info "FAIL: ping succeeded unexpectedly"
+ return 1
+ }
+
+ # assert: no VLAN tag (POP succeeded), untagged ICMP echo request arrived
+ tcpdump -nr "$pcap" 'vlan' 2>/dev/null | grep -q . && {
+ info "FAIL: POP_VLAN: VLAN tag still present"; return 1
+ }
+ tcpdump -nr "$pcap" 'icmp and icmp[icmptype]=8' \
+ 2>/dev/null | grep -q . || {
+ info "FAIL: POP_VLAN: no untagged ICMP echo request"; return 1
+ }
+
+ return 0
+}
+
run_test() {
(
tname="$1"
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v3 1/2] selftests: openvswitch: add vlan() and encap() flow string parsing
From: Minxi Hou @ 2026-05-03 12:09 UTC (permalink / raw)
To: netdev
Cc: aconole, echaudro, i.maximets, davem, edumazet, kuba, pabeni,
horms, shuah, Minxi Hou
In-Reply-To: <20260503120946.51869-1-houminxi@gmail.com>
Add VLAN TCI formatting and parsing support to ovs-dpctl.py:
- Add _vlan_dpstr() to decompose TCI into vid/pcp/cfi fields,
with raw tci=0x%04x fallback when cfi=0 for round-trip safety.
- Add _parse_vlan_from_flowstr() boundary check for missing ')'.
- Add encap_ovskey subclass restricting nla_map to L2-L4 attributes
(slots 0-21) that appear inside 802.1Q ENCAP, with metadata
attributes set to "none".
- Check parse() return value for unrecognized trailing content.
- Support callable format functions in dpstr() output.
Signed-off-by: Minxi Hou <houminxi@gmail.com>
---
.../selftests/net/openvswitch/ovs-dpctl.py | 267 +++++++++++++++++-
1 file changed, 259 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index 848f61fdcee0..87b1ab7bf201 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -901,11 +901,11 @@ class ovskey(nla):
nla_flags = NLA_F_NESTED
nla_map = (
("OVS_KEY_ATTR_UNSPEC", "none"),
- ("OVS_KEY_ATTR_ENCAP", "none"),
+ ("OVS_KEY_ATTR_ENCAP", "encap_ovskey"),
("OVS_KEY_ATTR_PRIORITY", "uint32"),
("OVS_KEY_ATTR_IN_PORT", "uint32"),
("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
- ("OVS_KEY_ATTR_VLAN", "uint16"),
+ ("OVS_KEY_ATTR_VLAN", "be16"),
("OVS_KEY_ATTR_ETHERTYPE", "be16"),
("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
@@ -1636,6 +1636,204 @@ class ovskey(nla):
class ovs_key_mpls(nla):
fields = (("lse", ">I"),)
+ # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet
+ _VLAN_CFI_MASK = 0x1000
+ _MAX_ENCAP_DEPTH = 4
+ _encap_depth = 0 # single-threaded usage assumed
+
+ @staticmethod
+ def _vlan_dpstr(tci):
+ """Format VLAN TCI as vid=X,pcp=Y,cfi=Z or tci=0xNNNN.
+
+ When cfi=1 (standard Ethernet VLAN), outputs decomposed
+ vid/pcp/cfi fields. When cfi=0 (truncated VLAN header),
+ falls back to raw tci=0x%04x to ensure round-trip
+ correctness: the parser auto-adds cfi=1 for vid/pcp
+ format, so cfi=0 would be lost on re-parse."""
+ vid = tci & 0x0FFF
+ pcp = (tci >> 13) & 0x7
+ cfi = (tci >> 12) & 0x1
+ if cfi:
+ return "vid=%d,pcp=%d,cfi=%d" % (vid, pcp, cfi)
+ return "tci=0x%04x" % tci
+
+ @staticmethod
+ def _parse_vlan_from_flowstr(flowstr):
+ """Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr.
+
+ Returns (remaining_flowstr, key_tci, mask_tci).
+ TCI values use standard bit layout (VID bits 0-11,
+ CFI bit 12, PCP bits 13-15); byte order conversion to
+ big-endian happens in pyroute2 be16 NLA serialization.
+ The mask covers only the fields the caller specified:
+ vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF.
+
+ The tci= key sets the raw TCI bitfield (no CFI validation) to allow
+ non-Ethernet use cases. Use cfi=1 for standard Ethernet VLAN matching.
+ """
+ tci = 0
+ mask = 0
+ has_tci = False
+ has_vid = has_pcp = has_cfi = False
+ _tci_mix_err = "vlan(): 'tci' cannot be mixed " \
+ "with 'vid'/'pcp'/'cfi'"
+ first = True
+ while True:
+ flowstr = flowstr.lstrip()
+ if not flowstr:
+ raise ValueError("vlan(): missing ')'")
+ if flowstr[0] == ')':
+ break
+ if not first:
+ flowstr = flowstr[1:] # skip ','
+ if not flowstr:
+ raise ValueError("vlan(): missing ')' after trailing comma")
+ flowstr = flowstr.lstrip()
+ if flowstr and flowstr[0] == ')':
+ break
+ if flowstr and flowstr[0] == ',':
+ raise ValueError(
+ "vlan(): empty or extra comma in field list")
+ first = False
+
+ eq = flowstr.find('=')
+ if eq == -1:
+ raise ValueError(
+ "vlan(): expected key=value, got '%s'" % flowstr)
+ key = flowstr[:eq].strip()
+ flowstr = flowstr[eq + 1:]
+
+ end = flowstr.find(',')
+ end2 = flowstr.find(')')
+ if end == -1 and end2 == -1:
+ raise ValueError("vlan(): missing ')'")
+ if end == -1 or (end2 != -1 and end2 < end):
+ end = end2
+ val = flowstr[:end].strip()
+ flowstr = flowstr[end:]
+
+ if not val:
+ raise ValueError("vlan(): empty value for key '%s'" % key)
+ try:
+ v = int(val, 16) if val.startswith(('0x', '0X')) else int(val)
+ except ValueError:
+ raise ValueError("vlan(): invalid value '%s' for key '%s'" %
+ (val, key))
+
+ if key == 'tci':
+ if has_tci:
+ raise ValueError("vlan(): duplicate 'tci'")
+ if has_vid or has_pcp or has_cfi:
+ raise ValueError(_tci_mix_err)
+ if v > 0xFFFF or v < 0:
+ raise ValueError("vlan(): tci=0x%x out of range" % v)
+ tci = v
+ mask = 0xFFFF
+ has_tci = True
+ elif key == 'vid':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_vid:
+ raise ValueError("vlan(): duplicate 'vid'")
+ if v < 0 or v > 0xFFF:
+ raise ValueError("vlan(): vid=%d out of range (0-4095)" % v)
+ tci |= v
+ mask |= 0x0FFF
+ has_vid = True
+ elif key == 'pcp':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_pcp:
+ raise ValueError("vlan(): duplicate 'pcp'")
+ if v < 0 or v > 7:
+ raise ValueError("vlan(): pcp=%d out of range (0-7)" % v)
+ tci |= (v & 0x7) << 13
+ mask |= 0xE000
+ has_pcp = True
+ elif key == 'cfi':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_cfi:
+ raise ValueError("vlan(): duplicate 'cfi'")
+ if v != 1:
+ raise ValueError("vlan(): cfi must be 1 for Ethernet")
+ tci |= ovskey._VLAN_CFI_MASK
+ mask |= ovskey._VLAN_CFI_MASK
+ has_cfi = True
+ else:
+ raise ValueError("vlan(): unknown key '%s'" % key)
+
+ flowstr = flowstr[1:] # skip ')'
+ # Catch immediate '))' (user error). A ')' after ',' is consumed
+ # by parse()'s strspn(flowstr, "), ") inter-field separator stripping.
+ if flowstr.lstrip().startswith(')'):
+ raise ValueError("vlan(): unmatched ')'")
+ # parse() strips trailing ',', ')', ' ' as inter-field separators,
+ # so we do not need to call strspn here.
+
+ if mask == 0:
+ raise ValueError("vlan(): no fields specified, "
+ "use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)")
+ if not has_tci:
+ tci |= ovskey._VLAN_CFI_MASK
+ mask |= ovskey._VLAN_CFI_MASK
+ return flowstr, tci, mask
+
+ @staticmethod
+ def _parse_encap_from_flowstr(flowstr):
+ """Parse encap(inner_flow) from flowstr.
+
+ Returns (remaining_flowstr, inner_key_dict, inner_mask_dict)
+ where each dict has an 'attrs' key for recursive NLA encoding.
+ Parenthesis-depth tracking handles nested encap() calls but not
+ quoted strings containing literal parentheses.
+ """
+ if ovskey._encap_depth >= ovskey._MAX_ENCAP_DEPTH:
+ raise ValueError("encap(): max nesting depth %d exceeded" %
+ ovskey._MAX_ENCAP_DEPTH)
+ try:
+ ovskey._encap_depth += 1
+ depth = 1
+ end = -1
+ for i, c in enumerate(flowstr):
+ if c == '(':
+ depth += 1
+ elif c == ')':
+ depth -= 1
+ if depth < 0:
+ raise ValueError(
+ "encap(): unmatched ')' at position %d" % i)
+ if depth == 0:
+ end = i
+ break
+
+ if end == -1:
+ if depth > 1:
+ raise ValueError("encap(): missing ')' at end")
+ raise ValueError("encap(): missing closing ')'")
+
+ inner_str = flowstr[:end].strip()
+ if not inner_str:
+ raise ValueError("encap(): empty inner flow")
+
+ flowstr = flowstr[end + 1:]
+ if flowstr.lstrip().startswith(')'):
+ raise ValueError("encap(): unmatched ')' after encap()")
+ # parse() strips trailing ',', ')', ' ' as inter-field separators,
+ # so we do not need to call strspn here.
+
+ inner_key = encap_ovskey()
+ inner_mask = encap_ovskey()
+ remaining = inner_key.parse(inner_str, inner_mask)
+ if remaining and re.search(r'[^\s,)]', remaining):
+ raise ValueError(
+ "encap(): unrecognized trailing "
+ "content '%s'" % remaining.strip())
+
+ return flowstr, inner_key, inner_mask
+ finally:
+ ovskey._encap_depth -= 1
+
def parse(self, flowstr, mask=None):
for field in (
("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse),
@@ -1657,6 +1855,16 @@ class ovskey(nla):
"eth_type",
lambda x: intparse(x, "0xffff"),
),
+ (
+ "OVS_KEY_ATTR_VLAN",
+ "vlan",
+ ovskey._parse_vlan_from_flowstr,
+ ),
+ (
+ "OVS_KEY_ATTR_ENCAP",
+ "encap",
+ ovskey._parse_encap_from_flowstr,
+ ),
(
"OVS_KEY_ATTR_IPV4",
"ipv4",
@@ -1794,6 +2002,9 @@ class ovskey(nla):
True,
),
("OVS_KEY_ATTR_ETHERNET", None, None, False, False),
+ ("OVS_KEY_ATTR_VLAN", "vlan", ovskey._vlan_dpstr,
+ lambda x: False, True),
+ ("OVS_KEY_ATTR_ENCAP", None, None, False, False),
(
"OVS_KEY_ATTR_ETHERTYPE",
"eth_type",
@@ -1821,22 +2032,61 @@ class ovskey(nla):
v = self.get_attr(field[0])
if v is not None:
m = None if mask is None else mask.get_attr(field[0])
+ fmt = field[2] # str format or callable
if field[4] is False:
print_str += v.dpstr(m, more)
print_str += ","
else:
if m is None or field[3](m):
- print_str += field[1] + "("
- print_str += field[2] % v
- print_str += "),"
+ val = fmt(v) if callable(fmt) else fmt % v
+ print_str += field[1] + "(" + val + "),"
elif more or m != 0:
- print_str += field[1] + "("
- print_str += (field[2] % v) + "/" + (field[2] % m)
- print_str += "),"
+ if callable(fmt):
+ val = fmt(v) + "/" + fmt(m)
+ else:
+ val = (fmt % v) + "/" + (fmt % m)
+ print_str += field[1] + "(" + val + "),"
return print_str
+class encap_ovskey(ovskey):
+ """Inner flow key attributes valid inside 802.1Q ENCAP.
+
+ Only L2-L4 key attributes (slots 0-21) appear inside ENCAP.
+ Metadata-only attributes (SKB_MARK, DP_HASH, RECIRC_ID, etc.)
+ are set to "none" — they never appear inside ENCAP per
+ ovs_nla_put_vlan() in net/openvswitch/flow_netlink.c.
+
+ nla_map indexes must match OVS_KEY_ATTR_* enum values in
+ include/uapi/linux/openvswitch.h.
+ """
+ nla_map = (
+ ("OVS_KEY_ATTR_UNSPEC", "none"), # 0
+ ("OVS_KEY_ATTR_ENCAP", "none"), # 1 — placeholder, no recursion
+ ("OVS_KEY_ATTR_PRIORITY", "none"), # 2 — skb metadata, not in ENCAP
+ ("OVS_KEY_ATTR_IN_PORT", "none"), # 3 — skb metadata, not in ENCAP
+ ("OVS_KEY_ATTR_ETHERNET", "ethaddr"), # 4
+ ("OVS_KEY_ATTR_VLAN", "be16"), # 5
+ ("OVS_KEY_ATTR_ETHERTYPE", "be16"), # 6
+ ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"), # 7
+ ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"), # 8
+ ("OVS_KEY_ATTR_TCP", "ovs_key_tcp"), # 9
+ ("OVS_KEY_ATTR_UDP", "ovs_key_udp"), # 10
+ ("OVS_KEY_ATTR_ICMP", "ovs_key_icmp"), # 11
+ ("OVS_KEY_ATTR_ICMPV6", "ovs_key_icmpv6"), # 12
+ ("OVS_KEY_ATTR_ARP", "ovs_key_arp"), # 13
+ ("OVS_KEY_ATTR_ND", "ovs_key_nd"), # 14
+ ("OVS_KEY_ATTR_SKB_MARK", "none"), # 15 — metadata, not in ENCAP
+ ("OVS_KEY_ATTR_TUNNEL", "none"), # 16 — tunnel metadata, not in ENCAP
+ ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"), # 17
+ ("OVS_KEY_ATTR_TCP_FLAGS", "be16"), # 18
+ ("OVS_KEY_ATTR_DP_HASH", "none"), # 19 — metadata, not in ENCAP
+ ("OVS_KEY_ATTR_RECIRC_ID", "none"), # 20 — metadata, not in ENCAP
+ ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"), # 21
+ )
+
+
class OvsPacket(GenericNetlinkSocket):
OVS_PACKET_CMD_MISS = 1 # Flow table miss
OVS_PACKET_CMD_ACTION = 2 # USERSPACE action
@@ -2576,6 +2826,7 @@ def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()):
def main(argv):
+ nlmsg_atoms.encap_ovskey = encap_ovskey
nlmsg_atoms.ovskey = ovskey
nlmsg_atoms.ovsactions = ovsactions
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v3 0/2] selftests: openvswitch: add pop_vlan test
From: Minxi Hou @ 2026-05-03 12:09 UTC (permalink / raw)
To: netdev
Cc: aconole, echaudro, i.maximets, davem, edumazet, kuba, pabeni,
horms, shuah, Minxi Hou
Add test_pop_vlan() to verify OVS kernel datapath pop_vlan action
correctly strips 802.1Q VLAN tags from frames.
Patch 1 extends ovs-dpctl.py with vlan(vid=X,pcp=Y,cfi=Z) formatting
and parsing, plus an encap_ovskey subclass for safe ENCAP NLA decoding.
Patch 2 adds the selftest with baseline, negative, and positive checks.
Tested with vng on x86_64, all OVS selftests pass (including new
test_pop_vlan).
v3:
- encap_ovskey: MPLS type "ovs_key_mpls" -> "array(ovs_key_mpls)"
- encap_ovskey: PRIORITY/IN_PORT set to "none" (metadata, not in ENCAP)
- _vlan_dpstr: cfi=0 falls back to tci=0x%04x for round-trip safety
- _vlan_dpstr: docstring updated to match cfi=0 fallback behavior
- encap parse(): check return value for unrecognized trailing content
- vlan parser: add boundary check when both ',' and ')' are missing
- start_capture: || return 1 -> || return $? (propagate ksft_skip)
- on_exit: moved after resource creation, not before
- ping success: changed from NOTE to FAIL + return 1
- VLAN interface creation: added || return 1 error propagation
- netns probe: distinguish EEXIST from missing CONFIG_NET_NS
- sbx_add: || return $ksft_skip -> || return $? (match sibling tests)
v2: https://lore.kernel.org/netdev/20260501133924.3100680-1-houminxi@gmail.com/
Minxi Hou (2):
selftests: openvswitch: add vlan() and encap() flow string parsing
selftests: openvswitch: add pop_vlan test
.../selftests/net/openvswitch/openvswitch.sh | 192 +++++++++++++
.../selftests/net/openvswitch/ovs-dpctl.py | 267 +++++++++++++++++-
2 files changed, 451 insertions(+), 8 deletions(-)
--
2.53.0
^ permalink raw reply
* [PATCH iproute2-next] tc: qdisc: provide tcm_handle and tcm_parent to kernel dump requests
From: Eric Dumazet @ 2026-05-03 11:51 UTC (permalink / raw)
To: David Ahern, Stephen Hemminger
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, netdev,
eric.dumazet, Eric Dumazet, Jamal Hadi Salim
linux-7.2 can filter "tc qdisc show ..." on tcm_handle / tcm_parent and
reduce dump costs.
Old kernels ignore these values.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
---
tc/tc_qdisc.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tc/tc_qdisc.c b/tc/tc_qdisc.c
index b3c395b65c2f6b47abbcb6aa37b7dc1f080032e8..7c3e7cb36b4830bca9f2785a58b38d319ec4b563 100644
--- a/tc/tc_qdisc.c
+++ b/tc/tc_qdisc.c
@@ -408,6 +408,9 @@ static int tc_qdisc_list(int argc, char **argv)
argc--; argv++;
}
+ /* Recent kernels (7.2+) can filter on tcm_parent/tcm_handle */
+ req.t.tcm_parent = filter_parent;
+ req.t.tcm_handle = filter_handle;
if (d[0]) {
req.t.tcm_ifindex = ll_name_to_index(d);
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH net-next] net/sched: speedup tc_dump_qdisc() when tcm_handle is provided
From: Eric Dumazet @ 2026-05-03 11:45 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, netdev, eric.dumazet,
Eric Dumazet
"tc qdisc show ... handle xxx" filtering can be done by the kernel.
A followup patch can do the same for tcm_parent.
iproute2/tc needs a small companion patch.
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
net/sched/sch_api.c | 27 +++++++++++++++++----------
1 file changed, 17 insertions(+), 10 deletions(-)
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
index dd0edc9bd4610ec865fc97a81f801a7da020667b..6f7847c5536f16e6754954f0a606581e17257361 100644
--- a/net/sched/sch_api.c
+++ b/net/sched/sch_api.c
@@ -979,13 +979,17 @@ static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
return -EMSGSIZE;
}
-static bool tc_qdisc_dump_ignore(struct Qdisc *q, bool dump_invisible)
+static bool tc_qdisc_dump_ignore(struct Qdisc *q, bool dump_invisible,
+ const struct tcmsg *tcm)
{
if (q->flags & TCQ_F_BUILTIN)
return true;
if ((q->flags & TCQ_F_INVISIBLE) && !dump_invisible)
return true;
-
+ if (tcm) {
+ if (tcm->tcm_handle && tcm->tcm_handle != q->handle)
+ return true;
+ }
return false;
}
@@ -1000,7 +1004,7 @@ static int qdisc_get_notify(struct net *net, struct sk_buff *oskb,
if (!skb)
return -ENOBUFS;
- if (!tc_qdisc_dump_ignore(q, false)) {
+ if (!tc_qdisc_dump_ignore(q, false, NULL)) {
if (tc_fill_qdisc(skb, q, clid, portid, n->nlmsg_seq, 0,
RTM_NEWQDISC, extack) < 0)
goto err_out;
@@ -1030,12 +1034,12 @@ static int qdisc_notify(struct net *net, struct sk_buff *oskb,
if (!skb)
return -ENOBUFS;
- if (old && !tc_qdisc_dump_ignore(old, false)) {
+ if (old && !tc_qdisc_dump_ignore(old, false, NULL)) {
if (tc_fill_qdisc(skb, old, clid, portid, n->nlmsg_seq,
0, RTM_DELQDISC, extack) < 0)
goto err_out;
}
- if (new && !tc_qdisc_dump_ignore(new, false)) {
+ if (new && !tc_qdisc_dump_ignore(new, false, NULL)) {
if (tc_fill_qdisc(skb, new, clid, portid, n->nlmsg_seq,
old ? NLM_F_REPLACE : 0, RTM_NEWQDISC, extack) < 0)
goto err_out;
@@ -1825,21 +1829,24 @@ static int tc_dump_qdisc_root(struct Qdisc *root, struct sk_buff *skb,
int *q_idx_p, int s_q_idx, bool recur,
bool dump_invisible)
{
+ const struct nlmsghdr *nlh = cb->nlh;
int ret = 0, q_idx = *q_idx_p;
+ const struct tcmsg *tcm;
struct Qdisc *q;
int b;
if (!root)
return 0;
+ tcm = nlmsg_data(nlh);
q = root;
if (q_idx < s_q_idx) {
q_idx++;
} else {
- if (!tc_qdisc_dump_ignore(q, dump_invisible))
+ if (!tc_qdisc_dump_ignore(q, dump_invisible, tcm))
ret = tc_fill_qdisc(skb, q, q->parent,
NETLINK_CB(cb->skb).portid,
- cb->nlh->nlmsg_seq, NLM_F_MULTI,
+ nlh->nlmsg_seq, NLM_F_MULTI,
RTM_NEWQDISC, NULL);
if (ret < 0)
goto out;
@@ -1860,10 +1867,10 @@ static int tc_dump_qdisc_root(struct Qdisc *root, struct sk_buff *skb,
q_idx++;
continue;
}
- if (!tc_qdisc_dump_ignore(q, dump_invisible))
+ if (!tc_qdisc_dump_ignore(q, dump_invisible, tcm))
ret = tc_fill_qdisc(skb, q, q->parent,
NETLINK_CB(cb->skb).portid,
- cb->nlh->nlmsg_seq, NLM_F_MULTI,
+ nlh->nlmsg_seq, NLM_F_MULTI,
RTM_NEWQDISC, NULL);
if (ret < 0)
goto out;
@@ -2341,7 +2348,7 @@ static int tc_dump_tclass_qdisc(struct Qdisc *q, struct sk_buff *skb,
{
struct qdisc_dump_args arg;
- if (tc_qdisc_dump_ignore(q, false) ||
+ if (tc_qdisc_dump_ignore(q, false, NULL) ||
*t_p < s_t || !q->ops->cl_ops ||
(tcm->tcm_parent &&
TC_H_MAJ(tcm->tcm_parent) != q->handle)) {
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH net-next v6 10/10] enic: add V2 VF probe with admin channel and PF registration
From: Satish Kharat @ 2026-05-03 11:22 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Sesidhar Baddela, Satish Kharat
In-Reply-To: <20260503-enic-sriov-v2-admin-channel-v2-v6-0-0af4fbc2d86d@cisco.com>
When a V2 SR-IOV VF probes, open the admin channel, initialize the
MBOX protocol, perform the capability check with the PF, and register
with the PF. This establishes the PF-VF communication path that the PF
uses to send link state notifications.
The admin channel and MBOX registration happen after enic_dev_init()
(which discovers admin channel resources) and before register_netdev()
so the VF is fully initialized before the interface is visible to
userspace.
The admin channel is opened before enic_mbox_init() installs the
receive handler. This is safe because enic_admin_rq_cq_service()
checks admin_rq_handler before enqueuing received buffers, so any
interrupt that fires between open and mbox_init is harmlessly
discarded.
On remove, the VF unregisters from the PF and closes its admin channel
before tearing down data path resources.
V2 VFs are not provisioned with an RES_TYPE_SRIOV_INTR resource by
firmware, so bypass that check in the admin channel capability
detection for V2 VFs. The PF still requires this resource.
Reserve an additional MSI-X interrupt for the admin channel when
has_admin_channel is set in enic_set_intr_mode(), so
enic_admin_setup_intr()'s intr_index = intr_count slot is guaranteed
to be within intr_avail bounds even at maximum queue configurations.
The admin INTR is currently allocated from the RES_TYPE_INTR_CTRL
pool shared with the data path. Firmware provisions a dedicated
RES_TYPE_SRIOV_INTR resource for admin channel use; migrating the
admin INTR to vnic_intr_alloc_with_type(... RES_TYPE_SRIOV_INTR) is
planned as a follow-up.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic.h | 1 +
drivers/net/ethernet/cisco/enic/enic_main.c | 95 +++++++++++++++++++++++++++--
drivers/net/ethernet/cisco/enic/enic_res.c | 3 +-
3 files changed, 93 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 1bf7a91ad915..89cf412cec1d 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -450,6 +450,7 @@ void enic_reset_addr_lists(struct enic *enic);
int enic_sriov_enabled(struct enic *enic);
int enic_is_valid_vf(struct enic *enic, int vf);
int enic_is_dynamic(struct enic *enic);
+int enic_is_sriov_vf_v2(struct enic *enic);
void enic_set_ethtool_ops(struct net_device *netdev);
int __enic_set_rsskey(struct enic *enic);
void enic_ext_cq(struct enic *enic);
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index 057716ccc283..643e547f3d48 100644
--- a/drivers/net/ethernet/cisco/enic/enic_main.c
+++ b/drivers/net/ethernet/cisco/enic/enic_main.c
@@ -316,6 +316,11 @@ static int enic_is_sriov_vf(struct enic *enic)
enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2;
}
+int enic_is_sriov_vf_v2(struct enic *enic)
+{
+ return enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2;
+}
+
int enic_is_valid_vf(struct enic *enic, int vf)
{
#ifdef CONFIG_PCI_IOV
@@ -2157,6 +2162,13 @@ static void enic_reset(struct work_struct *work)
enic_set_api_busy(enic, true);
enic_stop(enic->netdev);
+ /* CMD_SOFT_RESET disables all hardware queues including the
+ * admin channel queues (admin_wq, admin_rq, admin_cq). The
+ * recovery path below only reinitializes the data path queues.
+ * If the admin channel was active (V2 SR-IOV), it will be left
+ * in a disabled state after soft reset. Full admin channel
+ * recovery is planned as a future enhancement.
+ */
enic_dev_soft_reset(enic);
enic_reset_addr_lists(enic);
enic_init_vnic_resources(enic);
@@ -2312,16 +2324,27 @@ static int enic_adjust_resources(struct enic *enic)
enic->cq_count = 2;
enic->intr_count = enic->intr_avail;
break;
- case VNIC_DEV_INTR_MODE_MSIX:
+ case VNIC_DEV_INTR_MODE_MSIX: {
/* Adjust the number of wqs/rqs/cqs/interrupts that will be
- * used based on which resource is the most constrained
+ * used based on which resource is the most constrained.
+ * Reserve one extra MSI-X slot for the admin channel INTR
+ * when has_admin_channel is set so that
+ * enic_admin_setup_intr() can allocate at intr_count
+ * within the intr_avail bounds even when the data queue
+ * count is maxed out. intr_count counts only the data-path
+ * IRQs (registered by enic_request_intr()); the admin INTR
+ * lives at msix index intr_count and is set up later by
+ * enic_admin_setup_intr().
*/
+ unsigned int admin_reserve = enic->has_admin_channel ? 1 : 0;
+
wq_avail = min(enic->wq_avail, ENIC_WQ_MAX);
rq_default = max(netif_get_num_default_rss_queues(),
ENIC_RQ_MIN_DEFAULT);
rq_avail = min3(enic->rq_avail, ENIC_RQ_MAX, rq_default);
max_queues = min(enic->cq_avail,
- enic->intr_avail - ENIC_MSIX_RESERVED_INTR);
+ enic->intr_avail - ENIC_MSIX_RESERVED_INTR -
+ admin_reserve);
if (wq_avail + rq_avail <= max_queues) {
enic->rq_count = rq_avail;
enic->wq_count = wq_avail;
@@ -2339,6 +2362,7 @@ static int enic_adjust_resources(struct enic *enic)
enic->intr_count = enic->cq_count + ENIC_MSIX_RESERVED_INTR;
break;
+ }
default:
dev_err(enic_get_dev(enic), "Unknown interrupt mode\n");
return -EINVAL;
@@ -2992,6 +3016,38 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto err_out_dev_close;
}
+ /* V2 VF: open admin channel and register with PF.
+ * Must happen before register_netdev so the VF is fully
+ * initialized before the interface is visible to userspace.
+ *
+ * admin_channel_open() runs before enic_mbox_init() installs
+ * the receive handler. This is safe because
+ * enic_admin_rq_cq_service() checks admin_rq_handler before
+ * enqueuing any received buffer, so interrupts that fire
+ * between open and mbox_init are harmlessly discarded.
+ */
+ if (enic_is_sriov_vf_v2(enic)) {
+ err = enic_admin_channel_open(enic);
+ if (err) {
+ dev_err(dev,
+ "Failed to open admin channel: %d\n", err);
+ goto err_out_dev_deinit;
+ }
+ enic_mbox_init(enic);
+ err = enic_mbox_vf_capability_check(enic);
+ if (err) {
+ dev_err(dev,
+ "MBOX capability check failed: %d\n", err);
+ goto err_out_admin_close;
+ }
+ err = enic_mbox_vf_register(enic);
+ if (err) {
+ dev_err(dev,
+ "MBOX VF registration failed: %d\n", err);
+ goto err_out_admin_close;
+ }
+ }
+
netif_set_real_num_tx_queues(netdev, enic->wq_count);
netif_set_real_num_rx_queues(netdev, enic->rq_count);
@@ -3016,7 +3072,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
err = enic_set_mac_addr(netdev, enic->mac_addr);
if (err) {
dev_err(dev, "Invalid MAC address, aborting\n");
- goto err_out_dev_deinit;
+ goto err_out_admin_close;
}
enic->tx_coalesce_usecs = enic->config.intr_timer_usec;
@@ -3114,11 +3170,23 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
err = register_netdev(netdev);
if (err) {
dev_err(dev, "Cannot register net device, aborting\n");
- goto err_out_dev_deinit;
+ goto err_out_admin_close;
}
return 0;
+err_out_admin_close:
+ if (enic_is_sriov_vf_v2(enic)) {
+ if (enic->vf_registered) {
+ int unreg_err = enic_mbox_vf_unregister(enic);
+
+ if (unreg_err)
+ netdev_warn(netdev,
+ "Failed to unregister from PF: %d\n",
+ unreg_err);
+ }
+ enic_admin_channel_close(enic);
+ }
err_out_dev_deinit:
enic_dev_deinit(enic);
err_out_dev_close:
@@ -3155,6 +3223,23 @@ static void enic_remove(struct pci_dev *pdev)
cancel_work_sync(&enic->reset);
cancel_work_sync(&enic->change_mtu_work);
+
+ /* Close the admin channel and unregister from the PF before
+ * unregister_netdev() to prevent a late PF notification from
+ * touching a netdev that has been freed.
+ */
+ if (enic_is_sriov_vf_v2(enic)) {
+ if (enic->vf_registered) {
+ int unreg_err = enic_mbox_vf_unregister(enic);
+
+ if (unreg_err)
+ netdev_warn(netdev,
+ "Failed to unregister from PF: %d\n",
+ unreg_err);
+ }
+ enic_admin_channel_close(enic);
+ }
+
unregister_netdev(netdev);
#ifdef CONFIG_PCI_IOV
if (enic_sriov_enabled(enic)) {
diff --git a/drivers/net/ethernet/cisco/enic/enic_res.c b/drivers/net/ethernet/cisco/enic/enic_res.c
index 436326ace049..74cd2ee3af5c 100644
--- a/drivers/net/ethernet/cisco/enic/enic_res.c
+++ b/drivers/net/ethernet/cisco/enic/enic_res.c
@@ -211,7 +211,8 @@ void enic_get_res_counts(struct enic *enic)
vnic_dev_get_res_count(enic->vdev, RES_TYPE_ADMIN_RQ) >= 1 &&
vnic_dev_get_res_count(enic->vdev, RES_TYPE_ADMIN_CQ) >=
ARRAY_SIZE(enic->admin_cq) &&
- vnic_dev_get_res_count(enic->vdev, RES_TYPE_SRIOV_INTR) >= 1;
+ (enic_is_sriov_vf_v2(enic) ||
+ vnic_dev_get_res_count(enic->vdev, RES_TYPE_SRIOV_INTR) >= 1);
dev_info(enic_get_dev(enic),
"vNIC resources avail: wq %d rq %d cq %d intr %d admin %s\n",
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 09/10] enic: wire V2 SR-IOV enable with admin channel and MBOX
From: Satish Kharat @ 2026-05-03 11:22 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Sesidhar Baddela, Satish Kharat
In-Reply-To: <20260503-enic-sriov-v2-admin-channel-v2-v6-0-0af4fbc2d86d@cisco.com>
Extend enic_sriov_configure() to handle V2 SR-IOV VFs. When the PF
detects V2 VF device IDs, the enable path allocates per-VF MBOX state,
opens the admin channel, initializes the MBOX protocol, and then calls
pci_enable_sriov(). The admin channel must be ready before VFs are
created so that VF drivers can immediately begin the MBOX capability
and registration handshake during their probe.
The enic_sriov_configure() dispatcher and its V2 helpers
(enic_sriov_v2_enable, enic_sriov_v2_disable) are defined here but
intentionally not yet wired into struct pci_driver via
.sriov_configure -- hence the __maybe_unused annotations. This
series introduces only the admin channel and MBOX infrastructure;
sysfs-driven V2 enable/disable will be activated in a follow-up
patch by adding ".sriov_configure = enic_sriov_configure," to
enic_driver.
The disable path reverses this order: pci_disable_sriov() first (so VF
drivers unregister via MBOX), then the admin channel is closed and
per-VF state is freed.
Reject VF port profile requests when V2 SR-IOV is active
(enic_is_valid_pp_vf), since enic->pp is not reallocated for V2 VFs
and the V2 protocol uses MBOX instead of port profiles.
Update enic_remove() to run enic_dev_deinit() and vnic_dev_close()
after SR-IOV teardown, so the PF device remains functional while VFs
are being cleaned up. This ordering applies to both V1 and V2 SR-IOV
paths.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic.h | 1 +
drivers/net/ethernet/cisco/enic/enic_main.c | 139 ++++++++++++++++++++++++++--
drivers/net/ethernet/cisco/enic/enic_mbox.c | 13 ++-
drivers/net/ethernet/cisco/enic/enic_pp.c | 5 +
drivers/net/ethernet/cisco/enic/enic_res.c | 1 +
drivers/net/ethernet/cisco/enic/vnic_enet.h | 4 +-
6 files changed, 153 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 483053c781df..1bf7a91ad915 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -323,6 +323,7 @@ struct enic {
* waiter.
*/
u8 mbox_expected_reply;
+ bool mbox_initialized;
/* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */
struct enic_vf_state {
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index 53d68272d06a..057716ccc283 100644
--- a/drivers/net/ethernet/cisco/enic/enic_main.c
+++ b/drivers/net/ethernet/cisco/enic/enic_main.c
@@ -60,6 +60,8 @@
#include "enic_clsf.h"
#include "enic_rq.h"
#include "enic_wq.h"
+#include "enic_admin.h"
+#include "enic_mbox.h"
#define ENIC_NOTIFY_TIMER_PERIOD (2 * HZ)
@@ -2689,6 +2691,122 @@ static void enic_sriov_detect_vf_type(struct enic *enic)
enic->vf_type = ENIC_VF_TYPE_NONE;
}
}
+
+static int __maybe_unused
+enic_sriov_v2_enable(struct enic *enic, int num_vfs)
+{
+ int err;
+
+ if (!enic->has_admin_channel) {
+ netdev_err(enic->netdev,
+ "V2 SR-IOV requires admin channel resources\n");
+ return -EOPNOTSUPP;
+ }
+
+ enic->vf_state = kcalloc(num_vfs, sizeof(*enic->vf_state), GFP_KERNEL);
+ if (!enic->vf_state)
+ return -ENOMEM;
+
+ err = enic_admin_channel_open(enic);
+ if (err) {
+ netdev_err(enic->netdev,
+ "Failed to open admin channel: %d\n", err);
+ goto free_vf_state;
+ }
+
+ enic_mbox_init(enic);
+
+ enic->num_vfs = num_vfs;
+
+ err = pci_enable_sriov(enic->pdev, num_vfs);
+ if (err) {
+ netdev_err(enic->netdev,
+ "pci_enable_sriov failed: %d\n", err);
+ goto close_admin;
+ }
+
+ enic->priv_flags |= ENIC_SRIOV_ENABLED;
+ return num_vfs;
+
+close_admin:
+ enic->num_vfs = 0;
+ enic_admin_channel_close(enic);
+free_vf_state:
+ kfree(enic->vf_state);
+ enic->vf_state = NULL;
+ return err;
+}
+
+static void enic_sriov_v2_disable(struct enic *enic)
+{
+ pci_disable_sriov(enic->pdev);
+ enic_admin_channel_close(enic);
+ kfree(enic->vf_state);
+ enic->vf_state = NULL;
+ enic->num_vfs = 0;
+ enic->priv_flags &= ~ENIC_SRIOV_ENABLED;
+}
+
+static int __maybe_unused
+enic_sriov_configure(struct pci_dev *pdev, int num_vfs)
+{
+ struct net_device *netdev = pci_get_drvdata(pdev);
+ struct enic *enic = netdev_priv(netdev);
+ struct enic_port_profile *pp;
+ int err;
+
+ if (num_vfs > 0) {
+ if (enic->config.mq_subvnic_count) {
+ netdev_err(netdev,
+ "SR-IOV not supported with multi-queue sub-vnics\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (enic->vf_type == ENIC_VF_TYPE_NONE) {
+ netdev_err(netdev,
+ "SR-IOV not supported on this firmware version\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (enic->vf_type == ENIC_VF_TYPE_V2)
+ return enic_sriov_v2_enable(enic, num_vfs);
+
+ pp = kcalloc(num_vfs, sizeof(*pp), GFP_KERNEL);
+ if (!pp)
+ return -ENOMEM;
+
+ err = pci_enable_sriov(pdev, num_vfs);
+ if (err) {
+ kfree(pp);
+ return err;
+ }
+
+ kfree(enic->pp);
+ enic->pp = pp;
+ enic->num_vfs = num_vfs;
+ enic->priv_flags |= ENIC_SRIOV_ENABLED;
+ return num_vfs;
+ }
+
+ if (!enic_sriov_enabled(enic))
+ return 0;
+
+ if (enic->vf_type == ENIC_VF_TYPE_V2) {
+ enic_sriov_v2_disable(enic);
+ return 0;
+ }
+
+ pci_disable_sriov(pdev);
+ enic->num_vfs = 0;
+ enic->priv_flags &= ~ENIC_SRIOV_ENABLED;
+
+ kfree(enic->pp);
+ enic->pp = kzalloc_obj(*enic->pp, GFP_KERNEL);
+ if (!enic->pp)
+ return -ENOMEM;
+
+ return 0;
+}
#endif
static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
@@ -2787,12 +2905,18 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto err_out_vnic_unregister;
#ifdef CONFIG_PCI_IOV
- /* Get number of subvnics */
+ enic_sriov_detect_vf_type(enic);
+
+ /* Auto-enable SR-IOV if VFs were pre-configured (e.g. at boot).
+ * V2 VFs require the admin channel, which is not yet set up at probe
+ * time; use sysfs (enic_sriov_configure) to enable V2 SR-IOV instead.
+ */
pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV);
if (pos) {
pci_read_config_word(pdev, pos + PCI_SRIOV_TOTAL_VF,
&enic->num_vfs);
- if (enic->num_vfs) {
+ if (enic->num_vfs &&
+ enic->vf_type != ENIC_VF_TYPE_V2) {
err = pci_enable_sriov(pdev, enic->num_vfs);
if (err) {
dev_err(dev, "SRIOV enable failed, aborting."
@@ -2804,7 +2928,6 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
num_pps = enic->num_vfs;
}
}
- enic_sriov_detect_vf_type(enic);
#endif
/* Allocate structure for port profiles */
@@ -3033,14 +3156,16 @@ static void enic_remove(struct pci_dev *pdev)
cancel_work_sync(&enic->reset);
cancel_work_sync(&enic->change_mtu_work);
unregister_netdev(netdev);
- enic_dev_deinit(enic);
- vnic_dev_close(enic->vdev);
#ifdef CONFIG_PCI_IOV
if (enic_sriov_enabled(enic)) {
- pci_disable_sriov(pdev);
- enic->priv_flags &= ~ENIC_SRIOV_ENABLED;
+ if (enic->vf_type == ENIC_VF_TYPE_V2)
+ enic_sriov_v2_disable(enic);
+ else
+ pci_disable_sriov(pdev);
}
#endif
+ enic_dev_deinit(enic);
+ vnic_dev_close(enic->vdev);
kfree(enic->pp);
vnic_dev_unregister(enic->vdev);
enic_iounmap(enic);
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c
index 7680baece802..71da7f05d4a0 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.c
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -589,8 +589,17 @@ int enic_mbox_vf_unregister(struct enic *enic)
void enic_mbox_init(struct enic *enic)
{
+ /* mbox_lock and mbox_comp must be initialized exactly once per
+ * device lifetime; the PF sriov_configure path can re-enter this
+ * on each enable cycle where these primitives are already set up.
+ */
+ if (!enic->mbox_initialized) {
+ mutex_init(&enic->mbox_lock);
+ init_completion(&enic->mbox_comp);
+ enic->mbox_initialized = true;
+ } else {
+ reinit_completion(&enic->mbox_comp);
+ }
enic->mbox_msg_num = 0;
- mutex_init(&enic->mbox_lock);
- init_completion(&enic->mbox_comp);
enic->admin_rq_handler = enic_mbox_recv_handler;
}
diff --git a/drivers/net/ethernet/cisco/enic/enic_pp.c b/drivers/net/ethernet/cisco/enic/enic_pp.c
index 4720a952725d..3f611e240c25 100644
--- a/drivers/net/ethernet/cisco/enic/enic_pp.c
+++ b/drivers/net/ethernet/cisco/enic/enic_pp.c
@@ -25,6 +25,11 @@ int enic_is_valid_pp_vf(struct enic *enic, int vf, int *err)
if (vf != PORT_SELF_VF) {
#ifdef CONFIG_PCI_IOV
if (enic_sriov_enabled(enic)) {
+ /* V2 SR-IOV uses MBOX, not port profiles */
+ if (enic->vf_type == ENIC_VF_TYPE_V2) {
+ *err = -EOPNOTSUPP;
+ goto err_out;
+ }
if (vf < 0 || vf >= enic->num_vfs) {
*err = -EINVAL;
goto err_out;
diff --git a/drivers/net/ethernet/cisco/enic/enic_res.c b/drivers/net/ethernet/cisco/enic/enic_res.c
index 2b7545d6a67f..436326ace049 100644
--- a/drivers/net/ethernet/cisco/enic/enic_res.c
+++ b/drivers/net/ethernet/cisco/enic/enic_res.c
@@ -59,6 +59,7 @@ int enic_get_vnic_config(struct enic *enic)
GET_CONFIG(intr_timer_usec);
GET_CONFIG(loop_tag);
GET_CONFIG(num_arfs);
+ GET_CONFIG(mq_subvnic_count);
GET_CONFIG(max_rq_ring);
GET_CONFIG(max_wq_ring);
GET_CONFIG(max_cq_ring);
diff --git a/drivers/net/ethernet/cisco/enic/vnic_enet.h b/drivers/net/ethernet/cisco/enic/vnic_enet.h
index 9e8e86262a3f..519d2969990b 100644
--- a/drivers/net/ethernet/cisco/enic/vnic_enet.h
+++ b/drivers/net/ethernet/cisco/enic/vnic_enet.h
@@ -21,7 +21,9 @@ struct vnic_enet_config {
u16 loop_tag;
u16 vf_rq_count;
u16 num_arfs;
- u8 reserved[66];
+ u8 reserved1[32];
+ u16 mq_subvnic_count;
+ u8 reserved2[32];
u32 max_rq_ring; // MAX RQ ring size
u32 max_wq_ring; // MAX WQ ring size
u32 max_cq_ring; // MAX CQ ring size
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 06/10] enic: add MBOX core send and receive for admin channel
From: Satish Kharat @ 2026-05-03 11:22 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Sesidhar Baddela, Satish Kharat
In-Reply-To: <20260503-enic-sriov-v2-admin-channel-v2-v6-0-0af4fbc2d86d@cisco.com>
Implement the mailbox protocol engine used for PF-VF communication
over the admin channel.
The send path (enic_mbox_send_msg) builds a message with a common
header, DMA-maps it, posts a single WQ descriptor with the
destination vnic ID encoded in the VLAN tag field, and polls
the WQ CQ for completion.
The receive path (enic_mbox_recv_handler) is installed as the admin
RQ callback and validates incoming message headers. PF/VF-specific
dispatch will be added in subsequent commits.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/Makefile | 2 +-
drivers/net/ethernet/cisco/enic/enic.h | 6 +
drivers/net/ethernet/cisco/enic/enic_admin.c | 22 +++-
drivers/net/ethernet/cisco/enic/enic_mbox.c | 162 +++++++++++++++++++++++++++
drivers/net/ethernet/cisco/enic/enic_mbox.h | 8 ++
5 files changed, 198 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile
index 7ae72fefc99a..e38aaf34c148 100644
--- a/drivers/net/ethernet/cisco/enic/Makefile
+++ b/drivers/net/ethernet/cisco/enic/Makefile
@@ -4,5 +4,5 @@ obj-$(CONFIG_ENIC) := enic.o
enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \
enic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \
enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \
- enic_admin.o
+ enic_admin.o enic_mbox.o
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 1c09da3c0b1a..42f345aceced 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -292,6 +292,8 @@ struct enic {
/* Admin channel resources for SR-IOV MBOX */
bool has_admin_channel;
+ /* set on send timeout; cleared on channel re-open */
+ bool mbox_send_disabled;
struct vnic_wq admin_wq;
struct vnic_rq admin_rq;
struct vnic_cq admin_cq[2];
@@ -304,6 +306,10 @@ struct enic {
u64 admin_msg_drop_cnt;
void (*admin_rq_handler)(struct enic *enic, void *buf,
unsigned int len);
+
+ /* MBOX protocol state */
+ struct mutex mbox_lock;
+ u64 mbox_msg_num;
};
static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev)
diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c
index 5445815139b5..43e697b7b424 100644
--- a/drivers/net/ethernet/cisco/enic/enic_admin.c
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.c
@@ -19,6 +19,7 @@
#include "cq_enet_desc.h"
#include "wq_enet_desc.h"
#include "rq_enet_desc.h"
+#include "enic_mbox.h"
/* Clean up any admin WQ buffers still held by hardware at close time.
* Normally buffers are freed inline after send completion, but a timed-out
@@ -197,7 +198,25 @@ unsigned int enic_admin_rq_cq_service(struct enic *enic, unsigned int budget)
goto next_desc;
}
- enic_admin_msg_enqueue(enic, buf->os_buf, bytes_written);
+ if (enic->admin_rq_handler) {
+ u16 sender_vlan;
+
+ /* Firmware sets the CQ VLAN field to identify the
+ * sender: 0 = PF, 1-based = VF index. Overwrite
+ * the untrusted src_vnic_id in the MBOX header with
+ * the hardware-verified value.
+ */
+ sender_vlan = le16_to_cpu(rq_desc->vlan);
+ if (bytes_written >= sizeof(struct enic_mbox_hdr)) {
+ struct enic_mbox_hdr *hdr = buf->os_buf;
+
+ hdr->src_vnic_id = (sender_vlan == 0) ?
+ cpu_to_le16(ENIC_MBOX_DST_PF) :
+ cpu_to_le16(sender_vlan - 1);
+ }
+
+ enic_admin_msg_enqueue(enic, buf->os_buf, bytes_written);
+ }
next_desc:
enic_admin_rq_buf_clean(rq, rq->to_clean);
@@ -477,6 +496,7 @@ int enic_admin_channel_open(struct enic *enic)
if (!enic->has_admin_channel)
return -ENODEV;
+ enic->mbox_send_disabled = false;
err = enic_admin_alloc_resources(enic);
if (err) {
netdev_err(enic->netdev,
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c
new file mode 100644
index 000000000000..3b2fc9822176
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright 2025 Cisco Systems, Inc. All rights reserved.
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+
+#include "vnic_dev.h"
+#include "vnic_wq.h"
+#include "vnic_cq.h"
+#include "enic.h"
+#include "enic_admin.h"
+#include "enic_mbox.h"
+#include "wq_enet_desc.h"
+
+#define ENIC_MBOX_POLL_TIMEOUT_US 5000000
+#define ENIC_MBOX_POLL_INTERVAL_US 100
+
+static void enic_mbox_fill_hdr(struct enic *enic, struct enic_mbox_hdr *hdr,
+ u8 msg_type, u16 dst_vnic_id, u16 msg_len)
+{
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->dst_vnic_id = cpu_to_le16(dst_vnic_id);
+ hdr->msg_type = msg_type;
+ hdr->msg_len = cpu_to_le16(msg_len);
+ hdr->msg_num = cpu_to_le64(++enic->mbox_msg_num);
+}
+
+int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
+ void *payload, u16 payload_len)
+{
+ u16 total_len = sizeof(struct enic_mbox_hdr) + payload_len;
+ struct vnic_wq *wq = &enic->admin_wq;
+ struct wq_enet_desc *desc;
+ unsigned long timeout;
+ dma_addr_t dma_addr;
+ u16 vlan_tag;
+ void *buf;
+ int err;
+
+ /* Serialize MBOX sends. The admin channel is a low-frequency
+ * control path; holding the mutex across the poll is acceptable.
+ */
+ mutex_lock(&enic->mbox_lock);
+
+ if (!enic->has_admin_channel || enic->mbox_send_disabled) {
+ err = -ENODEV;
+ goto unlock;
+ }
+
+ if (vnic_wq_desc_avail(wq) == 0) {
+ err = -ENOSPC;
+ goto unlock;
+ }
+
+ buf = kmalloc(total_len, GFP_KERNEL);
+ if (!buf) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ enic_mbox_fill_hdr(enic, buf, msg_type, dst_vnic_id, total_len);
+ if (payload_len) {
+ void *dst = buf + sizeof(struct enic_mbox_hdr);
+
+ memcpy(dst, payload, payload_len);
+ }
+
+ dma_addr = dma_map_single(&enic->pdev->dev, buf, total_len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(&enic->pdev->dev, dma_addr)) {
+ kfree(buf);
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ /* Firmware uses vlan field for routing: 0 = PF, 1-based = VF index */
+ if (dst_vnic_id == ENIC_MBOX_DST_PF)
+ vlan_tag = 0;
+ else
+ vlan_tag = dst_vnic_id + 1;
+
+ desc = vnic_wq_next_desc(wq);
+ wq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET,
+ total_len, 0, 0, 0, 1, 1, 0, 1, vlan_tag, 0);
+ vnic_wq_post(wq, buf, dma_addr, total_len, 1, 1, 1, 1, 0, 0);
+ vnic_wq_doorbell(wq);
+
+ timeout = jiffies + usecs_to_jiffies(ENIC_MBOX_POLL_TIMEOUT_US);
+ err = -ETIMEDOUT;
+ while (time_before(jiffies, timeout)) {
+ if (enic_admin_wq_cq_service(enic)) {
+ err = 0;
+ break;
+ }
+ usleep_range(ENIC_MBOX_POLL_INTERVAL_US,
+ ENIC_MBOX_POLL_INTERVAL_US + 50);
+ }
+ /* Final check in case completion arrived during the last sleep */
+ if (err && enic_admin_wq_cq_service(enic))
+ err = 0;
+
+ if (!err) {
+ wq->to_clean = wq->to_clean->next;
+ wq->ring.desc_avail++;
+ dma_unmap_single(&enic->pdev->dev, dma_addr, total_len,
+ DMA_TO_DEVICE);
+ kfree(buf);
+ } else {
+ netdev_err(enic->netdev,
+ "MBOX send timed out (type %u dst %u), disabling channel\n",
+ msg_type, dst_vnic_id);
+ /*
+ * The WQ descriptor is still live in hardware. Do not unmap
+ * or free the buffer: the device may still DMA from dma_addr.
+ * Mark the channel unusable so no further sends are attempted.
+ */
+ enic->mbox_send_disabled = true;
+ }
+
+ netdev_dbg(enic->netdev,
+ "MBOX send msg_type %u dst %u vlan %u err %d\n",
+ msg_type, dst_vnic_id, vlan_tag, err);
+unlock:
+ mutex_unlock(&enic->mbox_lock);
+ return err;
+}
+
+static void enic_mbox_recv_handler(struct enic *enic, void *buf,
+ unsigned int len)
+{
+ struct enic_mbox_hdr *hdr = buf;
+
+ if (len < sizeof(*hdr)) {
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: truncated message (len %u < %zu)\n",
+ len, sizeof(*hdr));
+ return;
+ }
+
+ if (hdr->msg_type >= ENIC_MBOX_MAX) {
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: unknown msg type %u\n",
+ hdr->msg_type);
+ return;
+ }
+
+ netdev_dbg(enic->netdev,
+ "MBOX recv: type %u from vnic %u len %u\n",
+ hdr->msg_type, le16_to_cpu(hdr->src_vnic_id),
+ le16_to_cpu(hdr->msg_len));
+}
+
+void enic_mbox_init(struct enic *enic)
+{
+ enic->mbox_msg_num = 0;
+ mutex_init(&enic->mbox_lock);
+ enic->admin_rq_handler = enic_mbox_recv_handler;
+}
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h
index a52f1d25cb21..73fd7f783ee2 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.h
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h
@@ -80,4 +80,12 @@ struct enic_mbox_pf_link_state_ack_msg {
struct enic_mbox_generic_reply ack;
};
+#define ENIC_MBOX_DST_PF 0xFFFF
+
+struct enic;
+
+void enic_mbox_init(struct enic *enic);
+int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
+ void *payload, u16 payload_len);
+
#endif /* _ENIC_MBOX_H_ */
--
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